@rubytech/create-maxy-code 0.1.471 → 0.1.473

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 (257) hide show
  1. package/dist/__tests__/google-client-id-env.test.js +70 -0
  2. package/dist/index.js +10 -25
  3. package/dist/port-resolution.js +7 -1
  4. package/package.json +1 -1
  5. package/payload/platform/config/brand.json +1 -0
  6. package/payload/platform/docs/superpowers/plans/2026-07-18-ledger-write-path-fix.md +109 -0
  7. package/payload/platform/lib/ledger-core/dist/reconcile.d.ts +7 -0
  8. package/payload/platform/lib/ledger-core/dist/reconcile.d.ts.map +1 -1
  9. package/payload/platform/lib/ledger-core/dist/reconcile.js +5 -2
  10. package/payload/platform/lib/ledger-core/dist/reconcile.js.map +1 -1
  11. package/payload/platform/lib/ledger-core/src/__tests__/reconcile.test.ts +20 -0
  12. package/payload/platform/lib/ledger-core/src/reconcile.ts +12 -2
  13. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +24 -5
  14. package/payload/platform/plugins/docs/references/google-guide.md +10 -3
  15. package/payload/platform/plugins/docs/references/ledger.md +9 -1
  16. package/payload/platform/plugins/docs/references/troubleshooting.md +4 -0
  17. package/payload/platform/plugins/google/.claude-plugin/plugin.json +1 -1
  18. package/payload/platform/plugins/google/PLUGIN.md +4 -1
  19. package/payload/platform/plugins/google/mcp/dist/__tests__/account-register.test.js +95 -5
  20. package/payload/platform/plugins/google/mcp/dist/__tests__/account-register.test.js.map +1 -1
  21. package/payload/platform/plugins/google/mcp/dist/__tests__/boot.test.d.ts +2 -0
  22. package/payload/platform/plugins/google/mcp/dist/__tests__/boot.test.d.ts.map +1 -0
  23. package/payload/platform/plugins/google/mcp/dist/__tests__/boot.test.js +54 -0
  24. package/payload/platform/plugins/google/mcp/dist/__tests__/boot.test.js.map +1 -0
  25. package/payload/platform/plugins/google/mcp/dist/__tests__/google-client.test.js +26 -1
  26. package/payload/platform/plugins/google/mcp/dist/__tests__/google-client.test.js.map +1 -1
  27. package/payload/platform/plugins/google/mcp/dist/__tests__/index-registration.test.js +3 -2
  28. package/payload/platform/plugins/google/mcp/dist/__tests__/index-registration.test.js.map +1 -1
  29. package/payload/platform/plugins/google/mcp/dist/index.d.ts +9 -5
  30. package/payload/platform/plugins/google/mcp/dist/index.d.ts.map +1 -1
  31. package/payload/platform/plugins/google/mcp/dist/index.js +38 -37
  32. package/payload/platform/plugins/google/mcp/dist/index.js.map +1 -1
  33. package/payload/platform/plugins/google/mcp/dist/lib/google-client.d.ts +10 -7
  34. package/payload/platform/plugins/google/mcp/dist/lib/google-client.d.ts.map +1 -1
  35. package/payload/platform/plugins/google/mcp/dist/lib/google-client.js +25 -13
  36. package/payload/platform/plugins/google/mcp/dist/lib/google-client.js.map +1 -1
  37. package/payload/platform/plugins/google/mcp/dist/tools/account-register.d.ts +60 -2
  38. package/payload/platform/plugins/google/mcp/dist/tools/account-register.d.ts.map +1 -1
  39. package/payload/platform/plugins/google/mcp/dist/tools/account-register.js +141 -11
  40. package/payload/platform/plugins/google/mcp/dist/tools/account-register.js.map +1 -1
  41. package/payload/platform/plugins/google/references/auth.md +91 -74
  42. package/payload/platform/plugins/ledger/PLUGIN.md +13 -1
  43. package/payload/platform/plugins/ledger/mcp/dist/index.js +23 -15
  44. package/payload/platform/plugins/ledger/mcp/dist/index.js.map +1 -1
  45. package/payload/platform/plugins/ledger/mcp/dist/lib/read.d.ts +11 -1
  46. package/payload/platform/plugins/ledger/mcp/dist/lib/read.d.ts.map +1 -1
  47. package/payload/platform/plugins/ledger/mcp/dist/lib/read.js +20 -1
  48. package/payload/platform/plugins/ledger/mcp/dist/lib/read.js.map +1 -1
  49. package/payload/platform/plugins/ledger/mcp/dist/lib/types.d.ts +14 -11
  50. package/payload/platform/plugins/ledger/mcp/dist/lib/types.d.ts.map +1 -1
  51. package/payload/platform/plugins/ledger/mcp/dist/lib/types.js.map +1 -1
  52. package/payload/platform/plugins/ledger/mcp/dist/lib/write-rejects.d.ts +12 -0
  53. package/payload/platform/plugins/ledger/mcp/dist/lib/write-rejects.d.ts.map +1 -0
  54. package/payload/platform/plugins/ledger/mcp/dist/lib/write-rejects.js +50 -0
  55. package/payload/platform/plugins/ledger/mcp/dist/lib/write-rejects.js.map +1 -0
  56. package/payload/platform/plugins/ledger/mcp/dist/tools/cash-record.d.ts +5 -5
  57. package/payload/platform/plugins/ledger/mcp/dist/tools/cash-record.d.ts.map +1 -1
  58. package/payload/platform/plugins/ledger/mcp/dist/tools/cash-record.js +11 -11
  59. package/payload/platform/plugins/ledger/mcp/dist/tools/cash-record.js.map +1 -1
  60. package/payload/platform/plugins/ledger/mcp/dist/tools/invoice-record.d.ts +7 -2
  61. package/payload/platform/plugins/ledger/mcp/dist/tools/invoice-record.d.ts.map +1 -1
  62. package/payload/platform/plugins/ledger/mcp/dist/tools/invoice-record.js +9 -3
  63. package/payload/platform/plugins/ledger/mcp/dist/tools/invoice-record.js.map +1 -1
  64. package/payload/platform/plugins/ledger/mcp/dist/tools/payment-record.d.ts +7 -0
  65. package/payload/platform/plugins/ledger/mcp/dist/tools/payment-record.d.ts.map +1 -1
  66. package/payload/platform/plugins/ledger/mcp/dist/tools/payment-record.js +3 -4
  67. package/payload/platform/plugins/ledger/mcp/dist/tools/payment-record.js.map +1 -1
  68. package/payload/platform/plugins/ledger/mcp/dist/tools/reconcile.d.ts +1 -1
  69. package/payload/platform/plugins/ledger/mcp/dist/tools/reconcile.d.ts.map +1 -1
  70. package/payload/platform/plugins/ledger/mcp/dist/tools/reconcile.js +4 -1
  71. package/payload/platform/plugins/ledger/mcp/dist/tools/reconcile.js.map +1 -1
  72. package/payload/platform/services/claude-session-manager/dist/activity-rows.d.ts +27 -2
  73. package/payload/platform/services/claude-session-manager/dist/activity-rows.d.ts.map +1 -1
  74. package/payload/platform/services/claude-session-manager/dist/activity-rows.js +33 -2
  75. package/payload/platform/services/claude-session-manager/dist/activity-rows.js.map +1 -1
  76. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
  77. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +1 -0
  78. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
  79. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  80. package/payload/platform/services/claude-session-manager/dist/http-server.js +3 -0
  81. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  82. package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
  83. package/payload/server/{chunk-BSTIQX52.js → chunk-Y6V45R6M.js} +1 -12
  84. package/payload/server/maxy-edge.js +1 -1
  85. package/payload/server/public/activity.html +6 -4
  86. package/payload/server/public/assets/AdminLoginScreens-B7TQZV8u.js +1 -0
  87. package/payload/server/public/assets/AdminShell-DAuri4Yh.js +2 -0
  88. package/payload/server/public/assets/Checkbox-B1XhnhPk.js +1 -0
  89. package/payload/server/public/assets/activity-kRwzxEKU.js +1 -0
  90. package/payload/server/public/assets/admin-BdcccSb5.js +1 -0
  91. package/payload/server/public/assets/{arc-CFMJyMm-.js → arc-BrHWN2Kr.js} +1 -1
  92. package/payload/server/public/assets/architecture-YZFGNWBL-CZQzGRSM.js +1 -0
  93. package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-CmOpXLsH.js → architectureDiagram-Q4EWVU46-Clt9NOr0.js} +1 -1
  94. package/payload/server/public/assets/{blockDiagram-DXYQGD6D-CmS8f9YF.js → blockDiagram-DXYQGD6D-BOe_jmXi.js} +1 -1
  95. package/payload/server/public/assets/browser-B6woQ2qX.js +1 -0
  96. package/payload/server/public/assets/{c4Diagram-AHTNJAMY-BXIjMl6A.js → c4Diagram-AHTNJAMY-B64FaKMV.js} +1 -1
  97. package/payload/server/public/assets/calendar-CalCeg5T.js +1 -0
  98. package/payload/server/public/assets/channel-zDUAN9Ks.js +1 -0
  99. package/payload/server/public/assets/chat-9s2-L06v.js +1 -0
  100. package/payload/server/public/assets/chevron-left-C3ho8SnB.js +1 -0
  101. package/payload/server/public/assets/{chunk-2KRD3SAO-D4fktAwW.js → chunk-2KRD3SAO-BGAUfWvM.js} +1 -1
  102. package/payload/server/public/assets/{chunk-336JU56O-C2MKnRTj.js → chunk-336JU56O-PFi6uQZ0.js} +2 -2
  103. package/payload/server/public/assets/chunk-426QAEUC-iJnqNRug.js +1 -0
  104. package/payload/server/public/assets/{chunk-4BX2VUAB-C13Wz3zS.js → chunk-4BX2VUAB-CkxXXCiF.js} +1 -1
  105. package/payload/server/public/assets/{chunk-4TB4RGXK-CalYahTR.js → chunk-4TB4RGXK-B-7tN4IA.js} +1 -1
  106. package/payload/server/public/assets/{chunk-55IACEB6-TmOKdy8a.js → chunk-55IACEB6-CBkNmqrt.js} +1 -1
  107. package/payload/server/public/assets/{chunk-5FUZZQ4R-C-i0BVag.js → chunk-5FUZZQ4R-wa9ujn7i.js} +1 -1
  108. package/payload/server/public/assets/{chunk-5PVQY5BW-D6bJMLnf.js → chunk-5PVQY5BW-DIk-xBaE.js} +1 -1
  109. package/payload/server/public/assets/{chunk-67CJDMHE-Cif9Uu5C.js → chunk-67CJDMHE-BbMV1lgW.js} +1 -1
  110. package/payload/server/public/assets/{chunk-7N4EOEYR-WjTHCXnd.js → chunk-7N4EOEYR-C49Mv-nQ.js} +1 -1
  111. package/payload/server/public/assets/{chunk-AA7GKIK3-CwCfdQmb.js → chunk-AA7GKIK3-Dy_pJOp_.js} +1 -1
  112. package/payload/server/public/assets/{chunk-BSJP7CBP-9A67yXwT.js → chunk-BSJP7CBP-Ce-OmPqn.js} +1 -1
  113. package/payload/server/public/assets/{chunk-CIAEETIT-DPMiWSBi.js → chunk-CIAEETIT-tnjq-APC.js} +1 -1
  114. package/payload/server/public/assets/{chunk-EDXVE4YY-B9PBdwiT.js → chunk-EDXVE4YY-Bvv_deZ2.js} +1 -1
  115. package/payload/server/public/assets/{chunk-ENJZ2VHE-CM5OqcQ5.js → chunk-ENJZ2VHE-CmxCPKyU.js} +1 -1
  116. package/payload/server/public/assets/{chunk-FMBD7UC4-Bmt_ZRlV.js → chunk-FMBD7UC4-Top6y08S.js} +1 -1
  117. package/payload/server/public/assets/{chunk-FOC6F5B3-DfGJGxpX.js → chunk-FOC6F5B3-B9LPNCUS.js} +1 -1
  118. package/payload/server/public/assets/{chunk-ICPOFSXX-D6M89DA0.js → chunk-ICPOFSXX-Bi-_c351.js} +2 -2
  119. package/payload/server/public/assets/{chunk-K5T4RW27-DMpoaqw2.js → chunk-K5T4RW27-DAnRM04h.js} +1 -1
  120. package/payload/server/public/assets/{chunk-KGLVRYIC-DYUFgri2.js → chunk-KGLVRYIC-C0QYcbLe.js} +1 -1
  121. package/payload/server/public/assets/{chunk-LIHQZDEY-x3LmeKmx.js → chunk-LIHQZDEY-BjvA5ykt.js} +1 -1
  122. package/payload/server/public/assets/{chunk-ORNJ4GCN-BeuNAWkW.js → chunk-ORNJ4GCN-Cs46Gj7z.js} +1 -1
  123. package/payload/server/public/assets/{chunk-OYMX7WX6-Bv6gisQ6.js → chunk-OYMX7WX6-BBpimab5.js} +1 -1
  124. package/payload/server/public/assets/chunk-QZHKN3VN-Qlc2PFAj.js +1 -0
  125. package/payload/server/public/assets/{chunk-U2HBQHQK-C2iT9Av2.js → chunk-U2HBQHQK-CQUSBHoK.js} +1 -1
  126. package/payload/server/public/assets/{chunk-X2U36JSP-Bd6bv87A.js → chunk-X2U36JSP-Dm8TevG0.js} +1 -1
  127. package/payload/server/public/assets/{chunk-XPW4576I-ByFnN4JO.js → chunk-XPW4576I-B10QuxYb.js} +1 -1
  128. package/payload/server/public/assets/{chunk-YZCP3GAM-Bt8I6ZDb.js → chunk-YZCP3GAM-vaGsu4Wk.js} +1 -1
  129. package/payload/server/public/assets/{chunk-ZZ45TVLE-DBMPgZ_A.js → chunk-ZZ45TVLE-D1Qi22bL.js} +1 -1
  130. package/payload/server/public/assets/classDiagram-6PBFFD2Q-CFsKL52p.js +1 -0
  131. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-BWBUUCiO.js +1 -0
  132. package/payload/server/public/assets/clone-h5XhkDUL.js +1 -0
  133. package/payload/server/public/assets/{cose-bilkent-S5V4N54A-DgLEaApT.js → cose-bilkent-S5V4N54A-CRDvLRXs.js} +1 -1
  134. package/payload/server/public/assets/{dagre-mSazqXgG.js → dagre-CEmqJTt3.js} +1 -1
  135. package/payload/server/public/assets/{dagre-KV5264BT-kWVPgmmL.js → dagre-KV5264BT-LXMTo7Fp.js} +1 -1
  136. package/payload/server/public/assets/data-Cfr-7ERI.js +1 -0
  137. package/payload/server/public/assets/{diagram-5BDNPKRD-BHe_omJD.js → diagram-5BDNPKRD-zyis6nPt.js} +1 -1
  138. package/payload/server/public/assets/{diagram-G4DWMVQ6-fUFsmM3x.js → diagram-G4DWMVQ6-BiU0XAAD.js} +1 -1
  139. package/payload/server/public/assets/{diagram-MMDJMWI5-DmujPLlM.js → diagram-MMDJMWI5-rGFiz1SX.js} +1 -1
  140. package/payload/server/public/assets/{diagram-TYMM5635-B7Aoas89.js → diagram-TYMM5635-B0wak1ZJ.js} +1 -1
  141. package/payload/server/public/assets/{erDiagram-SMLLAGMA-6hS1rGiO.js → erDiagram-SMLLAGMA-D8AUxLyX.js} +1 -1
  142. package/payload/server/public/assets/{flatten-BXzaHgtL.js → flatten-naJ5PJ4f.js} +1 -1
  143. package/payload/server/public/assets/{flowDiagram-DWJPFMVM-Bqhr_r5b.js → flowDiagram-DWJPFMVM-BtdUf68b.js} +1 -1
  144. package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-B8kjyN4o.js → ganttDiagram-T4ZO3ILL-C-lkzDmf.js} +1 -1
  145. package/payload/server/public/assets/gitGraph-7Q5UKJZL-CBZPQJLj.js +1 -0
  146. package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF--TTRq4ir.js → gitGraphDiagram-UUTBAWPF-DRUKumq2.js} +1 -1
  147. package/payload/server/public/assets/{graph-C252ZHjp.js → graph-HeZaatSQ.js} +3 -3
  148. package/payload/server/public/assets/graph-labels-4W7JuOgG.js +1 -0
  149. package/payload/server/public/assets/{graphlib-CWkjawc5.js → graphlib-zRsaoFEC.js} +1 -1
  150. package/payload/server/public/assets/info-OMHHGYJF-1vIoX-kP.js +1 -0
  151. package/payload/server/public/assets/infoDiagram-42DDH7IO-KHcXRaan.js +2 -0
  152. package/payload/server/public/assets/{isEmpty-uB1dEu_Z.js → isEmpty-Bnpg7b8f.js} +1 -1
  153. package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-CR8B7VzM.js → ishikawaDiagram-UXIWVN3A-BguMgoR8.js} +1 -1
  154. package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-45lWAvtq.js → journeyDiagram-VCZTEJTY-CXhyK1n-.js} +1 -1
  155. package/payload/server/public/assets/{kanban-definition-6JOO6SKY-DkrPXLVb.js → kanban-definition-6JOO6SKY-C0UCWVx-.js} +1 -1
  156. package/payload/server/public/assets/{line-Db0H446a.js → line--uhS9YDE.js} +1 -1
  157. package/payload/server/public/assets/{linear-X3O7lwK3.js → linear-CAOWPodQ.js} +1 -1
  158. package/payload/server/public/assets/maximize-2-DKx-aTPb.js +1 -0
  159. package/payload/server/public/assets/{mermaid-parser.core-CixBHOya.js → mermaid-parser.core-D6kPx7vc.js} +2 -2
  160. package/payload/server/public/assets/{mermaid.core-BGQq98L7.js → mermaid.core-CTd4DS6O.js} +3 -3
  161. package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-v9cJl1mj.js → mindmap-definition-QFDTVHPH-_AyvyG8C.js} +1 -1
  162. package/payload/server/public/assets/operator-DD9L2VJM.js +1 -0
  163. package/payload/server/public/assets/{ordinal-CZY9laxg.js → ordinal-Wx4m9zHk.js} +1 -1
  164. package/payload/server/public/assets/packet-4T2RLAQJ-CTU0gCGb.js +1 -0
  165. package/payload/server/public/assets/page-Ceado9Kr.js +32 -0
  166. package/payload/server/public/assets/page-tJxw0zNO.js +1 -0
  167. package/payload/server/public/assets/{pdf-render-7HZBTZ0_.js → pdf-render-CeX6d2v7.js} +1 -1
  168. package/payload/server/public/assets/pie-ZZUOXDRM-D-EEHxhC.js +1 -0
  169. package/payload/server/public/assets/{pieDiagram-DEJITSTG-DQ0b6edX.js → pieDiagram-DEJITSTG-6TsUDkkQ.js} +1 -1
  170. package/payload/server/public/assets/public-BYFWxyeC.js +1 -0
  171. package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-B64mj7Ei.js → quadrantDiagram-34T5L4WZ-CWw9gOI4.js} +1 -1
  172. package/payload/server/public/assets/radar-PYXPWWZC-DHXATZGB.js +1 -0
  173. package/payload/server/public/assets/{reduce-BGQzAenn.js → reduce-BUmc8RDR.js} +1 -1
  174. package/payload/server/public/assets/{requirementDiagram-MS252O5E-CE2JIEWo.js → requirementDiagram-MS252O5E-DCOlxNvx.js} +1 -1
  175. package/payload/server/public/assets/rotate-ccw-DrzdV9h7.js +1 -0
  176. package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-Cmt5cfCz.js → sankeyDiagram-XADWPNL6-CW4Xbir3.js} +1 -1
  177. package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-B5vVn_sw.js → sequenceDiagram-FGHM5R23-vhb_pBLG.js} +1 -1
  178. package/payload/server/public/assets/{stateDiagram-FHFEXIEX-DiUEJ-RZ.js → stateDiagram-FHFEXIEX-Cb1WExEX.js} +1 -1
  179. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-CxOIKvXh.js +1 -0
  180. package/payload/server/public/assets/tasks-DSwoxGzh.js +1 -0
  181. package/payload/server/public/assets/time-entry-format-Bn79w6tq.js +1 -0
  182. package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-C9jprDVw.js → timeline-definition-GMOUNBTQ-BAHTf_AF.js} +1 -1
  183. package/payload/server/public/assets/treeView-SZITEDCU-CC7ouMFd.js +1 -0
  184. package/payload/server/public/assets/treemap-W4RFUUIX-CaCg27im.js +1 -0
  185. package/payload/server/public/assets/triangle-alert-CjDVh6r-.js +1 -0
  186. package/payload/server/public/assets/useCopyFeedback-DXHkkJw-.js +1 -0
  187. package/payload/server/public/assets/useSelectionMode-D2H8jAhE.js +1 -0
  188. package/payload/server/public/assets/{HeaderMenu-DtIEBHOU.css → useSubAccountSwitcher-Br7Z6Cpu.css} +1 -1
  189. package/payload/server/public/assets/useSubAccountSwitcher-CUJpgkKI.js +9 -0
  190. package/payload/server/public/assets/useVoiceRecorder-zUvPe55I.js +2 -0
  191. package/payload/server/public/assets/{vennDiagram-DHZGUBPP-DWqQJ9X0.js → vennDiagram-DHZGUBPP-DtEttvW4.js} +1 -1
  192. package/payload/server/public/assets/wardley-RL74JXVD-bMPsqQZ7.js +1 -0
  193. package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-Dg6c2Voh.js → wardleyDiagram-NUSXRM2D-K06y6CVJ.js} +1 -1
  194. package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-EhN-zeDj.js → xychartDiagram-5P7HB3ND-D66c40lp.js} +1 -1
  195. package/payload/server/public/browser.html +5 -6
  196. package/payload/server/public/calendar.html +6 -7
  197. package/payload/server/public/chat.html +13 -14
  198. package/payload/server/public/data.html +12 -13
  199. package/payload/server/public/graph.html +11 -12
  200. package/payload/server/public/index.html +15 -16
  201. package/payload/server/public/operator.html +15 -16
  202. package/payload/server/public/public.html +13 -14
  203. package/payload/server/public/tasks.html +5 -6
  204. package/payload/server/server.js +1419 -1312
  205. package/payload/server/public/assets/AdminLoginScreens-Rf94VD9w.js +0 -1
  206. package/payload/server/public/assets/AdminShell-CyGKg7MG.js +0 -2
  207. package/payload/server/public/assets/Checkbox-B0jmI2y2.js +0 -1
  208. package/payload/server/public/assets/HeaderMenu-BnTYGNaB.js +0 -9
  209. package/payload/server/public/assets/activity-BOBTWFr-.js +0 -1
  210. package/payload/server/public/assets/admin-HDLzwbLl.js +0 -1
  211. package/payload/server/public/assets/architecture-YZFGNWBL-Cypt5qHL.js +0 -1
  212. package/payload/server/public/assets/browser-D4_XLSSm.js +0 -1
  213. package/payload/server/public/assets/calendar-s5V_gbI7.js +0 -1
  214. package/payload/server/public/assets/channel-DhrFAm7m.js +0 -1
  215. package/payload/server/public/assets/chat-40WyKzGb.js +0 -1
  216. package/payload/server/public/assets/chevron-left-C35qGP5f.js +0 -1
  217. package/payload/server/public/assets/chunk-426QAEUC-BhU7-TyS.js +0 -1
  218. package/payload/server/public/assets/chunk-QZHKN3VN-7_kS3RBy.js +0 -1
  219. package/payload/server/public/assets/classDiagram-6PBFFD2Q-BrWqm88p.js +0 -1
  220. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-D0wzG5Fd.js +0 -1
  221. package/payload/server/public/assets/clone-DQA-nNrl.js +0 -1
  222. package/payload/server/public/assets/data-Bq0vU3qJ.js +0 -1
  223. package/payload/server/public/assets/gitGraph-7Q5UKJZL-C_-XjxLd.js +0 -1
  224. package/payload/server/public/assets/graph-labels-DzPbbhLc.js +0 -1
  225. package/payload/server/public/assets/info-OMHHGYJF-BH8yDCfL.js +0 -1
  226. package/payload/server/public/assets/infoDiagram-42DDH7IO-D8U4M0pK.js +0 -2
  227. package/payload/server/public/assets/maximize-2-CEbXFpQT.js +0 -1
  228. package/payload/server/public/assets/operator-zKk4HMdX.js +0 -1
  229. package/payload/server/public/assets/packet-4T2RLAQJ-Bajcns4r.js +0 -1
  230. package/payload/server/public/assets/page-BrmN_vzV.js +0 -1
  231. package/payload/server/public/assets/page-Cts-ESBe.js +0 -32
  232. package/payload/server/public/assets/pie-ZZUOXDRM-D7hjM5w1.js +0 -1
  233. package/payload/server/public/assets/public-BItzHBc0.js +0 -1
  234. package/payload/server/public/assets/radar-PYXPWWZC-B46d5x7a.js +0 -1
  235. package/payload/server/public/assets/rotate-ccw-B0EYGhGx.js +0 -1
  236. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-DIm3XuAD.js +0 -1
  237. package/payload/server/public/assets/tasks-ClE8-be8.js +0 -1
  238. package/payload/server/public/assets/time-entry-format-BwjGLnXu.js +0 -1
  239. package/payload/server/public/assets/treeView-SZITEDCU-3XQh5xQ1.js +0 -1
  240. package/payload/server/public/assets/treemap-W4RFUUIX-DwR-_l5z.js +0 -1
  241. package/payload/server/public/assets/triangle-alert-DejWYn1x.js +0 -1
  242. package/payload/server/public/assets/useCopyFeedback-CpSgOavK.js +0 -1
  243. package/payload/server/public/assets/useSelectionMode-ZQZQUgze.js +0 -1
  244. package/payload/server/public/assets/useSubAccountSwitcher-DuRLvCfO.js +0 -1
  245. package/payload/server/public/assets/useVoiceRecorder-BTYYeUJW.js +0 -2
  246. package/payload/server/public/assets/wardley-RL74JXVD-DZp9oCGv.js +0 -1
  247. /package/payload/server/public/assets/{_baseFor-CMWvz0uA.js → _baseFor-k8hYPe-U.js} +0 -0
  248. /package/payload/server/public/assets/{array-Cny_NT6T.js → array-BFDiaBgf.js} +0 -0
  249. /package/payload/server/public/assets/{cytoscape.esm-De0CCmB2.js → cytoscape.esm-Ch0xptA4.js} +0 -0
  250. /package/payload/server/public/assets/{defaultLocale-BahRht8U.js → defaultLocale-ZeknFqNe.js} +0 -0
  251. /package/payload/server/public/assets/{dist-C9D_xz81.js → dist-B2N0v8hZ.js} +0 -0
  252. /package/payload/server/public/assets/{init-DoIvjoJ_.js → init-0VJVrkRJ.js} +0 -0
  253. /package/payload/server/public/assets/{katex-Bfn1OZEl.js → katex-Br7bH10N.js} +0 -0
  254. /package/payload/server/public/assets/{path-CTezU8L5.js → path-INs8XTPH.js} +0 -0
  255. /package/payload/server/public/assets/{preload-helper-CT1Z6Pdu.js → preload-helper-D_sPnjeL.js} +0 -0
  256. /package/payload/server/public/assets/{rough.esm-BJo4pvXu.js → rough.esm-DU9bZLvZ.js} +0 -0
  257. /package/payload/server/public/assets/{src-BTKk8KAG.js → src-DHSQDYx-.js} +0 -0
@@ -0,0 +1,70 @@
1
+ // acceptance gate for GOOGLE_CLIENT_ID / GOOGLE_PUBLIC_BASE stamping in the
2
+ // claude-session-manager systemd unit (Task 1761).
3
+ //
4
+ // This test exists because the first fix stamped only the darwin `.env` mirror
5
+ // and missed this Linux unit, so on the actual (Ubuntu) install the google
6
+ // plugin loaded with an empty GOOGLE_CLIENT_ID, registered nothing usable, and
7
+ // the agent fell back to claude.ai connector advice — the very behaviour the
8
+ // plugin was built to remove. The unit env is the ONLY path the value reaches
9
+ // the MCP server: google is `mcp-manifest: auto`, whose generator discards the
10
+ // declared mcp.env block.
11
+ //
12
+ // Three invariants:
13
+ // (a) a brand client id AND an install public base emit both Environment lines.
14
+ // (b) either one missing emits NEITHER line — a client id without a redirect
15
+ // origin builds a consent URL Google rejects on its byte-for-byte
16
+ // redirect_uri comparison, so a half-stamp is worse than no stamp.
17
+ // (c) stamping google never disturbs the outlook pair.
18
+ import test from "node:test";
19
+ import assert from "node:assert/strict";
20
+ import { buildClaudeSessionManagerUnitFile } from "../port-resolution.js";
21
+ const ACCOUNT_ID = "12345678-9abc-def0-1234-56789abcdef0";
22
+ const GOOGLE_ID = "1032860640027-p61l59fnog4mad5o7grginpaps9jnu8c.apps.googleusercontent.com";
23
+ const SECRET = "GOCSPX-test-value-not-real";
24
+ const OUTLOOK_ID = "0fa68fe2-8bec-4007-a0c7-776636fbcf97";
25
+ function build(opts) {
26
+ return buildClaudeSessionManagerUnitFile({
27
+ productName: "SiteDesk",
28
+ brandHostname: "sitedesk-code",
29
+ installDir: "/home/me/sitedesk-code",
30
+ persistDir: "/home/me/.sitedesk-code",
31
+ claudeSessionManagerPort: 19402,
32
+ brandPort: 19300,
33
+ maxyUiInternalPort: 19301,
34
+ accountId: ACCOUNT_ID,
35
+ cdpPort: 9224,
36
+ ...opts,
37
+ });
38
+ }
39
+ test("id and secret present stamps both Environment lines", () => {
40
+ const unit = build({ googleClientId: GOOGLE_ID, googleClientSecret: SECRET });
41
+ assert.ok(unit.includes(`Environment=GOOGLE_CLIENT_ID=${GOOGLE_ID}`));
42
+ assert.ok(unit.includes(`Environment=GOOGLE_CLIENT_SECRET=${SECRET}`));
43
+ // The hosted callback is withdrawn (Task 1791); nothing consumes a public base.
44
+ assert.ok(!unit.includes("GOOGLE_PUBLIC_BASE"));
45
+ });
46
+ test("a client id with no secret stamps NEITHER line", () => {
47
+ const unit = build({ googleClientId: GOOGLE_ID });
48
+ assert.ok(!unit.includes("GOOGLE_CLIENT_ID"), "a half-stamp must not ship");
49
+ assert.ok(!unit.includes("GOOGLE_CLIENT_SECRET"));
50
+ });
51
+ test("a secret with no client id stamps NEITHER line", () => {
52
+ const unit = build({ googleClientSecret: SECRET });
53
+ assert.ok(!unit.includes("GOOGLE_CLIENT_ID"));
54
+ assert.ok(!unit.includes("GOOGLE_CLIENT_SECRET"));
55
+ });
56
+ test("a brand shipping no google app stamps neither line", () => {
57
+ const unit = build({});
58
+ assert.ok(!unit.includes("GOOGLE_CLIENT_ID"));
59
+ assert.ok(!unit.includes("GOOGLE_PUBLIC_BASE"));
60
+ });
61
+ test("google stamping leaves the outlook pair intact", () => {
62
+ const unit = build({
63
+ googleClientId: GOOGLE_ID,
64
+ googleClientSecret: SECRET,
65
+ outlookClientId: OUTLOOK_ID,
66
+ });
67
+ assert.ok(unit.includes(`Environment=OUTLOOK_CLIENT_ID=${OUTLOOK_ID}`));
68
+ assert.ok(unit.includes("Environment=OUTLOOK_TENANT_ID=common"));
69
+ assert.ok(unit.includes(`Environment=GOOGLE_CLIENT_ID=${GOOGLE_ID}`));
70
+ });
package/dist/index.js CHANGED
@@ -3721,31 +3721,10 @@ function installServiceDarwin() {
3721
3721
  }
3722
3722
  logFile(` [installer] op=outlook-client-id brand=${BRAND.hostname} stamped=${BRAND.outlookClientId ? "true" : "false"}`);
3723
3723
  // Task 1761 — same upsert/strip discipline for the Google vendor app.
3724
- // GOOGLE_PUBLIC_BASE is the https origin the consent redirect returns to;
3725
- // it must match the authorized redirect URI on the vendor app byte-for-byte,
3726
- // so it is derived from the brand's public host, not guessed at call time.
3727
- // GOOGLE_PUBLIC_BASE is the https origin Google redirects back to. It is a
3728
- // property of THIS INSTALL's tunnel, not of the brand, so it is read from
3729
- // the same tunnel.state the uninstaller reads. Absent tunnel → the var is
3730
- // stripped and google-account-register refuses with an actionable message,
3731
- // rather than a guessed host that would fail Google's byte-for-byte
3732
- // redirect_uri comparison at the token exchange.
3733
- let googlePublicBase = "";
3734
- try {
3735
- const tunnelState = join(PERSIST_DIR, "cloudflared/tunnel.state");
3736
- if (existsSync(tunnelState)) {
3737
- const parsed = JSON.parse(readFileSync(tunnelState, "utf-8"));
3738
- if (parsed.domain)
3739
- googlePublicBase = `https://public.${parsed.domain}`;
3740
- }
3741
- }
3742
- catch {
3743
- // Unreadable tunnel state is not fatal to the install; the var stays unset.
3744
- }
3745
- if (BRAND.googleClientId) {
3724
+ if (BRAND.googleClientId && BRAND.googleClientSecret) {
3746
3725
  for (const [key, value] of [
3747
3726
  ["GOOGLE_CLIENT_ID", BRAND.googleClientId],
3748
- ["GOOGLE_PUBLIC_BASE", googlePublicBase],
3727
+ ["GOOGLE_CLIENT_SECRET", BRAND.googleClientSecret],
3749
3728
  ]) {
3750
3729
  const re = new RegExp(`^${key}=.*$`, "m");
3751
3730
  if (re.test(envContent))
@@ -3755,11 +3734,11 @@ function installServiceDarwin() {
3755
3734
  }
3756
3735
  }
3757
3736
  else {
3758
- for (const key of ["GOOGLE_CLIENT_ID", "GOOGLE_PUBLIC_BASE"]) {
3737
+ for (const key of ["GOOGLE_CLIENT_ID", "GOOGLE_CLIENT_SECRET"]) {
3759
3738
  envContent = envContent.replace(new RegExp(`^${key}=.*\\n?`, "m"), "");
3760
3739
  }
3761
3740
  }
3762
- logFile(` [installer] op=google-client-id brand=${BRAND.hostname} stamped=${BRAND.googleClientId ? "true" : "false"} publicBase=${googlePublicBase || "unset"}`);
3741
+ logFile(` [installer] op=google-client-id brand=${BRAND.hostname} stamped=${BRAND.googleClientId && BRAND.googleClientSecret ? "true" : "false"} clientId=${BRAND.googleClientId ? "set" : "unset"} secret=${BRAND.googleClientSecret ? "set" : "unset"}`);
3763
3742
  writeFileSync(envPath, envContent);
3764
3743
  logFile(` .env: DISPLAY_MODE=${DISPLAY_MODE}, EMBED_MODEL=${EMBED_MODEL}, EMBED_DIMENSIONS=${EMBED_DIMS}, NEO4J_URI=bolt://localhost:${NEO4J_PORT}, PORT=${PORT}, MAXY_UI_INTERNAL_PORT=${PORT} (darwin-collapsed), CLAUDE_SESSION_MANAGER_PORT=${BRAND.claudeSessionManagerPort}, HOSTNAME=0.0.0.0, ACCOUNT_ID=${installAccountId.slice(0, 8)}…, CLAUDE_CONFIG_DIR=${persistDir}/.claude, CLAUDE_BIN=${canonicalClaude ?? "unresolved(PATH)"}, NODE_BIN=${nodeBin}, PATH=${darwinServiceEnvPath}`);
3765
3744
  }
@@ -4383,6 +4362,8 @@ function installService() {
4383
4362
  accountId: installAccountId,
4384
4363
  cdpPort: CDP_PORT_BRAND,
4385
4364
  outlookClientId: BRAND.outlookClientId,
4365
+ googleClientId: BRAND.googleClientId,
4366
+ googleClientSecret: BRAND.googleClientSecret,
4386
4367
  });
4387
4368
  writeFileSync(join(serviceDir, claudeSessionManagerUnitName), claudeSessionManagerUnit);
4388
4369
  logFile(` ${claudeSessionManagerUnitName}: CLAUDE_SESSION_MANAGER_PORT=${CLAUDE_SESSION_MANAGER_PORT_BRAND} MAXY_UI_INTERNAL_PORT=${MAXY_UI_INTERNAL_PORT}`);
@@ -4390,6 +4371,10 @@ function installService() {
4390
4371
  // (a non-empty outlookClientId) against what the unit shipped. `stamped=false`
4391
4372
  // while the brand has a value is the mis-stamp signature.
4392
4373
  logFile(` [installer] op=outlook-client-id brand=${BRAND.hostname} stamped=${BRAND.outlookClientId ? "true" : "false"}`);
4374
+ // Task 1761 — the same standing signal for the Google vendor app. Both parts
4375
+ // are reported because a client id without a public base stamps NEITHER: a
4376
+ // consent URL built without the redirect origin is one Google rejects.
4377
+ logFile(` [installer] op=google-client-id brand=${BRAND.hostname} stamped=${BRAND.googleClientId && BRAND.googleClientSecret ? "true" : "false"} clientId=${BRAND.googleClientId ? "set" : "unset"} secret=${BRAND.googleClientSecret ? "set" : "unset"}`);
4393
4378
  // Task 250 — write the brand-agnostic `claude-ptys.slice` unit. Every
4394
4379
  // brand's session manager drops its per-spawn scope units under this
4395
4380
  // slice. Writing it once per install is idempotent (the content is
@@ -135,6 +135,12 @@ export function buildClaudeSessionManagerUnitFile(o) {
135
135
  const outlookEnv = o.outlookClientId
136
136
  ? `\nEnvironment=OUTLOOK_CLIENT_ID=${o.outlookClientId}\nEnvironment=OUTLOOK_TENANT_ID=common`
137
137
  : "";
138
+ // Task 1791 — id and secret land together. A client id without its secret
139
+ // produces a consent URL the user can complete but an exchange that cannot,
140
+ // which fails later and less legibly than not offering the flow at all.
141
+ const googleEnv = o.googleClientId && o.googleClientSecret
142
+ ? `\nEnvironment=GOOGLE_CLIENT_ID=${o.googleClientId}\nEnvironment=GOOGLE_CLIENT_SECRET=${o.googleClientSecret}`
143
+ : "";
138
144
  return `[Unit]
139
145
  Description=${o.productName} Claude Session Manager
140
146
  After=network.target
@@ -156,7 +162,7 @@ Environment=PLATFORM_ROOT=${o.installDir}/platform
156
162
  Environment=MAXY_BRAND_PORT=${o.brandPort}
157
163
  Environment=MAXY_UI_INTERNAL_PORT=${o.maxyUiInternalPort}
158
164
  Environment=PLATFORM_PORT=${o.maxyUiInternalPort}
159
- Environment=CDP_PORT=${o.cdpPort}${outlookEnv}
165
+ Environment=CDP_PORT=${o.cdpPort}${outlookEnv}${googleEnv}
160
166
  EnvironmentFile=-${o.persistDir}/.env
161
167
  StandardOutput=append:${o.persistDir}/logs/server.log
162
168
  StandardError=append:${o.persistDir}/logs/server.log
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-maxy-code",
3
- "version": "0.1.471",
3
+ "version": "0.1.473",
4
4
  "description": "Install Maxy — AI for Productive People",
5
5
  "bin": {
6
6
  "create-maxy-code": "./dist/index.js"
@@ -16,6 +16,7 @@
16
16
  "claudeSessionManagerPort": 19400,
17
17
  "outlookClientId": "",
18
18
  "googleClientId": "",
19
+ "googleClientSecret": "",
19
20
  "commercialMode": false,
20
21
  "vertical": "schema-knowledge-work",
21
22
  "shipsPremiumBundles": ["venture-studio", "writer-craft", "teaching", "management-consulting"],
@@ -0,0 +1,109 @@
1
+ # Ledger write path fix — implementation plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax.
4
+
5
+ **Goal:** Every ledger write tool successfully creates its node, because relationships carry the element id `writeNodeWithEdges` actually requires.
6
+
7
+ **Architecture:** Delete the ledger's invented `GraphRelationship` and the cast that forced it past the type checker; import the real type so the shape becomes a compile-time constraint. Resolve each relationship target to an element id before writing. Add a census counter so a dead write path can no longer read as a healthy empty ledger.
8
+
9
+ **Spec:** [`.tasks/1788-...md`](../../../../.tasks/1788-ledger-write-tools-pass-an-invented-relationship-shape-so-every-write-is-rejected.md). No separate design doc; the task carries the decisions.
10
+
11
+ ## Global constraints
12
+
13
+ - Node 22. Never `npm install` in this worktree; deps are symlinked from the parent.
14
+ - Build and test from `maxy-code/platform`.
15
+ - The four write tools must not be tested with a stubbed `write`. That stub is why the bug shipped.
16
+ - Do not touch `lib/ledger-core`'s arithmetic (money, balance, ageing) — only `reconcile.ts` gains a counter.
17
+ - Baseline before changes: 56 ledger tests, 508 memory-plugin tests.
18
+
19
+ ---
20
+
21
+ ### Task 1: Make the wrong shape a compile error
22
+
23
+ **Files:** `plugins/ledger/mcp/src/lib/types.ts`, `plugins/ledger/mcp/src/index.ts`
24
+
25
+ **Produces:** the real `GraphRelationship { type, direction, targetNodeId }` in scope for every tool.
26
+
27
+ - [ ] **Step 1: Prove the current code is wrong at the type level.** In `types.ts`, delete the local `GraphRelationship` and re-export the real one:
28
+
29
+ ```ts
30
+ export type { GraphRelationship } from "../../../../../lib/graph-write/dist/index.js";
31
+ ```
32
+
33
+ - [ ] **Step 2: Delete the cast in `index.ts`.** Replace the `writeNodeWithEdges as unknown as (...)` wrapper with a direct reference. Run `tsc -p plugins/ledger/mcp/tsconfig.json` and record every error. Expect failures in `invoice-record.ts`, `payment-record.ts`, `cash-record.ts` on `targetLabel`/`targetProps`. **Those errors are the bug** — they are what the cast was hiding.
34
+
35
+ - [ ] **Step 3: Fix the attribution while here.** `createdBy` becomes `{ agent: agentSlug, session: sessionId, tool, source: "ledger" }` with no `?? ""`, so `graph-write:254`'s `agent ?? source ?? "unknown"` chain can work.
36
+
37
+ - [ ] **Step 4: Commit** once the remaining errors are only the three tool files.
38
+
39
+ ---
40
+
41
+ ### Task 2: Resolve targets to element ids
42
+
43
+ **Files:** `plugins/ledger/mcp/src/lib/read.ts`, `tools/{invoice-record,payment-record,cash-record}.ts`, `index.ts`, `PLUGIN.md`
44
+
45
+ **Consumes:** the real type from Task 1.
46
+
47
+ - [ ] **Step 1: `readInvoice` returns the node's id.** Add `nodeId: string` to `InvoiceRead`; add `elementId(i) AS nodeId` to the Cypher and map it.
48
+
49
+ - [ ] **Step 2: Write the failing tests first.** For each of the three tools, assert the relationship carries a `targetNodeId` equal to the id supplied or resolved, and that no relationship carries `targetLabel`:
50
+
51
+ ```ts
52
+ it('targets the counterparty by element id', async () => {
53
+ const write = okWrite()
54
+ await invoiceRecord({ ...base, counterpartyNodeId: '4:abc:12', session: {} as never, write })
55
+ const rel = write.mock.calls[0]![0].relationships[0]
56
+ expect(rel.targetNodeId).toBe('4:abc:12')
57
+ expect(rel).not.toHaveProperty('targetLabel')
58
+ })
59
+ ```
60
+
61
+ - [ ] **Step 3: Implement.** `invoice-record` and `cash-record` take a required `counterpartyNodeId`. `payment-record` and `credit-record` use `invoice.nodeId` from the read. Each `zod` schema gains the field with a description telling the agent to look the organisation up first.
62
+
63
+ - [ ] **Step 4: Reject a blank id** before writing, with `op=reject reason=missing-counterparty`, so the failure names the cause rather than surfacing as `unresolved-target`.
64
+
65
+ - [ ] **Step 5: `tsc` clean, ledger tests green, commit.**
66
+
67
+ ---
68
+
69
+ ### Task 3: The test that would have caught it
70
+
71
+ **Files:** `plugins/ledger/mcp/src/tools/__tests__/write-contract.test.ts` (new)
72
+
73
+ One integration test per write tool that calls the **real** `writeNodeWithEdges` against a fake `Session` (`run` + `close`), asserting the issued Cypher carries the resolved target id and that no `unresolved-target` rejection occurs.
74
+
75
+ - [ ] **Step 1: Write it against the current code and watch it fail** if Tasks 1 and 2 were somehow incomplete.
76
+ - [ ] **Step 2: Confirm it passes.**
77
+ - [ ] **Step 3: Prove it is load-bearing.** Temporarily reintroduce `targetLabel` in one tool; the build must fail. Revert.
78
+ - [ ] **Step 4: Commit.**
79
+
80
+ ---
81
+
82
+ ### Task 4: Make a dead write path visible
83
+
84
+ **Files:** `lib/ledger-core/src/reconcile.ts`, `plugins/ledger/mcp/src/lib/read.ts`, `tools/reconcile.ts`, `ui/app/lib/ledger-census.ts`, tests
85
+
86
+ `[ledger-census]` currently prints `invoices=0 payments=0` identically whether the ledger is healthy-and-empty or wholly broken. That is why this bug ran all day unseen.
87
+
88
+ - [ ] **Step 1: Failing test.** `reconcileLedger` alarms when `writeRejects24h > 0`.
89
+ - [ ] **Step 2: Implement.** Add `writeRejects24h` to `LedgerCensusRows`, `LedgerCensusFinding`, the alarm condition, and `formatCensusLine`.
90
+ - [ ] **Step 3: Source the count.** Both the census and the reconcile tool count `[graph-write] reject` lines in `<LOG_DIR>/mcp-ledger-*.log` with a timestamp inside the trailing 24 hours.
91
+ - [ ] **Step 4: Tests green, commit.**
92
+
93
+ ---
94
+
95
+ ### Task 5: Docs
96
+
97
+ **Files:** `plugins/ledger/PLUGIN.md`, `plugins/docs/references/ledger.md`
98
+
99
+ State that the counterparty must be looked up first and passed by id, that the ledger never creates organisations, and what `writeRejects24h` means. Keep the shipped reference free of em-dashes.
100
+
101
+ - [ ] Commit.
102
+
103
+ ---
104
+
105
+ ## Self-review
106
+
107
+ **Spec coverage.** Type import and cast removal: Task 1. Attribution: Task 1 step 3. Element-id resolution for all four write tools: Task 2. The banned-stub integration test: Task 3. Census counter: Task 4. Docs: Task 5. Out-of-scope items (duplicate-org matching, `:InvoiceLine`, read tools, ledger-core arithmetic) are untouched.
108
+
109
+ **Risk.** Task 2 changes the tool signatures, so any agent prompt or skill that calls `ledger-invoice-record` with a bare counterparty name breaks. Nothing calls these tools yet except live agent turns, and they have never succeeded, so there is no working caller to regress.
@@ -31,6 +31,12 @@ export interface LedgerCensusRows {
31
31
  storedBalanceProps: number;
32
32
  paymentKeys: readonly PaymentKey[];
33
33
  declaredNotLive: readonly string[];
34
+ /** Count of `[graph-write] reject` lines from the ledger's MCP logs in the
35
+ * trailing 24 hours. Without it the census cannot tell a healthy empty
36
+ * ledger from one whose write path is wholly dead: both print
37
+ * `invoices=0 payments=0`. Task 1788, after exactly that went unseen for a
38
+ * day. */
39
+ writeRejects24h: number;
34
40
  }
35
41
  export interface LedgerCensusFinding {
36
42
  invoices: number;
@@ -39,6 +45,7 @@ export interface LedgerCensusFinding {
39
45
  storedBalanceProps: number;
40
46
  suspectDuplicates: number;
41
47
  declaredNotLive: readonly string[];
48
+ writeRejects24h: number;
42
49
  alarm: boolean;
43
50
  }
44
51
  export declare function reconcileLedger(rows: LedgerCensusRows, _now: number): LedgerCensusFinding;
@@ -1 +1 @@
1
- {"version":3,"file":"reconcile.d.ts","sourceRoot":"","sources":["../src/reconcile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH;;;;mEAImE;AACnE,eAAO,MAAM,kBAAkB,EAAE,SAAS,MAAM,EAO/C,CAAA;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,cAAc,EAAE,MAAM,CAAA;IACtB,kBAAkB,EAAE,MAAM,CAAA;IAC1B,WAAW,EAAE,SAAS,UAAU,EAAE,CAAA;IAClC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAA;CACnC;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,cAAc,EAAE,MAAM,CAAA;IACtB,kBAAkB,EAAE,MAAM,CAAA;IAC1B,iBAAiB,EAAE,MAAM,CAAA;IACzB,eAAe,EAAE,SAAS,MAAM,EAAE,CAAA;IAClC,KAAK,EAAE,OAAO,CAAA;CACf;AAwBD,wBAAgB,eAAe,CAAC,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,GAAG,mBAAmB,CAiBzF;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,mBAAmB,GAAG,MAAM,CAO/D"}
1
+ {"version":3,"file":"reconcile.d.ts","sourceRoot":"","sources":["../src/reconcile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH;;;;mEAImE;AACnE,eAAO,MAAM,kBAAkB,EAAE,SAAS,MAAM,EAO/C,CAAA;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,cAAc,EAAE,MAAM,CAAA;IACtB,kBAAkB,EAAE,MAAM,CAAA;IAC1B,WAAW,EAAE,SAAS,UAAU,EAAE,CAAA;IAClC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAA;IAClC;;;;eAIW;IACX,eAAe,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,cAAc,EAAE,MAAM,CAAA;IACtB,kBAAkB,EAAE,MAAM,CAAA;IAC1B,iBAAiB,EAAE,MAAM,CAAA;IACzB,eAAe,EAAE,SAAS,MAAM,EAAE,CAAA;IAClC,eAAe,EAAE,MAAM,CAAA;IACvB,KAAK,EAAE,OAAO,CAAA;CACf;AAwBD,wBAAgB,eAAe,CAAC,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,GAAG,mBAAmB,CAmBzF;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,mBAAmB,GAAG,MAAM,CAQ/D"}
@@ -52,7 +52,8 @@ export function reconcileLedger(rows, _now) {
52
52
  const alarm = rows.orphanPayments > 0 ||
53
53
  rows.storedBalanceProps > 0 ||
54
54
  suspectDuplicates > 0 ||
55
- rows.declaredNotLive.length > 0;
55
+ rows.declaredNotLive.length > 0 ||
56
+ rows.writeRejects24h > 0;
56
57
  return {
57
58
  invoices: rows.invoices,
58
59
  payments: rows.payments,
@@ -60,6 +61,7 @@ export function reconcileLedger(rows, _now) {
60
61
  storedBalanceProps: rows.storedBalanceProps,
61
62
  suspectDuplicates,
62
63
  declaredNotLive: rows.declaredNotLive,
64
+ writeRejects24h: rows.writeRejects24h,
63
65
  alarm,
64
66
  };
65
67
  }
@@ -72,6 +74,7 @@ export function formatCensusLine(f) {
72
74
  return (`[ledger-census] invoices=${f.invoices} payments=${f.payments} ` +
73
75
  `orphanPayments=${f.orphanPayments} storedBalanceProps=${f.storedBalanceProps} ` +
74
76
  `suspectDuplicates=${f.suspectDuplicates} ` +
75
- `declaredNotLive=${f.declaredNotLive.length === 0 ? "none" : f.declaredNotLive.join(",")}`);
77
+ `declaredNotLive=${f.declaredNotLive.length === 0 ? "none" : f.declaredNotLive.join(",")} ` +
78
+ `writeRejects24h=${f.writeRejects24h}`);
76
79
  }
77
80
  //# sourceMappingURL=reconcile.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"reconcile.js","sourceRoot":"","sources":["../src/reconcile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH;;;;mEAImE;AACnE,MAAM,CAAC,MAAM,kBAAkB,GAAsB;IACnD,qCAAqC;IACrC,mCAAmC;IACnC,oBAAoB;IACpB,sBAAsB;IACtB,yBAAyB;IACzB,gBAAgB;CACjB,CAAA;AA4BD;;;;;;;;GAQG;AACH,SAAS,sBAAsB,CAAC,IAA2B;IACzD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAuB,CAAA;IAC7C,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,MAAM,SAAS,GAAG,GAAG,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,IAAI,EAAE,CAAA;QACxD,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,IAAI,GAAG,EAAU,CAAA;QACtD,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;QACpB,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,CAAA;IAC5B,CAAC;IACD,IAAI,KAAK,GAAG,CAAC,CAAA;IACb,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,MAAM,EAAE;QAAE,KAAK,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,CAAA;IACrE,OAAO,KAAK,CAAA;AACd,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,IAAsB,EAAE,IAAY;IAClE,MAAM,iBAAiB,GAAG,sBAAsB,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;IAClE,MAAM,KAAK,GACT,IAAI,CAAC,cAAc,GAAG,CAAC;QACvB,IAAI,CAAC,kBAAkB,GAAG,CAAC;QAC3B,iBAAiB,GAAG,CAAC;QACrB,IAAI,CAAC,eAAe,CAAC,MAAM,GAAG,CAAC,CAAA;IAEjC,OAAO;QACL,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,cAAc,EAAE,IAAI,CAAC,cAAc;QACnC,kBAAkB,EAAE,IAAI,CAAC,kBAAkB;QAC3C,iBAAiB;QACjB,eAAe,EAAE,IAAI,CAAC,eAAe;QACrC,KAAK;KACN,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,CAAsB;IACrD,OAAO,CACL,4BAA4B,CAAC,CAAC,QAAQ,aAAa,CAAC,CAAC,QAAQ,GAAG;QAChE,kBAAkB,CAAC,CAAC,cAAc,uBAAuB,CAAC,CAAC,kBAAkB,GAAG;QAChF,qBAAqB,CAAC,CAAC,iBAAiB,GAAG;QAC3C,mBAAmB,CAAC,CAAC,eAAe,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAC3F,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"reconcile.js","sourceRoot":"","sources":["../src/reconcile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH;;;;mEAImE;AACnE,MAAM,CAAC,MAAM,kBAAkB,GAAsB;IACnD,qCAAqC;IACrC,mCAAmC;IACnC,oBAAoB;IACpB,sBAAsB;IACtB,yBAAyB;IACzB,gBAAgB;CACjB,CAAA;AAmCD;;;;;;;;GAQG;AACH,SAAS,sBAAsB,CAAC,IAA2B;IACzD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAuB,CAAA;IAC7C,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,MAAM,SAAS,GAAG,GAAG,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,IAAI,EAAE,CAAA;QACxD,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,IAAI,GAAG,EAAU,CAAA;QACtD,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;QACpB,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,CAAA;IAC5B,CAAC;IACD,IAAI,KAAK,GAAG,CAAC,CAAA;IACb,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,MAAM,EAAE;QAAE,KAAK,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,CAAA;IACrE,OAAO,KAAK,CAAA;AACd,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,IAAsB,EAAE,IAAY;IAClE,MAAM,iBAAiB,GAAG,sBAAsB,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;IAClE,MAAM,KAAK,GACT,IAAI,CAAC,cAAc,GAAG,CAAC;QACvB,IAAI,CAAC,kBAAkB,GAAG,CAAC;QAC3B,iBAAiB,GAAG,CAAC;QACrB,IAAI,CAAC,eAAe,CAAC,MAAM,GAAG,CAAC;QAC/B,IAAI,CAAC,eAAe,GAAG,CAAC,CAAA;IAE1B,OAAO;QACL,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,cAAc,EAAE,IAAI,CAAC,cAAc;QACnC,kBAAkB,EAAE,IAAI,CAAC,kBAAkB;QAC3C,iBAAiB;QACjB,eAAe,EAAE,IAAI,CAAC,eAAe;QACrC,eAAe,EAAE,IAAI,CAAC,eAAe;QACrC,KAAK;KACN,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,CAAsB;IACrD,OAAO,CACL,4BAA4B,CAAC,CAAC,QAAQ,aAAa,CAAC,CAAC,QAAQ,GAAG;QAChE,kBAAkB,CAAC,CAAC,cAAc,uBAAuB,CAAC,CAAC,kBAAkB,GAAG;QAChF,qBAAqB,CAAC,CAAC,iBAAiB,GAAG;QAC3C,mBAAmB,CAAC,CAAC,eAAe,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG;QAC3F,mBAAmB,CAAC,CAAC,eAAe,EAAE,CACvC,CAAA;AACH,CAAC"}
@@ -8,6 +8,7 @@ const clean = {
8
8
  storedBalanceProps: 0,
9
9
  paymentKeys: [],
10
10
  declaredNotLive: [],
11
+ writeRejects24h: 0,
11
12
  }
12
13
  const NOW = Date.parse('2026-07-18T00:00:00Z')
13
14
 
@@ -84,4 +85,23 @@ describe('reconcileLedger', () => {
84
85
  expect(LEDGER_INDEX_NAMES).toHaveLength(6)
85
86
  expect(LEDGER_INDEX_NAMES).toContain('cash_entry_account_entryid_unique')
86
87
  })
88
+
89
+ it('alarms when the write path is rejecting', () => {
90
+ // The case this counter exists for: on 2026-07-18 every ledger write was
91
+ // rejected all day while the census printed invoices=0 payments=0, which
92
+ // is byte-identical to a healthy ledger nobody has used yet.
93
+ const f = reconcileLedger({ ...clean, writeRejects24h: 4 }, NOW)
94
+ expect(f.alarm).toBe(true)
95
+ expect(formatCensusLine(f)).toContain('writeRejects24h=4')
96
+ })
97
+
98
+ it('a healthy empty ledger is distinguishable from a broken one', () => {
99
+ const empty = { ...clean, invoices: 0, payments: 0 }
100
+ const broken = { ...empty, writeRejects24h: 4 }
101
+ expect(formatCensusLine(reconcileLedger(empty, NOW))).not.toEqual(
102
+ formatCensusLine(reconcileLedger(broken, NOW)),
103
+ )
104
+ expect(reconcileLedger(empty, NOW).alarm).toBe(false)
105
+ expect(reconcileLedger(broken, NOW).alarm).toBe(true)
106
+ })
87
107
  })
@@ -41,6 +41,12 @@ export interface LedgerCensusRows {
41
41
  storedBalanceProps: number
42
42
  paymentKeys: readonly PaymentKey[]
43
43
  declaredNotLive: readonly string[]
44
+ /** Count of `[graph-write] reject` lines from the ledger's MCP logs in the
45
+ * trailing 24 hours. Without it the census cannot tell a healthy empty
46
+ * ledger from one whose write path is wholly dead: both print
47
+ * `invoices=0 payments=0`. Task 1788, after exactly that went unseen for a
48
+ * day. */
49
+ writeRejects24h: number
44
50
  }
45
51
 
46
52
  export interface LedgerCensusFinding {
@@ -50,6 +56,7 @@ export interface LedgerCensusFinding {
50
56
  storedBalanceProps: number
51
57
  suspectDuplicates: number
52
58
  declaredNotLive: readonly string[]
59
+ writeRejects24h: number
53
60
  alarm: boolean
54
61
  }
55
62
 
@@ -81,7 +88,8 @@ export function reconcileLedger(rows: LedgerCensusRows, _now: number): LedgerCen
81
88
  rows.orphanPayments > 0 ||
82
89
  rows.storedBalanceProps > 0 ||
83
90
  suspectDuplicates > 0 ||
84
- rows.declaredNotLive.length > 0
91
+ rows.declaredNotLive.length > 0 ||
92
+ rows.writeRejects24h > 0
85
93
 
86
94
  return {
87
95
  invoices: rows.invoices,
@@ -90,6 +98,7 @@ export function reconcileLedger(rows: LedgerCensusRows, _now: number): LedgerCen
90
98
  storedBalanceProps: rows.storedBalanceProps,
91
99
  suspectDuplicates,
92
100
  declaredNotLive: rows.declaredNotLive,
101
+ writeRejects24h: rows.writeRejects24h,
93
102
  alarm,
94
103
  }
95
104
  }
@@ -104,6 +113,7 @@ export function formatCensusLine(f: LedgerCensusFinding): string {
104
113
  `[ledger-census] invoices=${f.invoices} payments=${f.payments} ` +
105
114
  `orphanPayments=${f.orphanPayments} storedBalanceProps=${f.storedBalanceProps} ` +
106
115
  `suspectDuplicates=${f.suspectDuplicates} ` +
107
- `declaredNotLive=${f.declaredNotLive.length === 0 ? "none" : f.declaredNotLive.join(",")}`
116
+ `declaredNotLive=${f.declaredNotLive.length === 0 ? "none" : f.declaredNotLive.join(",")} ` +
117
+ `writeRejects24h=${f.writeRejects24h}`
108
118
  )
109
119
  }
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: platform-architecture
3
3
  description: Use when grounding any documented-surface claim about what Maxy ships — plugins, skills, specialists, install/deploy flows, internals. This is the install catalogue, not evidence of what is enabled on the current account. For install state on this account, call `capabilities-here`; for documented surface, cite the `Source:` URL inline.
4
- content-hash: sha256:46e07a18921b711849e53f63b8c29b72f3324f91bc682eb5be585ccd64c98971
4
+ content-hash: sha256:c15214d349505b6337668b0362b849c9c5301fd25304800cf7384888a6a21435
5
5
  brand: maxy-code
6
6
  product-name: Maxy
7
7
  ---
@@ -636,6 +636,12 @@ If you need statutory accounts, this is not that. It is the answer to "who owes
636
636
 
637
637
  **Ask for a statement.** Everything owed to you and everything you owe, sorted into 0-30, 31-60, 61-90 and 90+ days overdue, plus net cash.
638
638
 
639
+ ## You look the customer up first
640
+
641
+ When you record an invoice or a cash movement, the agent finds the company or person in your records first, then attaches the money to that exact record. It will not create a new company for you, and the name that appears on the invoice is taken from the record it attached to, so the two can never disagree.
642
+
643
+ That is on purpose. Deciding whether "Beagle Direct Ltd" is the same firm as an existing "Beagle Direct" is a judgement about your records, not a bookkeeping step, and getting it wrong quietly splits one customer's payment history across two entries.
644
+
639
645
  ## Three behaviours worth knowing about
640
646
 
641
647
  **What is outstanding is never stored.** It is worked out from the payments each time you ask. This means the figure cannot fall out of step with the payments behind it. If it were stored, a payment recorded by any route that forgot to update it would leave the invoice quietly reporting a wrong number, and nothing would fail.
@@ -648,7 +654,9 @@ If you need statutory accounts, this is not that. It is the answer to "who owes
648
654
 
649
655
  Some ways this can go wrong announce themselves. Others do not: a payment that ends up attached to no invoice, a stale balance field reappearing, or the same payment recorded twice under two different ids. None of those produce an error at the time, because nothing goes wrong at the moment they happen.
650
656
 
651
- So there is a check that runs by itself every fifteen minutes and writes one line to the log beginning `[ledger-census]`. When it is healthy the counters are all zero. When something is wrong the line ends with `alarm=true` and says which of the four it found.
657
+ So there is a check that runs by itself every fifteen minutes and writes one line to the log beginning `[ledger-census]`. When it is healthy the counters are all zero. When something is wrong the line ends with `alarm=true` and says which of them it found.
658
+
659
+ One of those counters is the number of writes that were refused in the last day. It is there because of a real failure: for a whole day every attempt to record money was refused, and the check still read as healthy, because a ledger nobody has used and a ledger that cannot be written to both show nothing.
652
660
 
653
661
  You can also ask for that check on demand at any time, and it reports the same thing in plain words.
654
662
 
@@ -2218,9 +2226,16 @@ force Google's annual third-party security assessment. Calendar is only a
2218
2226
  ## Connecting an account
2219
2227
 
2220
2228
  `google-account-register` returns a consent URL. Relay it to the user: they open
2221
- it on any device, sign in, and approve. Google redirects to this install's
2222
- public host at `/api/google/oauth/callback`, which exchanges the code and stores
2223
- encrypted tokens. `google-account-register-poll` then reports the outcome.
2229
+ it on any device, sign in, and approve.
2230
+
2231
+ Their browser then lands on a page that **fails to load**. That is expected, not
2232
+ a fault — the vendor app is a Desktop client, so Google redirects to a loopback
2233
+ address on the user's own device where nothing is listening. The address bar
2234
+ carries the code. Ask them to copy it and pass it to
2235
+ `google-account-register-submit`, which completes the connection.
2236
+
2237
+ If they paste the whole `code=...` fragment, or a value containing `%2F`, the
2238
+ tool handles it. `google-account-register-poll` reports the outcome at any point.
2224
2239
 
2225
2240
  The register tool does not block and does not wait. A second register while one
2226
2241
  is already live is refused rather than overwriting it; that refusal names how
@@ -4891,6 +4906,10 @@ To deliberately disconnect the wrong account, use the header menu's **Disconnect
4891
4906
  3. `grep '\[auth-health\]' ~/.${brand}/logs/server.log | tail -n 5` — the heartbeat fires every five minutes. `status=dead expiresIn=...` means the refresh token is gone; only a re-login fixes it. `status=ok` heartbeats with no spawns in between mean the credentials file is healthy and the failure lives elsewhere.
4892
4907
  4. The spawn-failure surface now carries `reason=auth-refresh-failed` (with `authStatus` in the JSON body) instead of generic `pid-file-timeout` whenever the credentials file is in `dead` or `expired` state at the moment of failure — visible in `grep '\[spawn-failed\]'` on server.log.
4893
4908
 
4909
+ **A WhatsApp message you can see on your phone never appears in the app.** First check whether it was ever stored, rather than assuming it routed elsewhere: ask Maxy for the most recent ingest reconcile, or `grep '\[wa-ingest-audit\]' ~/.${brand}/logs/server.log | tail -1`. A healthy line reads `op=reconcile … unpersisted=0 inFlight=0 guard=installed`. If `unpersisted` is above zero, the `ids=` field names the exact message ids that arrived at the wire and were never written to the store — those messages are genuinely lost to the platform, not hidden in another workspace or sub-account, and they cannot be re-fetched because WhatsApp only delivers an acked message once. Forward the message from your phone to get it back in; it re-enters as a new message through the normal path.
4910
+
4911
+ The audit runs every 15 minutes and holds a stanza for 10 minutes before judging it, so a message can take up to 25 minutes to be reported. The wait is deliberate: a large batch of photos arriving at once can take several minutes to finish storing, and a false "a message was lost" alarm would make the real one easy to ignore. Three classes are resolved rather than reported, each counted separately: `skippedStatus` (status broadcasts, never stored), `skippedReaction` (reactions, stored under the message they react to rather than under their own id — a consequence being that a lost *reaction* is not detectable), and `declined` (messages carrying neither text nor a file, such as a shared contact card, a poll, or a delete-for-everyone, which are dropped by design). `oldestUnpersistedMin` tells you whether a reported loss is new or one you have already seen, since the same id is re-reported every 15 minutes for a day. `guard=absent` or `inFlight` stuck above zero on an idle install are both faults worth surfacing to support: the first means the media-download crash guard failed to install at boot, the second means its counter leaked. `contained` counts crashes the guard prevented — a number that climbs while media traffic stays flat is also worth reporting.
4912
+
4894
4913
  ---
4895
4914
 
4896
4915
  ## Memory Not Working
@@ -12,9 +12,16 @@ force Google's annual third-party security assessment. Calendar is only a
12
12
  ## Connecting an account
13
13
 
14
14
  `google-account-register` returns a consent URL. Relay it to the user: they open
15
- it on any device, sign in, and approve. Google redirects to this install's
16
- public host at `/api/google/oauth/callback`, which exchanges the code and stores
17
- encrypted tokens. `google-account-register-poll` then reports the outcome.
15
+ it on any device, sign in, and approve.
16
+
17
+ Their browser then lands on a page that **fails to load**. That is expected, not
18
+ a fault — the vendor app is a Desktop client, so Google redirects to a loopback
19
+ address on the user's own device where nothing is listening. The address bar
20
+ carries the code. Ask them to copy it and pass it to
21
+ `google-account-register-submit`, which completes the connection.
22
+
23
+ If they paste the whole `code=...` fragment, or a value containing `%2F`, the
24
+ tool handles it. `google-account-register-poll` reports the outcome at any point.
18
25
 
19
26
  The register tool does not block and does not wait. A second register while one
20
27
  is already live is refused rather than overwriting it; that refusal names how
@@ -24,6 +24,12 @@ If you need statutory accounts, this is not that. It is the answer to "who owes
24
24
 
25
25
  **Ask for a statement.** Everything owed to you and everything you owe, sorted into 0-30, 31-60, 61-90 and 90+ days overdue, plus net cash.
26
26
 
27
+ ## You look the customer up first
28
+
29
+ When you record an invoice or a cash movement, the agent finds the company or person in your records first, then attaches the money to that exact record. It will not create a new company for you, and the name that appears on the invoice is taken from the record it attached to, so the two can never disagree.
30
+
31
+ That is on purpose. Deciding whether "Beagle Direct Ltd" is the same firm as an existing "Beagle Direct" is a judgement about your records, not a bookkeeping step, and getting it wrong quietly splits one customer's payment history across two entries.
32
+
27
33
  ## Three behaviours worth knowing about
28
34
 
29
35
  **What is outstanding is never stored.** It is worked out from the payments each time you ask. This means the figure cannot fall out of step with the payments behind it. If it were stored, a payment recorded by any route that forgot to update it would leave the invoice quietly reporting a wrong number, and nothing would fail.
@@ -36,7 +42,9 @@ If you need statutory accounts, this is not that. It is the answer to "who owes
36
42
 
37
43
  Some ways this can go wrong announce themselves. Others do not: a payment that ends up attached to no invoice, a stale balance field reappearing, or the same payment recorded twice under two different ids. None of those produce an error at the time, because nothing goes wrong at the moment they happen.
38
44
 
39
- So there is a check that runs by itself every fifteen minutes and writes one line to the log beginning `[ledger-census]`. When it is healthy the counters are all zero. When something is wrong the line ends with `alarm=true` and says which of the four it found.
45
+ So there is a check that runs by itself every fifteen minutes and writes one line to the log beginning `[ledger-census]`. When it is healthy the counters are all zero. When something is wrong the line ends with `alarm=true` and says which of them it found.
46
+
47
+ One of those counters is the number of writes that were refused in the last day. It is there because of a real failure: for a whole day every attempt to record money was refused, and the check still read as healthy, because a ledger nobody has used and a ledger that cannot be written to both show nothing.
40
48
 
41
49
  You can also ask for that check on demand at any time, and it reports the same thing in plain words.
42
50
 
@@ -147,6 +147,10 @@ To deliberately disconnect the wrong account, use the header menu's **Disconnect
147
147
  3. `grep '\[auth-health\]' ~/.${brand}/logs/server.log | tail -n 5` — the heartbeat fires every five minutes. `status=dead expiresIn=...` means the refresh token is gone; only a re-login fixes it. `status=ok` heartbeats with no spawns in between mean the credentials file is healthy and the failure lives elsewhere.
148
148
  4. The spawn-failure surface now carries `reason=auth-refresh-failed` (with `authStatus` in the JSON body) instead of generic `pid-file-timeout` whenever the credentials file is in `dead` or `expired` state at the moment of failure — visible in `grep '\[spawn-failed\]'` on server.log.
149
149
 
150
+ **A WhatsApp message you can see on your phone never appears in the app.** First check whether it was ever stored, rather than assuming it routed elsewhere: ask {{productName}} for the most recent ingest reconcile, or `grep '\[wa-ingest-audit\]' ~/.${brand}/logs/server.log | tail -1`. A healthy line reads `op=reconcile … unpersisted=0 inFlight=0 guard=installed`. If `unpersisted` is above zero, the `ids=` field names the exact message ids that arrived at the wire and were never written to the store — those messages are genuinely lost to the platform, not hidden in another workspace or sub-account, and they cannot be re-fetched because WhatsApp only delivers an acked message once. Forward the message from your phone to get it back in; it re-enters as a new message through the normal path.
151
+
152
+ The audit runs every 15 minutes and holds a stanza for 10 minutes before judging it, so a message can take up to 25 minutes to be reported. The wait is deliberate: a large batch of photos arriving at once can take several minutes to finish storing, and a false "a message was lost" alarm would make the real one easy to ignore. Three classes are resolved rather than reported, each counted separately: `skippedStatus` (status broadcasts, never stored), `skippedReaction` (reactions, stored under the message they react to rather than under their own id — a consequence being that a lost *reaction* is not detectable), and `declined` (messages carrying neither text nor a file, such as a shared contact card, a poll, or a delete-for-everyone, which are dropped by design). `oldestUnpersistedMin` tells you whether a reported loss is new or one you have already seen, since the same id is re-reported every 15 minutes for a day. `guard=absent` or `inFlight` stuck above zero on an idle install are both faults worth surfacing to support: the first means the media-download crash guard failed to install at boot, the second means its counter leaked. `contained` counts crashes the guard prevented — a number that climbs while media traffic stays flat is also worth reporting.
153
+
150
154
  ---
151
155
 
152
156
  ## Memory Not Working
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "google",
3
- "description": "Live Google Calendar for one account via the Google Calendar API v3 — read plus control. Per-account OAuth authorization-code + PKCE; the consent URL is minted by the plugin and the code is exchanged by platform/ui, which holds the house-only client secret. One account can connect several Google accounts; every operational tool takes an optional account selector (the email address) and refuses ambiguously when two or more are connected and none is named. There is deliberately no Gmail surface — mail runs on the `email` plugin's IMAP/SMTP path, and Gmail's restricted scopes would force Google's annual third-party security assessment, whereas Calendar is only a sensitive scope. Tools — google-account-register / google-account-register-poll: connect a Google Calendar (returns a consent URL, then reports the outcome from local state); google-calendar-list: events in the next rangeDays days, recurring series expanded; google-calendar-event: one event by id; google-calendar-create / google-calendar-update / google-calendar-cancel: control events, attendees notified, update changes only the fields named; google-calendar-respond: accept, decline or tentatively accept an invitation, leaving other attendees' responses untouched; google-calendar-freebusy: busy blocks for this or named calendars; google-account-info: auth state for one connected Google account; google-account-list: every connected Google account with its own status.",
3
+ "description": "Live Google Calendar for one account via the Google Calendar API v3 — read plus control. Per-account OAuth authorization-code + PKCE; the consent URL is minted by the plugin and the code is exchanged by platform/ui, which holds the house-only client secret. One account can connect several Google accounts; every operational tool takes an optional account selector (the email address) and refuses ambiguously when two or more are connected and none is named. There is deliberately no Gmail surface — mail runs on the `email` plugin's IMAP/SMTP path, and Gmail's restricted scopes would force Google's annual third-party security assessment, whereas Calendar is only a sensitive scope. Tools — google-account-register / google-account-register-submit / google-account-register-poll: connect a Google Calendar (returns a consent URL; the user approves, lands on a page that fails to load by design, and pastes the code back to submit); google-calendar-list: events in the next rangeDays days, recurring series expanded; google-calendar-event: one event by id; google-calendar-create / google-calendar-update / google-calendar-cancel: control events, attendees notified, update changes only the fields named; google-calendar-respond: accept, decline or tentatively accept an invitation, leaving other attendees' responses untouched; google-calendar-freebusy: busy blocks for this or named calendars; google-account-info: auth state for one connected Google account; google-account-list: every connected Google account with its own status.",
4
4
  "version": "0.1.0",
5
5
  "author": {
6
6
  "name": "Rubytech LLC"
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: google
3
- description: Live Google Calendar for one account via the Google Calendar API v3 — read plus control. Per-account OAuth authorization-code + PKCE; the consent URL is minted by the plugin and the code is exchanged by platform/ui, which holds the house-only client secret. One account can connect several Google accounts; every operational tool takes an optional account selector (the email address) and refuses ambiguously when two or more are connected and none is named. There is deliberately no Gmail surface — mail runs on the `email` plugin's IMAP/SMTP path, and Gmail's restricted scopes would force Google's annual third-party security assessment, whereas Calendar is only a sensitive scope. Tools — google-account-register / google-account-register-poll: connect a Google Calendar (returns a consent URL, then reports the outcome from local state); google-calendar-list: events in the next rangeDays days, recurring series expanded; google-calendar-event: one event by id; google-calendar-create / google-calendar-update / google-calendar-cancel: control events, attendees notified, update changes only the fields named; google-calendar-respond: accept, decline or tentatively accept an invitation, leaving other attendees' responses untouched; google-calendar-freebusy: busy blocks for this or named calendars; google-account-info: auth state for one connected Google account; google-account-list: every connected Google account with its own status.
3
+ description: Live Google Calendar for one account via the Google Calendar API v3 — read plus control. Per-account OAuth authorization-code + PKCE; the consent URL is minted by the plugin and the code is exchanged by platform/ui, which holds the house-only client secret. One account can connect several Google accounts; every operational tool takes an optional account selector (the email address) and refuses ambiguously when two or more are connected and none is named. There is deliberately no Gmail surface — mail runs on the `email` plugin's IMAP/SMTP path, and Gmail's restricted scopes would force Google's annual third-party security assessment, whereas Calendar is only a sensitive scope. Tools — google-account-register / google-account-register-submit / google-account-register-poll: connect a Google Calendar (returns a consent URL; the user approves, lands on a page that fails to load by design, and pastes the code back to submit); google-calendar-list: events in the next rangeDays days, recurring series expanded; google-calendar-event: one event by id; google-calendar-create / google-calendar-update / google-calendar-cancel: control events, attendees notified, update changes only the fields named; google-calendar-respond: accept, decline or tentatively accept an invitation, leaving other attendees' responses untouched; google-calendar-freebusy: busy blocks for this or named calendars; google-account-info: auth state for one connected Google account; google-account-list: every connected Google account with its own status.
4
4
  tools:
5
5
  - name: google-account-register
6
6
  publicAllowlist: false
@@ -8,6 +8,9 @@ tools:
8
8
  - name: google-account-register-poll
9
9
  publicAllowlist: false
10
10
  adminAllowlist: false
11
+ - name: google-account-register-submit
12
+ publicAllowlist: false
13
+ adminAllowlist: false
11
14
  - name: google-calendar-list
12
15
  publicAllowlist: false
13
16
  adminAllowlist: false