@rubytech/create-maxy-code 0.1.470 → 0.1.471

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 (423) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/config/brand.json +1 -1
  3. package/payload/platform/docs/superpowers/plans/2026-07-18-graph-native-ledger.md +807 -0
  4. package/payload/platform/docs/superpowers/specs/2026-07-18-graph-native-ledger-design.md +241 -0
  5. package/payload/platform/lib/ledger-core/dist/ageing.d.ts +14 -0
  6. package/payload/platform/lib/ledger-core/dist/ageing.d.ts.map +1 -0
  7. package/payload/platform/lib/ledger-core/dist/ageing.js +38 -0
  8. package/payload/platform/lib/ledger-core/dist/ageing.js.map +1 -0
  9. package/payload/platform/lib/ledger-core/dist/balance.d.ts +25 -0
  10. package/payload/platform/lib/ledger-core/dist/balance.d.ts.map +1 -0
  11. package/payload/platform/lib/ledger-core/dist/balance.js +38 -0
  12. package/payload/platform/lib/ledger-core/dist/balance.js.map +1 -0
  13. package/payload/platform/lib/ledger-core/dist/index.d.ts +5 -0
  14. package/payload/platform/lib/ledger-core/dist/index.d.ts.map +1 -0
  15. package/payload/platform/lib/ledger-core/dist/index.js +5 -0
  16. package/payload/platform/lib/ledger-core/dist/index.js.map +1 -0
  17. package/payload/platform/lib/ledger-core/dist/money.d.ts +12 -0
  18. package/payload/platform/lib/ledger-core/dist/money.d.ts.map +1 -0
  19. package/payload/platform/lib/ledger-core/dist/money.js +16 -0
  20. package/payload/platform/lib/ledger-core/dist/money.js.map +1 -0
  21. package/payload/platform/lib/ledger-core/dist/reconcile.d.ts +51 -0
  22. package/payload/platform/lib/ledger-core/dist/reconcile.d.ts.map +1 -0
  23. package/payload/platform/lib/ledger-core/dist/reconcile.js +77 -0
  24. package/payload/platform/lib/ledger-core/dist/reconcile.js.map +1 -0
  25. package/payload/platform/lib/ledger-core/package.json +16 -0
  26. package/payload/platform/lib/ledger-core/src/__tests__/ageing.test.ts +42 -0
  27. package/payload/platform/lib/ledger-core/src/__tests__/balance.test.ts +48 -0
  28. package/payload/platform/lib/ledger-core/src/__tests__/reconcile.test.ts +87 -0
  29. package/payload/platform/lib/ledger-core/src/ageing.ts +51 -0
  30. package/payload/platform/lib/ledger-core/src/balance.ts +52 -0
  31. package/payload/platform/lib/ledger-core/src/index.ts +4 -0
  32. package/payload/platform/lib/ledger-core/src/money.ts +17 -0
  33. package/payload/platform/lib/ledger-core/src/reconcile.ts +109 -0
  34. package/payload/platform/lib/ledger-core/tsconfig.json +6 -0
  35. package/payload/platform/neo4j/schema.cypher +16 -0
  36. package/payload/platform/package.json +2 -2
  37. package/payload/platform/plugins/.claude-plugin/marketplace.json +5 -0
  38. package/payload/platform/plugins/admin/hooks/lib/maxy-mcp-plugins.txt +1 -0
  39. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +98 -2
  40. package/payload/platform/plugins/cloudflare/bin/__tests__/portal-brand-css.test.sh +139 -0
  41. package/payload/platform/plugins/cloudflare/bin/portal-brand-css.mjs +128 -0
  42. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-enrol.test.ts +1 -1
  43. package/payload/platform/plugins/cloudflare/mcp/__tests__/skill-contract.test.ts +25 -0
  44. package/payload/platform/plugins/cloudflare/mcp/__tests__/template-config.test.ts +55 -3
  45. package/payload/platform/plugins/cloudflare/skills/data-portal/SKILL.md +38 -3
  46. package/payload/platform/plugins/cloudflare/skills/data-portal/template/{public/portal.css → portal.css} +13 -2
  47. package/payload/platform/plugins/cloudflare/skills/data-portal/template/wrangler.toml +1 -1
  48. package/payload/platform/plugins/docs/references/admin-ui.md +48 -0
  49. package/payload/platform/plugins/docs/references/internals.md +1 -1
  50. package/payload/platform/plugins/docs/references/ledger.md +43 -0
  51. package/payload/platform/plugins/email/PLUGIN.md +4 -0
  52. package/payload/platform/plugins/email/mcp/dist/__tests__/display-truncation.test.d.ts +2 -0
  53. package/payload/platform/plugins/email/mcp/dist/__tests__/display-truncation.test.d.ts.map +1 -0
  54. package/payload/platform/plugins/email/mcp/dist/__tests__/display-truncation.test.js +196 -0
  55. package/payload/platform/plugins/email/mcp/dist/__tests__/display-truncation.test.js.map +1 -0
  56. package/payload/platform/plugins/email/mcp/dist/__tests__/source-fetch-efficiency.test.js +10 -8
  57. package/payload/platform/plugins/email/mcp/dist/__tests__/source-fetch-efficiency.test.js.map +1 -1
  58. package/payload/platform/plugins/email/mcp/dist/__tests__/truncation-tool-output.test.d.ts +2 -0
  59. package/payload/platform/plugins/email/mcp/dist/__tests__/truncation-tool-output.test.d.ts.map +1 -0
  60. package/payload/platform/plugins/email/mcp/dist/__tests__/truncation-tool-output.test.js +110 -0
  61. package/payload/platform/plugins/email/mcp/dist/__tests__/truncation-tool-output.test.js.map +1 -0
  62. package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts +22 -0
  63. package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts.map +1 -1
  64. package/payload/platform/plugins/email/mcp/dist/lib/imap.js +90 -36
  65. package/payload/platform/plugins/email/mcp/dist/lib/imap.js.map +1 -1
  66. package/payload/platform/plugins/email/mcp/dist/tools/email-read.d.ts.map +1 -1
  67. package/payload/platform/plugins/email/mcp/dist/tools/email-read.js +6 -2
  68. package/payload/platform/plugins/email/mcp/dist/tools/email-read.js.map +1 -1
  69. package/payload/platform/plugins/email/mcp/dist/tools/email-search.d.ts.map +1 -1
  70. package/payload/platform/plugins/email/mcp/dist/tools/email-search.js +6 -2
  71. package/payload/platform/plugins/email/mcp/dist/tools/email-search.js.map +1 -1
  72. package/payload/platform/plugins/email/skills/email-ingest/SKILL.md +4 -0
  73. package/payload/platform/plugins/google/mcp/dist/__tests__/account-register.test.d.ts +2 -0
  74. package/payload/platform/plugins/google/mcp/dist/__tests__/account-register.test.d.ts.map +1 -0
  75. package/payload/platform/plugins/google/mcp/dist/__tests__/account-register.test.js +60 -0
  76. package/payload/platform/plugins/google/mcp/dist/__tests__/account-register.test.js.map +1 -0
  77. package/payload/platform/plugins/google/mcp/dist/__tests__/calendar-tools.test.d.ts +2 -0
  78. package/payload/platform/plugins/google/mcp/dist/__tests__/calendar-tools.test.d.ts.map +1 -0
  79. package/payload/platform/plugins/google/mcp/dist/__tests__/calendar-tools.test.js +48 -0
  80. package/payload/platform/plugins/google/mcp/dist/__tests__/calendar-tools.test.js.map +1 -0
  81. package/payload/platform/plugins/google/mcp/dist/__tests__/google-client.test.d.ts +2 -0
  82. package/payload/platform/plugins/google/mcp/dist/__tests__/google-client.test.d.ts.map +1 -0
  83. package/payload/platform/plugins/google/mcp/dist/__tests__/google-client.test.js +25 -0
  84. package/payload/platform/plugins/google/mcp/dist/__tests__/google-client.test.js.map +1 -0
  85. package/payload/platform/plugins/google/mcp/dist/__tests__/index-registration.test.d.ts +2 -0
  86. package/payload/platform/plugins/google/mcp/dist/__tests__/index-registration.test.d.ts.map +1 -0
  87. package/payload/platform/plugins/google/mcp/dist/__tests__/index-registration.test.js +40 -0
  88. package/payload/platform/plugins/google/mcp/dist/__tests__/index-registration.test.js.map +1 -0
  89. package/payload/platform/plugins/google/mcp/dist/__tests__/pending-store.test.d.ts +2 -0
  90. package/payload/platform/plugins/google/mcp/dist/__tests__/pending-store.test.d.ts.map +1 -0
  91. package/payload/platform/plugins/google/mcp/dist/__tests__/pending-store.test.js +35 -0
  92. package/payload/platform/plugins/google/mcp/dist/__tests__/pending-store.test.js.map +1 -0
  93. package/payload/platform/plugins/google/mcp/dist/__tests__/token-store.test.d.ts +2 -0
  94. package/payload/platform/plugins/google/mcp/dist/__tests__/token-store.test.d.ts.map +1 -0
  95. package/payload/platform/plugins/google/mcp/dist/__tests__/token-store.test.js +85 -0
  96. package/payload/platform/plugins/google/mcp/dist/__tests__/token-store.test.js.map +1 -0
  97. package/payload/platform/plugins/google/mcp/dist/auth/pending-store.d.ts +25 -0
  98. package/payload/platform/plugins/google/mcp/dist/auth/pending-store.d.ts.map +1 -0
  99. package/payload/platform/plugins/google/mcp/dist/auth/pending-store.js +87 -0
  100. package/payload/platform/plugins/google/mcp/dist/auth/pending-store.js.map +1 -0
  101. package/payload/platform/plugins/google/mcp/dist/auth/token-store.d.ts +46 -0
  102. package/payload/platform/plugins/google/mcp/dist/auth/token-store.d.ts.map +1 -0
  103. package/payload/platform/plugins/google/mcp/dist/auth/token-store.js +138 -0
  104. package/payload/platform/plugins/google/mcp/dist/auth/token-store.js.map +1 -0
  105. package/payload/platform/plugins/google/mcp/dist/index.d.ts +25 -0
  106. package/payload/platform/plugins/google/mcp/dist/index.d.ts.map +1 -0
  107. package/payload/platform/plugins/google/mcp/dist/index.js +300 -0
  108. package/payload/platform/plugins/google/mcp/dist/index.js.map +1 -0
  109. package/payload/platform/plugins/google/mcp/dist/lib/google-client.d.ts +28 -0
  110. package/payload/platform/plugins/google/mcp/dist/lib/google-client.d.ts.map +1 -0
  111. package/payload/platform/plugins/google/mcp/dist/lib/google-client.js +102 -0
  112. package/payload/platform/plugins/google/mcp/dist/lib/google-client.js.map +1 -0
  113. package/payload/platform/plugins/google/mcp/dist/lib/log.d.ts +3 -0
  114. package/payload/platform/plugins/google/mcp/dist/lib/log.d.ts.map +1 -0
  115. package/payload/platform/plugins/google/mcp/dist/lib/log.js +16 -0
  116. package/payload/platform/plugins/google/mcp/dist/lib/log.js.map +1 -0
  117. package/payload/platform/plugins/google/mcp/dist/tools/account-info.d.ts +12 -0
  118. package/payload/platform/plugins/google/mcp/dist/tools/account-info.d.ts.map +1 -0
  119. package/payload/platform/plugins/google/mcp/dist/tools/account-info.js +23 -0
  120. package/payload/platform/plugins/google/mcp/dist/tools/account-info.js.map +1 -0
  121. package/payload/platform/plugins/google/mcp/dist/tools/account-list.d.ts +12 -0
  122. package/payload/platform/plugins/google/mcp/dist/tools/account-list.d.ts.map +1 -0
  123. package/payload/platform/plugins/google/mcp/dist/tools/account-list.js +9 -0
  124. package/payload/platform/plugins/google/mcp/dist/tools/account-list.js.map +1 -0
  125. package/payload/platform/plugins/google/mcp/dist/tools/account-register.d.ts +42 -0
  126. package/payload/platform/plugins/google/mcp/dist/tools/account-register.d.ts.map +1 -0
  127. package/payload/platform/plugins/google/mcp/dist/tools/account-register.js +102 -0
  128. package/payload/platform/plugins/google/mcp/dist/tools/account-register.js.map +1 -0
  129. package/payload/platform/plugins/google/mcp/dist/tools/calendar-cancel.d.ts +10 -0
  130. package/payload/platform/plugins/google/mcp/dist/tools/calendar-cancel.d.ts.map +1 -0
  131. package/payload/platform/plugins/google/mcp/dist/tools/calendar-cancel.js +9 -0
  132. package/payload/platform/plugins/google/mcp/dist/tools/calendar-cancel.js.map +1 -0
  133. package/payload/platform/plugins/google/mcp/dist/tools/calendar-create.d.ts +20 -0
  134. package/payload/platform/plugins/google/mcp/dist/tools/calendar-create.d.ts.map +1 -0
  135. package/payload/platform/plugins/google/mcp/dist/tools/calendar-create.js +25 -0
  136. package/payload/platform/plugins/google/mcp/dist/tools/calendar-create.js.map +1 -0
  137. package/payload/platform/plugins/google/mcp/dist/tools/calendar-event.d.ts +6 -0
  138. package/payload/platform/plugins/google/mcp/dist/tools/calendar-event.d.ts.map +1 -0
  139. package/payload/platform/plugins/google/mcp/dist/tools/calendar-event.js +7 -0
  140. package/payload/platform/plugins/google/mcp/dist/tools/calendar-event.js.map +1 -0
  141. package/payload/platform/plugins/google/mcp/dist/tools/calendar-freebusy.d.ts +14 -0
  142. package/payload/platform/plugins/google/mcp/dist/tools/calendar-freebusy.d.ts.map +1 -0
  143. package/payload/platform/plugins/google/mcp/dist/tools/calendar-freebusy.js +16 -0
  144. package/payload/platform/plugins/google/mcp/dist/tools/calendar-freebusy.js.map +1 -0
  145. package/payload/platform/plugins/google/mcp/dist/tools/calendar-list.d.ts +44 -0
  146. package/payload/platform/plugins/google/mcp/dist/tools/calendar-list.d.ts.map +1 -0
  147. package/payload/platform/plugins/google/mcp/dist/tools/calendar-list.js +36 -0
  148. package/payload/platform/plugins/google/mcp/dist/tools/calendar-list.js.map +1 -0
  149. package/payload/platform/plugins/google/mcp/dist/tools/calendar-respond.d.ts +16 -0
  150. package/payload/platform/plugins/google/mcp/dist/tools/calendar-respond.d.ts.map +1 -0
  151. package/payload/platform/plugins/google/mcp/dist/tools/calendar-respond.js +29 -0
  152. package/payload/platform/plugins/google/mcp/dist/tools/calendar-respond.js.map +1 -0
  153. package/payload/platform/plugins/google/mcp/dist/tools/calendar-update.d.ts +16 -0
  154. package/payload/platform/plugins/google/mcp/dist/tools/calendar-update.d.ts.map +1 -0
  155. package/payload/platform/plugins/google/mcp/dist/tools/calendar-update.js +32 -0
  156. package/payload/platform/plugins/google/mcp/dist/tools/calendar-update.js.map +1 -0
  157. package/payload/platform/plugins/google/references/auth.md +35 -0
  158. package/payload/platform/plugins/ledger/.claude-plugin/plugin.json +21 -0
  159. package/payload/platform/plugins/ledger/PLUGIN.md +82 -0
  160. package/payload/platform/plugins/ledger/lib/mcp-spawn-tee/index.js +193 -0
  161. package/payload/platform/plugins/ledger/lib/mcp-spawn-tee/package.json +3 -0
  162. package/payload/platform/plugins/ledger/mcp/dist/index.d.ts +2 -0
  163. package/payload/platform/plugins/ledger/mcp/dist/index.d.ts.map +1 -0
  164. package/payload/platform/plugins/ledger/mcp/dist/index.js +168 -0
  165. package/payload/platform/plugins/ledger/mcp/dist/index.js.map +1 -0
  166. package/payload/platform/plugins/ledger/mcp/dist/lib/ids.d.ts +4 -0
  167. package/payload/platform/plugins/ledger/mcp/dist/lib/ids.d.ts.map +1 -0
  168. package/payload/platform/plugins/ledger/mcp/dist/lib/ids.js +0 -0
  169. package/payload/platform/plugins/ledger/mcp/dist/lib/ids.js.map +1 -0
  170. package/payload/platform/plugins/ledger/mcp/dist/lib/neo4j.d.ts +5 -0
  171. package/payload/platform/plugins/ledger/mcp/dist/lib/neo4j.d.ts.map +1 -0
  172. package/payload/platform/plugins/ledger/mcp/dist/lib/neo4j.js +40 -0
  173. package/payload/platform/plugins/ledger/mcp/dist/lib/neo4j.js.map +1 -0
  174. package/payload/platform/plugins/ledger/mcp/dist/lib/read.d.ts +29 -0
  175. package/payload/platform/plugins/ledger/mcp/dist/lib/read.d.ts.map +1 -0
  176. package/payload/platform/plugins/ledger/mcp/dist/lib/read.js +138 -0
  177. package/payload/platform/plugins/ledger/mcp/dist/lib/read.js.map +1 -0
  178. package/payload/platform/plugins/ledger/mcp/dist/lib/types.d.ts +47 -0
  179. package/payload/platform/plugins/ledger/mcp/dist/lib/types.d.ts.map +1 -0
  180. package/payload/platform/plugins/ledger/mcp/dist/lib/types.js +14 -0
  181. package/payload/platform/plugins/ledger/mcp/dist/lib/types.js.map +1 -0
  182. package/payload/platform/plugins/ledger/mcp/dist/tools/balance.d.ts +18 -0
  183. package/payload/platform/plugins/ledger/mcp/dist/tools/balance.d.ts.map +1 -0
  184. package/payload/platform/plugins/ledger/mcp/dist/tools/balance.js +23 -0
  185. package/payload/platform/plugins/ledger/mcp/dist/tools/balance.js.map +1 -0
  186. package/payload/platform/plugins/ledger/mcp/dist/tools/cash-record.d.ts +26 -0
  187. package/payload/platform/plugins/ledger/mcp/dist/tools/cash-record.d.ts.map +1 -0
  188. package/payload/platform/plugins/ledger/mcp/dist/tools/cash-record.js +33 -0
  189. package/payload/platform/plugins/ledger/mcp/dist/tools/cash-record.js.map +1 -0
  190. package/payload/platform/plugins/ledger/mcp/dist/tools/invoice-record.d.ts +27 -0
  191. package/payload/platform/plugins/ledger/mcp/dist/tools/invoice-record.d.ts.map +1 -0
  192. package/payload/platform/plugins/ledger/mcp/dist/tools/invoice-record.js +40 -0
  193. package/payload/platform/plugins/ledger/mcp/dist/tools/invoice-record.js.map +1 -0
  194. package/payload/platform/plugins/ledger/mcp/dist/tools/payment-record.d.ts +46 -0
  195. package/payload/platform/plugins/ledger/mcp/dist/tools/payment-record.d.ts.map +1 -0
  196. package/payload/platform/plugins/ledger/mcp/dist/tools/payment-record.js +68 -0
  197. package/payload/platform/plugins/ledger/mcp/dist/tools/payment-record.js.map +1 -0
  198. package/payload/platform/plugins/ledger/mcp/dist/tools/reconcile.d.ts +20 -0
  199. package/payload/platform/plugins/ledger/mcp/dist/tools/reconcile.d.ts.map +1 -0
  200. package/payload/platform/plugins/ledger/mcp/dist/tools/reconcile.js +30 -0
  201. package/payload/platform/plugins/ledger/mcp/dist/tools/reconcile.js.map +1 -0
  202. package/payload/platform/plugins/ledger/mcp/dist/tools/statement.d.ts +26 -0
  203. package/payload/platform/plugins/ledger/mcp/dist/tools/statement.d.ts.map +1 -0
  204. package/payload/platform/plugins/ledger/mcp/dist/tools/statement.js +35 -0
  205. package/payload/platform/plugins/ledger/mcp/dist/tools/statement.js.map +1 -0
  206. package/payload/platform/plugins/ledger/mcp/package.json +21 -0
  207. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/ledger-vertical-coverage.test.d.ts +2 -0
  208. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/ledger-vertical-coverage.test.d.ts.map +1 -0
  209. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/ledger-vertical-coverage.test.js +81 -0
  210. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/ledger-vertical-coverage.test.js.map +1 -0
  211. package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.d.ts +9 -7
  212. package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.d.ts.map +1 -1
  213. package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.js +10 -8
  214. package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.js.map +1 -1
  215. package/payload/platform/plugins/memory/references/schema-base.md +22 -0
  216. package/payload/platform/plugins/memory/references/schema-construction.md +0 -18
  217. package/payload/platform/plugins/scheduling/PLUGIN.md +20 -0
  218. package/payload/platform/plugins/scheduling/mcp/dist/lib/meeting-write.d.ts.map +1 -1
  219. package/payload/platform/plugins/scheduling/mcp/dist/lib/meeting-write.js +11 -2
  220. package/payload/platform/plugins/scheduling/mcp/dist/lib/meeting-write.js.map +1 -1
  221. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js +1 -1
  222. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js.map +1 -1
  223. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-create.js +3 -3
  224. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-create.js.map +1 -1
  225. package/payload/platform/services/claude-session-manager/dist/activity-rows.d.ts +138 -0
  226. package/payload/platform/services/claude-session-manager/dist/activity-rows.d.ts.map +1 -0
  227. package/payload/platform/services/claude-session-manager/dist/activity-rows.js +268 -0
  228. package/payload/platform/services/claude-session-manager/dist/activity-rows.js.map +1 -0
  229. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
  230. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +8 -0
  231. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
  232. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts +6 -0
  233. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  234. package/payload/platform/services/claude-session-manager/dist/http-server.js +42 -0
  235. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  236. package/payload/platform/services/claude-session-manager/dist/index.js +10 -1
  237. package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
  238. package/payload/platform/services/claude-session-manager/dist/pty-census.d.ts +80 -6
  239. package/payload/platform/services/claude-session-manager/dist/pty-census.d.ts.map +1 -1
  240. package/payload/platform/services/claude-session-manager/dist/pty-census.js +96 -20
  241. package/payload/platform/services/claude-session-manager/dist/pty-census.js.map +1 -1
  242. package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.d.ts.map +1 -1
  243. package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.js +5 -1
  244. package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.js.map +1 -1
  245. package/payload/platform/templates/specialists/agents/database-operator.md +1 -1
  246. package/payload/platform/templates/specialists/agents/project-manager.md +1 -1
  247. package/payload/server/{chunk-JRIBGO45.js → chunk-BSTIQX52.js} +44 -0
  248. package/payload/server/maxy-edge.js +1 -1
  249. package/payload/server/public/activity.html +17 -0
  250. package/payload/server/public/assets/AdminLoginScreens-Rf94VD9w.js +1 -0
  251. package/payload/server/public/assets/AdminShell-CyGKg7MG.js +2 -0
  252. package/payload/server/public/assets/Checkbox-B0jmI2y2.js +1 -0
  253. package/payload/server/public/assets/HeaderMenu-BnTYGNaB.js +9 -0
  254. package/payload/server/public/assets/{useSubAccountSwitcher-pu0jjLCz.css → HeaderMenu-DtIEBHOU.css} +1 -1
  255. package/payload/server/public/assets/activity-BOBTWFr-.js +1 -0
  256. package/payload/server/public/assets/admin-HDLzwbLl.js +1 -0
  257. package/payload/server/public/assets/{arc-qKfXIt8v.js → arc-CFMJyMm-.js} +1 -1
  258. package/payload/server/public/assets/architecture-YZFGNWBL-Cypt5qHL.js +1 -0
  259. package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-DkVrGn7G.js → architectureDiagram-Q4EWVU46-CmOpXLsH.js} +1 -1
  260. package/payload/server/public/assets/{blockDiagram-DXYQGD6D-DqavswGH.js → blockDiagram-DXYQGD6D-CmS8f9YF.js} +1 -1
  261. package/payload/server/public/assets/browser-D4_XLSSm.js +1 -0
  262. package/payload/server/public/assets/{c4Diagram-AHTNJAMY-B7fyKiXe.js → c4Diagram-AHTNJAMY-BXIjMl6A.js} +1 -1
  263. package/payload/server/public/assets/calendar-s5V_gbI7.js +1 -0
  264. package/payload/server/public/assets/channel-DhrFAm7m.js +1 -0
  265. package/payload/server/public/assets/chat-40WyKzGb.js +1 -0
  266. package/payload/server/public/assets/chevron-left-C35qGP5f.js +1 -0
  267. package/payload/server/public/assets/{chunk-2KRD3SAO-DsgMtGkv.js → chunk-2KRD3SAO-D4fktAwW.js} +1 -1
  268. package/payload/server/public/assets/{chunk-336JU56O-CkdISfNt.js → chunk-336JU56O-C2MKnRTj.js} +2 -2
  269. package/payload/server/public/assets/chunk-426QAEUC-BhU7-TyS.js +1 -0
  270. package/payload/server/public/assets/{chunk-4BX2VUAB-CYpiF2bl.js → chunk-4BX2VUAB-C13Wz3zS.js} +1 -1
  271. package/payload/server/public/assets/{chunk-4TB4RGXK-CWyc_F7v.js → chunk-4TB4RGXK-CalYahTR.js} +1 -1
  272. package/payload/server/public/assets/{chunk-55IACEB6-BbzKMw2Z.js → chunk-55IACEB6-TmOKdy8a.js} +1 -1
  273. package/payload/server/public/assets/{chunk-5FUZZQ4R-u19Nai9e.js → chunk-5FUZZQ4R-C-i0BVag.js} +1 -1
  274. package/payload/server/public/assets/{chunk-5PVQY5BW-Ba8by6Uy.js → chunk-5PVQY5BW-D6bJMLnf.js} +1 -1
  275. package/payload/server/public/assets/{chunk-67CJDMHE-BwUuxtyb.js → chunk-67CJDMHE-Cif9Uu5C.js} +1 -1
  276. package/payload/server/public/assets/{chunk-7N4EOEYR-BRH9iM9L.js → chunk-7N4EOEYR-WjTHCXnd.js} +1 -1
  277. package/payload/server/public/assets/{chunk-AA7GKIK3-BGoztgLL.js → chunk-AA7GKIK3-CwCfdQmb.js} +1 -1
  278. package/payload/server/public/assets/{chunk-BSJP7CBP-C5GDAZTl.js → chunk-BSJP7CBP-9A67yXwT.js} +1 -1
  279. package/payload/server/public/assets/{chunk-CIAEETIT-ChY32VPS.js → chunk-CIAEETIT-DPMiWSBi.js} +1 -1
  280. package/payload/server/public/assets/{chunk-EDXVE4YY-CPSD3p6p.js → chunk-EDXVE4YY-B9PBdwiT.js} +1 -1
  281. package/payload/server/public/assets/{chunk-ENJZ2VHE-D1oL-iUr.js → chunk-ENJZ2VHE-CM5OqcQ5.js} +1 -1
  282. package/payload/server/public/assets/{chunk-FMBD7UC4-CPvYEr54.js → chunk-FMBD7UC4-Bmt_ZRlV.js} +1 -1
  283. package/payload/server/public/assets/{chunk-FOC6F5B3-9Yyj7Vbz.js → chunk-FOC6F5B3-DfGJGxpX.js} +1 -1
  284. package/payload/server/public/assets/{chunk-ICPOFSXX-BaYgh8FR.js → chunk-ICPOFSXX-D6M89DA0.js} +2 -2
  285. package/payload/server/public/assets/{chunk-K5T4RW27-5ucpeRvm.js → chunk-K5T4RW27-DMpoaqw2.js} +1 -1
  286. package/payload/server/public/assets/{chunk-KGLVRYIC-DW-FHk7T.js → chunk-KGLVRYIC-DYUFgri2.js} +1 -1
  287. package/payload/server/public/assets/{chunk-LIHQZDEY-CGGVfAyG.js → chunk-LIHQZDEY-x3LmeKmx.js} +1 -1
  288. package/payload/server/public/assets/{chunk-ORNJ4GCN-Bv8EUa5d.js → chunk-ORNJ4GCN-BeuNAWkW.js} +1 -1
  289. package/payload/server/public/assets/{chunk-OYMX7WX6-C1mXJnyw.js → chunk-OYMX7WX6-Bv6gisQ6.js} +1 -1
  290. package/payload/server/public/assets/chunk-QZHKN3VN-7_kS3RBy.js +1 -0
  291. package/payload/server/public/assets/{chunk-U2HBQHQK-PwSQz6EP.js → chunk-U2HBQHQK-C2iT9Av2.js} +1 -1
  292. package/payload/server/public/assets/{chunk-X2U36JSP--NPHTBlU.js → chunk-X2U36JSP-Bd6bv87A.js} +1 -1
  293. package/payload/server/public/assets/{chunk-XPW4576I-Bbc7Da32.js → chunk-XPW4576I-ByFnN4JO.js} +1 -1
  294. package/payload/server/public/assets/{chunk-YZCP3GAM-D-9v8e--.js → chunk-YZCP3GAM-Bt8I6ZDb.js} +1 -1
  295. package/payload/server/public/assets/{chunk-ZZ45TVLE-CMPg670Q.js → chunk-ZZ45TVLE-DBMPgZ_A.js} +1 -1
  296. package/payload/server/public/assets/classDiagram-6PBFFD2Q-BrWqm88p.js +1 -0
  297. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-D0wzG5Fd.js +1 -0
  298. package/payload/server/public/assets/clone-DQA-nNrl.js +1 -0
  299. package/payload/server/public/assets/{cose-bilkent-S5V4N54A-NVSb1Bhy.js → cose-bilkent-S5V4N54A-DgLEaApT.js} +1 -1
  300. package/payload/server/public/assets/{dagre-KV5264BT-V2Ai1DFl.js → dagre-KV5264BT-kWVPgmmL.js} +1 -1
  301. package/payload/server/public/assets/{dagre-QfLcO-1j.js → dagre-mSazqXgG.js} +1 -1
  302. package/payload/server/public/assets/data-Bq0vU3qJ.js +1 -0
  303. package/payload/server/public/assets/{diagram-5BDNPKRD-Caw21693.js → diagram-5BDNPKRD-BHe_omJD.js} +1 -1
  304. package/payload/server/public/assets/{diagram-G4DWMVQ6-CgwVkT2q.js → diagram-G4DWMVQ6-fUFsmM3x.js} +1 -1
  305. package/payload/server/public/assets/{diagram-MMDJMWI5-DFUrLZhs.js → diagram-MMDJMWI5-DmujPLlM.js} +1 -1
  306. package/payload/server/public/assets/{diagram-TYMM5635-CEEiEuZJ.js → diagram-TYMM5635-B7Aoas89.js} +1 -1
  307. package/payload/server/public/assets/{dist-Cvgxm7qv.js → dist-C9D_xz81.js} +1 -1
  308. package/payload/server/public/assets/{erDiagram-SMLLAGMA-CG10NrT3.js → erDiagram-SMLLAGMA-6hS1rGiO.js} +1 -1
  309. package/payload/server/public/assets/{flatten-DAO0sfCM.js → flatten-BXzaHgtL.js} +1 -1
  310. package/payload/server/public/assets/{flowDiagram-DWJPFMVM-BqkV9Okv.js → flowDiagram-DWJPFMVM-Bqhr_r5b.js} +1 -1
  311. package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-DRiCDQF6.js → ganttDiagram-T4ZO3ILL-B8kjyN4o.js} +1 -1
  312. package/payload/server/public/assets/gitGraph-7Q5UKJZL-C_-XjxLd.js +1 -0
  313. package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-BZV2voyy.js → gitGraphDiagram-UUTBAWPF--TTRq4ir.js} +1 -1
  314. package/payload/server/public/assets/{graph-BZiudUGr.js → graph-C252ZHjp.js} +3 -3
  315. package/payload/server/public/assets/graph-labels-DzPbbhLc.js +1 -0
  316. package/payload/server/public/assets/{graphlib-DGPThX8M.js → graphlib-CWkjawc5.js} +1 -1
  317. package/payload/server/public/assets/info-OMHHGYJF-BH8yDCfL.js +1 -0
  318. package/payload/server/public/assets/infoDiagram-42DDH7IO-D8U4M0pK.js +2 -0
  319. package/payload/server/public/assets/{isEmpty-Cb_U81C6.js → isEmpty-uB1dEu_Z.js} +1 -1
  320. package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-BKsAxSvp.js → ishikawaDiagram-UXIWVN3A-CR8B7VzM.js} +1 -1
  321. package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-CmliuOd-.js → journeyDiagram-VCZTEJTY-45lWAvtq.js} +1 -1
  322. package/payload/server/public/assets/{kanban-definition-6JOO6SKY-DAlElMb-.js → kanban-definition-6JOO6SKY-DkrPXLVb.js} +1 -1
  323. package/payload/server/public/assets/{line-DKZqLKAC.js → line-Db0H446a.js} +1 -1
  324. package/payload/server/public/assets/{linear-CbSGdh1x.js → linear-X3O7lwK3.js} +1 -1
  325. package/payload/server/public/assets/maximize-2-CEbXFpQT.js +1 -0
  326. package/payload/server/public/assets/{mermaid-parser.core-DFvbao9C.js → mermaid-parser.core-CixBHOya.js} +2 -2
  327. package/payload/server/public/assets/{mermaid.core-C0vEBxDm.js → mermaid.core-BGQq98L7.js} +3 -3
  328. package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-CShASUKd.js → mindmap-definition-QFDTVHPH-v9cJl1mj.js} +1 -1
  329. package/payload/server/public/assets/operator-zKk4HMdX.js +1 -0
  330. package/payload/server/public/assets/{ordinal-H0tVmBDP.js → ordinal-CZY9laxg.js} +1 -1
  331. package/payload/server/public/assets/packet-4T2RLAQJ-Bajcns4r.js +1 -0
  332. package/payload/server/public/assets/page-BrmN_vzV.js +1 -0
  333. package/payload/server/public/assets/page-Cts-ESBe.js +32 -0
  334. package/payload/server/public/assets/{pdf-render-jAQU0nXt.js → pdf-render-7HZBTZ0_.js} +1 -1
  335. package/payload/server/public/assets/pie-ZZUOXDRM-D7hjM5w1.js +1 -0
  336. package/payload/server/public/assets/{pieDiagram-DEJITSTG-CBZHpDAJ.js → pieDiagram-DEJITSTG-DQ0b6edX.js} +1 -1
  337. package/payload/server/public/assets/public-BItzHBc0.js +1 -0
  338. package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-n3bB3K91.js → quadrantDiagram-34T5L4WZ-B64mj7Ei.js} +1 -1
  339. package/payload/server/public/assets/radar-PYXPWWZC-B46d5x7a.js +1 -0
  340. package/payload/server/public/assets/{reduce-DAvwat-a.js → reduce-BGQzAenn.js} +1 -1
  341. package/payload/server/public/assets/{requirementDiagram-MS252O5E-DiDvQK3c.js → requirementDiagram-MS252O5E-CE2JIEWo.js} +1 -1
  342. package/payload/server/public/assets/rotate-ccw-B0EYGhGx.js +1 -0
  343. package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-DWDQQZmd.js → sankeyDiagram-XADWPNL6-Cmt5cfCz.js} +1 -1
  344. package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-DmAqRlBT.js → sequenceDiagram-FGHM5R23-B5vVn_sw.js} +1 -1
  345. package/payload/server/public/assets/{src-DV4LKv2E.js → src-BTKk8KAG.js} +1 -1
  346. package/payload/server/public/assets/{stateDiagram-FHFEXIEX-D4R-n6bZ.js → stateDiagram-FHFEXIEX-DiUEJ-RZ.js} +1 -1
  347. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-DIm3XuAD.js +1 -0
  348. package/payload/server/public/assets/tasks-ClE8-be8.js +1 -0
  349. package/payload/server/public/assets/time-entry-format-BwjGLnXu.js +1 -0
  350. package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-CjiHGP4l.js → timeline-definition-GMOUNBTQ-C9jprDVw.js} +1 -1
  351. package/payload/server/public/assets/treeView-SZITEDCU-3XQh5xQ1.js +1 -0
  352. package/payload/server/public/assets/treemap-W4RFUUIX-DwR-_l5z.js +1 -0
  353. package/payload/server/public/assets/triangle-alert-DejWYn1x.js +1 -0
  354. package/payload/server/public/assets/useCopyFeedback-CpSgOavK.js +1 -0
  355. package/payload/server/public/assets/useSelectionMode-ZQZQUgze.js +1 -0
  356. package/payload/server/public/assets/useSubAccountSwitcher-DuRLvCfO.js +1 -0
  357. package/payload/server/public/assets/useVoiceRecorder-BTYYeUJW.js +2 -0
  358. package/payload/server/public/assets/{vennDiagram-DHZGUBPP-CBdDQ7c-.js → vennDiagram-DHZGUBPP-DWqQJ9X0.js} +1 -1
  359. package/payload/server/public/assets/wardley-RL74JXVD-DZp9oCGv.js +1 -0
  360. package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-BlIQPKLb.js → wardleyDiagram-NUSXRM2D-Dg6c2Voh.js} +1 -1
  361. package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-CslKgh9B.js → xychartDiagram-5P7HB3ND-EhN-zeDj.js} +1 -1
  362. package/payload/server/public/browser.html +7 -6
  363. package/payload/server/public/calendar.html +8 -7
  364. package/payload/server/public/chat.html +15 -14
  365. package/payload/server/public/data.html +14 -12
  366. package/payload/server/public/graph.html +13 -12
  367. package/payload/server/public/index.html +17 -15
  368. package/payload/server/public/operator.html +17 -16
  369. package/payload/server/public/public.html +15 -14
  370. package/payload/server/public/tasks.html +7 -6
  371. package/payload/server/server.js +525 -244
  372. package/payload/server/public/assets/AdminLoginScreens-Bed8SDxY.js +0 -1
  373. package/payload/server/public/assets/AdminShell-BLh9O6dn.js +0 -2
  374. package/payload/server/public/assets/Checkbox-BC_KSbpS.js +0 -1
  375. package/payload/server/public/assets/admin-DfWgJVI3.js +0 -1
  376. package/payload/server/public/assets/architecture-YZFGNWBL-FPHT7s-C.js +0 -1
  377. package/payload/server/public/assets/browser-C0vkv-hH.js +0 -1
  378. package/payload/server/public/assets/calendar-_pfHw0Hp.js +0 -1
  379. package/payload/server/public/assets/channel-j70eMvcL.js +0 -1
  380. package/payload/server/public/assets/chat-DYT5H81o.js +0 -1
  381. package/payload/server/public/assets/chevron-left-BnpVBu51.js +0 -1
  382. package/payload/server/public/assets/chunk-426QAEUC-D-NcAlTW.js +0 -1
  383. package/payload/server/public/assets/chunk-QZHKN3VN-CAwbQBtf.js +0 -1
  384. package/payload/server/public/assets/classDiagram-6PBFFD2Q-Ct6Rn0vt.js +0 -1
  385. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-B_2XCIFv.js +0 -1
  386. package/payload/server/public/assets/clone-DjPWA_NH.js +0 -1
  387. package/payload/server/public/assets/data-CR-fbPWr.js +0 -1
  388. package/payload/server/public/assets/gitGraph-7Q5UKJZL-B8Hx7f-D.js +0 -1
  389. package/payload/server/public/assets/graph-labels-BzmmJwCR.js +0 -1
  390. package/payload/server/public/assets/info-OMHHGYJF-BicCJTqU.js +0 -1
  391. package/payload/server/public/assets/infoDiagram-42DDH7IO-tf-8rOez.js +0 -2
  392. package/payload/server/public/assets/maximize-2-Bhdsv0U-.js +0 -1
  393. package/payload/server/public/assets/operator-gm2eP4r6.js +0 -1
  394. package/payload/server/public/assets/packet-4T2RLAQJ-Ckn_5hq0.js +0 -1
  395. package/payload/server/public/assets/page-8AB3jUa_.js +0 -1
  396. package/payload/server/public/assets/page-BoPorLg-.js +0 -32
  397. package/payload/server/public/assets/pie-ZZUOXDRM-DzlGQW3J.js +0 -1
  398. package/payload/server/public/assets/public-DqJk6qgW.js +0 -1
  399. package/payload/server/public/assets/radar-PYXPWWZC-BHo2YD8b.js +0 -1
  400. package/payload/server/public/assets/rotate-ccw-D_5cETGt.js +0 -1
  401. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-BcFRbGRu.js +0 -1
  402. package/payload/server/public/assets/tasks-EyKLXINi.js +0 -1
  403. package/payload/server/public/assets/time-entry-format-DAwmmR3i.js +0 -1
  404. package/payload/server/public/assets/treeView-SZITEDCU-BtRr5vXp.js +0 -1
  405. package/payload/server/public/assets/treemap-W4RFUUIX-D8p0sbyo.js +0 -1
  406. package/payload/server/public/assets/useCopyFeedback-rOWd_lzl.js +0 -1
  407. package/payload/server/public/assets/useSelectionMode-DDmLmPph.js +0 -1
  408. package/payload/server/public/assets/useSubAccountSwitcher-CU-lOxTd.js +0 -9
  409. package/payload/server/public/assets/useVoiceRecorder-azG8zSUI.js +0 -2
  410. package/payload/server/public/assets/wardley-RL74JXVD-DgMn-9ZJ.js +0 -1
  411. /package/payload/platform/plugins/cloudflare/skills/data-portal/{template/schema.sql → schema.sql} +0 -0
  412. /package/payload/platform/plugins/cloudflare/skills/data-portal/template/{public/index.html → index.html} +0 -0
  413. /package/payload/platform/plugins/cloudflare/skills/data-portal/template/{public/portal.js → portal.js} +0 -0
  414. /package/payload/server/public/assets/{_baseFor-D3BqAOBr.js → _baseFor-CMWvz0uA.js} +0 -0
  415. /package/payload/server/public/assets/{array-iYP9fzJp.js → array-Cny_NT6T.js} +0 -0
  416. /package/payload/server/public/assets/{chunk-Ck5AOu5v.js → chunk-TPjIAO9U.js} +0 -0
  417. /package/payload/server/public/assets/{cytoscape.esm-BQSBzPsB.js → cytoscape.esm-De0CCmB2.js} +0 -0
  418. /package/payload/server/public/assets/{defaultLocale-BrdA1gDi.js → defaultLocale-BahRht8U.js} +0 -0
  419. /package/payload/server/public/assets/{init-Bx0S6pkp.js → init-DoIvjoJ_.js} +0 -0
  420. /package/payload/server/public/assets/{katex-BsiH_a6d.js → katex-Bfn1OZEl.js} +0 -0
  421. /package/payload/server/public/assets/{path-BLSgTy3w.js → path-CTezU8L5.js} +0 -0
  422. /package/payload/server/public/assets/{preload-helper-ClBMtM1g.js → preload-helper-CT1Z6Pdu.js} +0 -0
  423. /package/payload/server/public/assets/{rough.esm-BMOtDaaS.js → rough.esm-BJo4pvXu.js} +0 -0
@@ -0,0 +1,241 @@
1
+ # Graph-native cash book + AR/AP ledger — design
2
+
3
+ Spec for [Task 1771](../../../../.tasks/1771-graph-native-cash-book-ledger.md). Written 2026-07-18.
4
+
5
+ ## Why
6
+
7
+ On 2026-07-18 an admin asked the agent to record a £2,000 payment. The agent began inventing `Invoice`
8
+ properties (`total`, `amountPaid`, `balance`, `status:"part-paid"`), then reported that no ledger exists
9
+ and offered QuickBooks, a bespoke design, or a reminder task.
10
+
11
+ The report was partly wrong. `:Invoice` is in the base ontology (`schema-base.md:20`). But
12
+ `:InvoicePayment`, `:Credit`, `:InvoiceLine` and `:InboundInvoice` are declared only for the construction
13
+ vertical (`schema-construction.md:63-64`), so on the other nine `businessType` values an invoice can be
14
+ raised and a payment against it has nowhere to land. `paymentStatus` is free text. No arithmetic anywhere
15
+ relates an invoice total to what has been paid. The QuickBooks plugin performs zero graph writes. Write
16
+ Cypher is specialist-only (`platform/plugins/graph/PLUGIN.md:42`), so the admin agent has no deterministic
17
+ path even where the labels exist.
18
+
19
+ ## Corrections to the task file
20
+
21
+ Three statements in Task 1771 were checked against the tree and do not hold. The implementation follows
22
+ the tree, not the task file.
23
+
24
+ 1. **`ADMIN_CORE_TOOLS` does not exist.** `grep -rn "ADMIN_CORE_TOOLS\s*[=:]"` returns zero hits. It is
25
+ named in `CLAUDE.md:35` but `.docs/superpowers/plans/2026-06-26-email-body-extraction.md:15` records it
26
+ as stale. The live mechanism is `eagerTool` (`platform/lib/mcp-eager/src/index.ts:63`), which sets
27
+ `_meta["anthropic/alwaysLoad"]`, plus the generated canonical-tool-names artifact.
28
+ 2. **`packages/create-maxy-code/payload/` must not be hand-edited.** It is gitignored and
29
+ `scripts/bundle.js:276-280` wipes it on every run.
30
+ 3. **The ledger edges do not belong in `TYPED_EDGE_ALLOWLIST`.** See § Ontology below. The task file's
31
+ list of eight triples is dropped; five patterns go to `schema-base.md` § Relationship Patterns instead,
32
+ and `typed-edge-schema.ts` is not touched.
33
+ 4. **A tool alone is not a standing check.** There is no plugin-level API for recurring jobs. The two real
34
+ homes are a `setInterval` module wired into `platform/ui/server/index.ts`, or code called from
35
+ `platform/plugins/scheduling/mcp/src/scripts/check-due-events.ts`. The SiteDesk censuses cited as
36
+ precedent in the task file (`quote-generation.md:241-243`, `issue-flow.md:103-115`) have **no emitter in
37
+ code**; they are documented log lines nothing produces. Shipping `ledger-reconcile` as only a tool would
38
+ reproduce that defect.
39
+
40
+ ## Architecture
41
+
42
+ Three units. The arithmetic invariant is needed by two processes, so it lives in neither of them.
43
+
44
+ ### 1. `platform/lib/ledger-core/` — pure
45
+
46
+ No Neo4j, no filesystem, no environment reads. Exports:
47
+
48
+ - `computeOutstanding(totalPaymentDue, payments, credits)` — the invariant
49
+ `outstanding = totalPaymentDue - Σpayments - Σcredits`.
50
+ - `applyPayment(invoice, existing, candidate)` — returns `{accepted}` or
51
+ `{rejected, reason: 'over-application', remaining}`. Never clamps.
52
+ - `ageBuckets(openInvoices, now)` — 0-30 / 31-60 / 61-90 / 90+ days.
53
+ - `reconcileLedger(rows, now)` — returns the census finding, including `alarm`.
54
+
55
+ `platform/ui` already imports `platform/lib/*/dist` (`server/index.ts:45`,
56
+ `app/lib/graph-health.ts:25`), so one shared lib serves both consumers and the invariant is defined once.
57
+ ledger-core computes in integer minor units so the equality is exact and never subject to float drift.
58
+ Graph storage is unchanged: `amount` and `totalPaymentDue` stay decimal numbers, matching the existing
59
+ JobLogic-exact construction rows. The tools convert on the way in and on the way out, so no stored value
60
+ changes shape and existing construction data stays readable.
61
+
62
+ ### 2. `platform/plugins/ledger/` — the MCP
63
+
64
+ Seven tools, registered with `eagerTool`, which wraps each handler in `wrapWithLifeline` and therefore
65
+ emits `op=request` and `op=result` with a correlation token and a handle census for free. This also
66
+ satisfies `platform/scripts/check-no-raw-mcp-registrations.mjs`.
67
+
68
+ | Tool | Purpose |
69
+ |---|---|
70
+ | `ledger-invoice-record` | Raise an AR `:Invoice` or record an AP `:InboundInvoice`, selected by a required `direction` |
71
+ | `ledger-payment-record` | Apply a payment to either invoice label |
72
+ | `ledger-credit-record` | Apply a credit to an `:Invoice` |
73
+ | `ledger-cash-record` | A `:CashEntry` not attached to any invoice |
74
+ | `ledger-balance` | One invoice: total, applied, outstanding |
75
+ | `ledger-statement` | Open receivables in ageing buckets, open payables, net cash position |
76
+ | `ledger-reconcile` | On-demand form of the census |
77
+
78
+ Structure copies `platform/plugins/contacts/`: own `mcp/src/lib/neo4j.ts`, per-tool files under
79
+ `mcp/src/tools/`, writes through `writeNodeWithEdges` from `platform/lib/graph-write/dist/index.js`.
80
+ `accountId` is read once from `process.env.ACCOUNT_ID` at `index.ts` and passed in, never derived inside a
81
+ tool. `createdBy` is `{agent: AGENT_SLUG, session: SESSION_ID, tool: <name>}`. Every arithmetic decision
82
+ delegates to ledger-core; the plugin contains no money arithmetic of its own.
83
+
84
+ ### 3. `platform/ui/app/lib/ledger-census.ts` — the standing check
85
+
86
+ Mirrors `platform/ui/app/lib/timeentry-census.ts`: pure reconcile imported from ledger-core, impure
87
+ `runLedgerCensusOnce(session, now)`, and `startLedgerCensus(openSession, opts)` returning a stop function.
88
+ The interval is `unref()`'d so it never holds the process open, defaults to 15 minutes matching its
89
+ sibling, and takes a boot tick. A Neo4j error logs and returns null rather than throwing into the timer.
90
+ Wired at `platform/ui/server/index.ts` alongside `startTimeEntryCensus` (`:2208-2215`).
91
+
92
+ `ledger-reconcile` and the interval both call the same `reconcileLedger`, so the on-demand and standing
93
+ forms cannot drift apart.
94
+
95
+ ## Ontology
96
+
97
+ Promote to `schema-base.md` so all ten verticals are covered by one definition:
98
+
99
+ | Label | Natural key |
100
+ |---|---|
101
+ | `:InboundInvoice` | `(accountId, supplier, confirmationNumber)` |
102
+ | `:InvoiceLine` | `(accountId, invoiceId, lineId)` |
103
+ | `:InvoicePayment` | `(accountId, invoiceId, paymentId)` |
104
+ | `:Credit` | `(accountId, creditId)` |
105
+ | `:CashEntry` | `(accountId, entryId)` — new |
106
+
107
+ `:CashEntry` requires `accountId`, `entryId`, `direction` (`'in'|'out'`), `amount`, `date`, `description`.
108
+
109
+ Two derivations, not stored fields:
110
+
111
+ - **Direction of a payment comes from the parent label.** An `:InvoicePayment` under `:Invoice` is money
112
+ received; under `:InboundInvoice` it is money paid. No `direction` property on `:InvoicePayment`.
113
+ - **Outstanding is computed on read.** `outstanding`, `balance` and `amountPaid` join § Forbidden
114
+ Properties (`schema-base.md:119`) for `:Invoice` and `:InboundInvoice`.
115
+
116
+ `schema.cypher` additionally gains the `:Invoice` `(accountId, confirmationNumber)` uniqueness constraint
117
+ that base already claims as its natural key but never declared, the `:CashEntry` constraint and index, and
118
+ entries in the `entity_search_admin` fulltext union (`:465-481`).
119
+
120
+ `schema-construction.md` drops the rows now owned by base. Construction-only labels stay.
121
+
122
+ The line-item `amount` carve-out against the base `totalPaymentDue` synonym ban
123
+ (`schema-construction.md:66-70`) moves to `schema-base.md` § Property Naming Rules with the labels.
124
+
125
+ Five edge patterns join `schema-base.md` § Relationship Patterns (`:168`), beside the
126
+ `(:Invoice)-[:BILLED_TO]->` pattern already there:
127
+
128
+ ```
129
+ (:Invoice|:InboundInvoice)-[:HAS_LINE]->(:InvoiceLine)
130
+ (:Invoice|:InboundInvoice)-[:HAS_PAYMENT]->(:InvoicePayment)
131
+ (:Invoice)-[:HAS_CREDIT]->(:Credit)
132
+ (:InboundInvoice)-[:FROM_SUPPLIER]->(:Organization)
133
+ (:CashEntry)-[:COUNTERPARTY]->(:Person|Organization)
134
+ ```
135
+
136
+ **Not `TYPED_EDGE_ALLOWLIST`.** That constant is imported by exactly two consumers,
137
+ `plugins/memory/mcp/src/tools/memory-ingest.ts:25` and `scripts/generate-edge-docs.ts:20`. It governs what
138
+ the typed-edge classifier may propose from free-text document ingest. `lib/graph-write/src/index.ts` never
139
+ references it and `lib/schema-validator.ts` performs no edge-type validation, so the ledger tools' writes
140
+ are not checked against it. Adding ledger triples there would make the classifier extract ledger edges out
141
+ of ingested documents, which is not intended. The allowlist header states the rule directly: edge types
142
+ outside it "are governed by schema-base.md / vertical-schema rules".
143
+
144
+ ## Data flow — one payment
145
+
146
+ 1. Resolve the invoice by reference; capture its label and `totalPaymentDue`.
147
+ 2. Fetch existing `:InvoicePayment` and `:Credit` children.
148
+ 3. ledger-core validates the candidate.
149
+ 4. Write through `writeNodeWithEdges`, or reject.
150
+ 5. Re-read outstanding from the graph.
151
+ 6. Emit `op=verify`.
152
+
153
+ Step 5 is the point of the design. `op=write` proves intention; only the re-read proves outcome.
154
+
155
+ ## Errors
156
+
157
+ Over-application rejects, is logged, and is reported to the user verbatim. It is never clamped and never
158
+ silently recorded. An unresolvable invoice reference rejects. Neo4j failures surface as `isError` payloads,
159
+ which `wrapWithLifeline` logs as `outcome=error` (`mcp-lifeline/src/index.ts:20-26`). The census swallows
160
+ nothing but never throws into the interval.
161
+
162
+ ## Observability
163
+
164
+ Failure modes and the signal for each. Four are no-event failures owned by the census, not by a log line.
165
+
166
+ | # | Failure | Event? | Signal |
167
+ |---|---|---|---|
168
+ | 1 | Narrated as recorded, nothing written | No | `[ledger-census]` counts flat across a window containing `op=request` lines |
169
+ | 2 | Payment with no parent-invoice edge | No | `[ledger-census] orphanPayments=<N>` |
170
+ | 3 | A stored balance field reappears and drifts | No | `[ledger-census] storedBalanceProps=<N>` |
171
+ | 4 | Duplicate payment via non-deterministic `paymentId` | No | `[ledger-census] suspectDuplicates=<N>` |
172
+ | 5 | Over-application | Yes | `op=reject reason=over-application` |
173
+ | 6 | Write rejected, resolved vertical lacks the label | Yes | `op=reject reason=unknown-label vertical=<file>` |
174
+ | 7 | `schema.cypher` edited, `seed-neo4j.sh` not re-run | No | `[ledger-census] declaredNotLive=<labels>` from `live-schema-source.ts:180` |
175
+ | 8 | Tool in one registry, absent from the other | No | `[ledger-census] toolsResolved=7/7` |
176
+
177
+ Lifecycle for `ledger-payment-record`, correlated by `paymentId`. Lines 1 and 6 come free from
178
+ `wrapWithLifeline`; the rest are domain lines the tool emits.
179
+
180
+ ```
181
+ [ledger] op=request paymentId=… invoiceRef=… direction=… amount=… date=…
182
+ [ledger] op=resolve paymentId=… found=<bool> label=<Invoice|InboundInvoice> vertical=… totalPaymentDue=…
183
+ [ledger] op=arithmetic paymentId=… priorApplied=… priorCredits=… amount=… outstandingBefore=… outstandingAfter=…
184
+ [ledger] op=write paymentId=… nodesCreated=… edgesCreated=…
185
+ [ledger] op=reject paymentId=… reason=<over-application|unknown-label|no-parent> detail=…
186
+ [ledger] op=verify paymentId=… outstandingReRead=…
187
+ [ledger] op=exit paymentId=… ranMs=… outcome=<applied|rejected|error>
188
+ [ledger-census] invoices=… payments=… orphanPayments=… storedBalanceProps=… suspectDuplicates=… declaredNotLive=… toolsResolved=…/7
189
+ ```
190
+
191
+ **Success:** `op=verify` present, `outstandingReRead` equal to `op=arithmetic`'s `outstandingAfter`; census
192
+ with every counter zero and `toolsResolved=7/7`.
193
+
194
+ **Failure:** `op=request` with no matching `op=exit`; `op=write` with no `op=verify`; any non-zero census
195
+ counter; **absence of a census line** in the expected window; `op=resolve found=false` on a reference the
196
+ user just supplied.
197
+
198
+ **Diagnostic path:** the plugin MCP tee log, `grep -E '\[ledger\]|\[ledger-census\]'`, then filter by one
199
+ `paymentId` for that operation's full lifeline.
200
+
201
+ ## Registration
202
+
203
+ - `platform/plugins/ledger/PLUGIN.md` frontmatter is the source of truth for tool names.
204
+ - `cd platform && npm run gen:canonical-tools` regenerates
205
+ `services/claude-session-manager/src/canonical-tool-names.generated.ts`.
206
+ `platform/scripts/check-canonical-tool-names.mjs` fails the build on drift.
207
+ - `plugins.core[]` in all four brand JSONs: `maxy-code`, `realagent-code`, `sitedesk-code`,
208
+ `property-administrators`. A plugin absent from a brand's list does not ship to that brand.
209
+ - Specialist templates `database-operator.md` and `project-manager.md`.
210
+ - `platform/package.json` build chain gains `lib/ledger-core`. The `plugins/*/mcp` glob picks the plugin up
211
+ automatically.
212
+
213
+ ## Testing
214
+
215
+ - **ledger-core** — pure unit tests, no infrastructure. Part payment leaves the exact remainder; two
216
+ payments summing to the total leave zero; a credit reduces outstanding identically to a payment; an
217
+ over-application is rejected with the remaining figure; a zero-total invoice accepts no payment; the same
218
+ `paymentId` twice does not double-count; ageing buckets at the 30/60/90 boundaries.
219
+ - **Tools** — stubbed session; asserts the reject branch writes nothing and that `op=verify` re-reads.
220
+ - **Per-vertical coverage** — iterates all ten `businessType` values resolved by
221
+ `resolve-active-vertical.ts:38,67-81` including the `trades → schema-construction` alias, validating an
222
+ `:Invoice` + `:InvoicePayment` + `:CashEntry` write against each. Fails before the promotion, passes
223
+ after. This is the test that would have caught the reported defect.
224
+ - **Census** — pure `reconcileLedger` cases for each non-zero counter.
225
+ - **Drift gate** — `schema-cypher-drift.test.ts:60` green with no new `PROSE_ONLY_LABELS` entries.
226
+
227
+ Baseline in this worktree before any change: 508 vitest tests pass across the memory plugin, 56 node:test
228
+ tests pass. Three suites report as failed under vitest because they are `node:test` files vitest cannot
229
+ collect. That is pre-existing and unrelated.
230
+
231
+ ## Regression boundary
232
+
233
+ Existing construction-vertical tests unchanged. `resolve-active-vertical.test.ts` cases at `:69,:82,:95,:108`
234
+ unchanged. `account-schema-owned-dirs.test.sh` confirms no new account file-schema bucket is minted, since
235
+ `schema-base.md` has no `## Top-level node types` section. The SiteDesk `:Job -> :Quote -> :QuoteLine`
236
+ reification is untouched.
237
+
238
+ ## Out of scope
239
+
240
+ Double-entry. QuickBooks-to-graph mirroring. VAT, tax and multi-currency. Bank feed import. Any UI.
241
+ SiteDesk quoting figures. Backfill of existing construction accounts.
@@ -0,0 +1,14 @@
1
+ export interface OpenInvoice {
2
+ ref: string;
3
+ dueDateMs: number;
4
+ outstanding: number;
5
+ }
6
+ export interface AgeBuckets {
7
+ current: number;
8
+ d31to60: number;
9
+ d61to90: number;
10
+ d90plus: number;
11
+ total: number;
12
+ }
13
+ export declare function ageBuckets(invoices: readonly OpenInvoice[], now: number): AgeBuckets;
14
+ //# sourceMappingURL=ageing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ageing.d.ts","sourceRoot":"","sources":["../src/ageing.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;CACd;AAED,wBAAgB,UAAU,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,UAAU,CAsBpF"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Task 1771 — receivables and payables ageing.
3
+ *
4
+ * Buckets are inclusive-lower on whole days past the due date: 0-30 is
5
+ * `current`, 31-60, 61-90, and 91 and above is `d90plus`. An invoice not yet
6
+ * due has a negative age, which clamps to 0 and lands in `current`.
7
+ *
8
+ * Every bucket is a summed money amount, not a count of invoices. Summing is
9
+ * done in minor units so a statement of many decimal amounts does not drift.
10
+ */
11
+ import { toMinor, fromMinor } from './money.js';
12
+ const DAY_MS = 86_400_000;
13
+ export function ageBuckets(invoices, now) {
14
+ let current = 0;
15
+ let d31to60 = 0;
16
+ let d61to90 = 0;
17
+ let d90plus = 0;
18
+ for (const inv of invoices) {
19
+ const ageDays = Math.max(0, Math.floor((now - inv.dueDateMs) / DAY_MS));
20
+ const minor = toMinor(inv.outstanding);
21
+ if (ageDays <= 30)
22
+ current += minor;
23
+ else if (ageDays <= 60)
24
+ d31to60 += minor;
25
+ else if (ageDays <= 90)
26
+ d61to90 += minor;
27
+ else
28
+ d90plus += minor;
29
+ }
30
+ return {
31
+ current: fromMinor(current),
32
+ d31to60: fromMinor(d31to60),
33
+ d61to90: fromMinor(d61to90),
34
+ d90plus: fromMinor(d90plus),
35
+ total: fromMinor(current + d31to60 + d61to90 + d90plus),
36
+ };
37
+ }
38
+ //# sourceMappingURL=ageing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ageing.js","sourceRoot":"","sources":["../src/ageing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAE/C,MAAM,MAAM,GAAG,UAAU,CAAA;AAgBzB,MAAM,UAAU,UAAU,CAAC,QAAgC,EAAE,GAAW;IACtE,IAAI,OAAO,GAAG,CAAC,CAAA;IACf,IAAI,OAAO,GAAG,CAAC,CAAA;IACf,IAAI,OAAO,GAAG,CAAC,CAAA;IACf,IAAI,OAAO,GAAG,CAAC,CAAA;IAEf,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC3B,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC,SAAS,CAAC,GAAG,MAAM,CAAC,CAAC,CAAA;QACvE,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;QACtC,IAAI,OAAO,IAAI,EAAE;YAAE,OAAO,IAAI,KAAK,CAAA;aAC9B,IAAI,OAAO,IAAI,EAAE;YAAE,OAAO,IAAI,KAAK,CAAA;aACnC,IAAI,OAAO,IAAI,EAAE;YAAE,OAAO,IAAI,KAAK,CAAA;;YACnC,OAAO,IAAI,KAAK,CAAA;IACvB,CAAC;IAED,OAAO;QACL,OAAO,EAAE,SAAS,CAAC,OAAO,CAAC;QAC3B,OAAO,EAAE,SAAS,CAAC,OAAO,CAAC;QAC3B,OAAO,EAAE,SAAS,CAAC,OAAO,CAAC;QAC3B,OAAO,EAAE,SAAS,CAAC,OAAO,CAAC;QAC3B,KAAK,EAAE,SAAS,CAAC,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;KACxD,CAAA;AACH,CAAC"}
@@ -0,0 +1,25 @@
1
+ export type ApplyResult = {
2
+ accepted: true;
3
+ outstandingAfter: number;
4
+ } | {
5
+ accepted: false;
6
+ reason: 'over-application';
7
+ remaining: number;
8
+ };
9
+ /**
10
+ * outstanding = totalPaymentDue - sum(payments) - sum(credits).
11
+ *
12
+ * A credit reduces what is owed exactly as a payment does; the two differ in
13
+ * provenance, not arithmetic, which is why they are summed together here.
14
+ */
15
+ export declare function computeOutstanding(totalPaymentDue: number, payments: readonly number[], credits: readonly number[]): number;
16
+ /**
17
+ * Validates one candidate payment or credit against what is still outstanding.
18
+ *
19
+ * Never clamps. A candidate that would take applied-to-date above the invoice
20
+ * total is rejected outright and the caller reports it, rather than silently
21
+ * recording a figure that does not reconcile. A non-positive candidate is
22
+ * rejected for the same reason: it is not a payment.
23
+ */
24
+ export declare function applyPayment(totalPaymentDue: number, payments: readonly number[], credits: readonly number[], candidate: number): ApplyResult;
25
+ //# sourceMappingURL=balance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"balance.d.ts","sourceRoot":"","sources":["../src/balance.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,WAAW,GACnB;IAAE,QAAQ,EAAE,IAAI,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAA;CAAE,GAC5C;IAAE,QAAQ,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,kBAAkB,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAA;AAEtE;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,eAAe,EAAE,MAAM,EACvB,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,OAAO,EAAE,SAAS,MAAM,EAAE,GACzB,MAAM,CAGR;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAC1B,eAAe,EAAE,MAAM,EACvB,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,SAAS,EAAE,MAAM,GAChB,WAAW,CASb"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Task 1771 — the outstanding invariant, defined exactly once.
3
+ *
4
+ * Nothing stores a balance. `outstanding`, `balance` and `amountPaid` are
5
+ * forbidden properties on :Invoice and :InboundInvoice (schema-base.md
6
+ * § Forbidden Properties), because a stored figure drifts silently the moment
7
+ * a payment lands by any other path. Every consumer computes from this module.
8
+ */
9
+ import { toMinor, fromMinor } from './money.js';
10
+ /**
11
+ * outstanding = totalPaymentDue - sum(payments) - sum(credits).
12
+ *
13
+ * A credit reduces what is owed exactly as a payment does; the two differ in
14
+ * provenance, not arithmetic, which is why they are summed together here.
15
+ */
16
+ export function computeOutstanding(totalPaymentDue, payments, credits) {
17
+ const applied = [...payments, ...credits].reduce((acc, a) => acc + toMinor(a), 0);
18
+ return fromMinor(toMinor(totalPaymentDue) - applied);
19
+ }
20
+ /**
21
+ * Validates one candidate payment or credit against what is still outstanding.
22
+ *
23
+ * Never clamps. A candidate that would take applied-to-date above the invoice
24
+ * total is rejected outright and the caller reports it, rather than silently
25
+ * recording a figure that does not reconcile. A non-positive candidate is
26
+ * rejected for the same reason: it is not a payment.
27
+ */
28
+ export function applyPayment(totalPaymentDue, payments, credits, candidate) {
29
+ const remaining = computeOutstanding(totalPaymentDue, payments, credits);
30
+ if (toMinor(candidate) <= 0) {
31
+ return { accepted: false, reason: 'over-application', remaining };
32
+ }
33
+ if (toMinor(candidate) > toMinor(remaining)) {
34
+ return { accepted: false, reason: 'over-application', remaining };
35
+ }
36
+ return { accepted: true, outstandingAfter: fromMinor(toMinor(remaining) - toMinor(candidate)) };
37
+ }
38
+ //# sourceMappingURL=balance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"balance.js","sourceRoot":"","sources":["../src/balance.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAM/C;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAChC,eAAuB,EACvB,QAA2B,EAC3B,OAA0B;IAE1B,MAAM,OAAO,GAAG,CAAC,GAAG,QAAQ,EAAE,GAAG,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IACjF,OAAO,SAAS,CAAC,OAAO,CAAC,eAAe,CAAC,GAAG,OAAO,CAAC,CAAA;AACtD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAC1B,eAAuB,EACvB,QAA2B,EAC3B,OAA0B,EAC1B,SAAiB;IAEjB,MAAM,SAAS,GAAG,kBAAkB,CAAC,eAAe,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAA;IACxE,IAAI,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;QAC5B,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,kBAAkB,EAAE,SAAS,EAAE,CAAA;IACnE,CAAC;IACD,IAAI,OAAO,CAAC,SAAS,CAAC,GAAG,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;QAC5C,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,kBAAkB,EAAE,SAAS,EAAE,CAAA;IACnE,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,gBAAgB,EAAE,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,CAAA;AACjG,CAAC"}
@@ -0,0 +1,5 @@
1
+ export * from './money.js';
2
+ export * from './balance.js';
3
+ export * from './ageing.js';
4
+ export * from './reconcile.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA"}
@@ -0,0 +1,5 @@
1
+ export * from './money.js';
2
+ export * from './balance.js';
3
+ export * from './ageing.js';
4
+ export * from './reconcile.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Task 1771 — money is compared and summed in integer minor units so the
3
+ * outstanding invariant is exact and never subject to float drift. Graph
4
+ * storage is unchanged: `amount` and `totalPaymentDue` stay decimal numbers,
5
+ * matching the existing JobLogic-exact construction rows. Conversion happens
6
+ * here, at the boundary, so no stored value changes shape.
7
+ */
8
+ /** Decimal major units to integer minor units. */
9
+ export declare function toMinor(n: number): number;
10
+ /** Integer minor units back to decimal major units. */
11
+ export declare function fromMinor(n: number): number;
12
+ //# sourceMappingURL=money.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"money.d.ts","sourceRoot":"","sources":["../src/money.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,kDAAkD;AAClD,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEzC;AAED,uDAAuD;AACvD,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE3C"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Task 1771 — money is compared and summed in integer minor units so the
3
+ * outstanding invariant is exact and never subject to float drift. Graph
4
+ * storage is unchanged: `amount` and `totalPaymentDue` stay decimal numbers,
5
+ * matching the existing JobLogic-exact construction rows. Conversion happens
6
+ * here, at the boundary, so no stored value changes shape.
7
+ */
8
+ /** Decimal major units to integer minor units. */
9
+ export function toMinor(n) {
10
+ return Math.round(n * 100);
11
+ }
12
+ /** Integer minor units back to decimal major units. */
13
+ export function fromMinor(n) {
14
+ return n / 100;
15
+ }
16
+ //# sourceMappingURL=money.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"money.js","sourceRoot":"","sources":["../src/money.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,kDAAkD;AAClD,MAAM,UAAU,OAAO,CAAC,CAAS;IAC/B,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,CAAC,CAAA;AAC5B,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,SAAS,CAAC,CAAS;IACjC,OAAO,CAAC,GAAG,GAAG,CAAA;AAChB,CAAC"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Task 1771 — the ledger census finding.
3
+ *
4
+ * Four of the ledger's failure modes emit no event of their own: a write that
5
+ * was narrated but never landed, a payment with no parent invoice, a stored
6
+ * balance property that has drifted, and a duplicate payment created by a
7
+ * non-deterministic paymentId. None of them can be caught by logging an
8
+ * action, because no action occurs. This reconcile is the substitute, and it
9
+ * is called from two places: the `ledger-reconcile` tool (on demand) and the
10
+ * standing interval in the UI server. Both share this function so the two
11
+ * forms cannot report different things.
12
+ *
13
+ * Pure: no I/O, and `now` is a parameter rather than a Date.now() call.
14
+ */
15
+ /** The index names schema.cypher declares for the ledger. Their absence from a
16
+ * live database means schema.cypher was edited but seed-neo4j.sh never re-ran
17
+ * on that install, which emits nothing on its own. Defined here because both
18
+ * the on-demand tool and the standing census check the same list; two copies
19
+ * would let them disagree about whether the schema is applied. */
20
+ export declare const LEDGER_INDEX_NAMES: readonly string[];
21
+ export interface PaymentKey {
22
+ invoiceId: string;
23
+ paymentId: string;
24
+ amount: number;
25
+ date: string;
26
+ }
27
+ export interface LedgerCensusRows {
28
+ invoices: number;
29
+ payments: number;
30
+ orphanPayments: number;
31
+ storedBalanceProps: number;
32
+ paymentKeys: readonly PaymentKey[];
33
+ declaredNotLive: readonly string[];
34
+ }
35
+ export interface LedgerCensusFinding {
36
+ invoices: number;
37
+ payments: number;
38
+ orphanPayments: number;
39
+ storedBalanceProps: number;
40
+ suspectDuplicates: number;
41
+ declaredNotLive: readonly string[];
42
+ alarm: boolean;
43
+ }
44
+ export declare function reconcileLedger(rows: LedgerCensusRows, _now: number): LedgerCensusFinding;
45
+ /**
46
+ * The one `[ledger-census]` line format. Shared by the on-demand tool and the
47
+ * standing interval so a log search finds both, and so a new counter cannot be
48
+ * added to one emission and missed by the other.
49
+ */
50
+ export declare function formatCensusLine(f: LedgerCensusFinding): string;
51
+ //# sourceMappingURL=reconcile.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Task 1771 — the ledger census finding.
3
+ *
4
+ * Four of the ledger's failure modes emit no event of their own: a write that
5
+ * was narrated but never landed, a payment with no parent invoice, a stored
6
+ * balance property that has drifted, and a duplicate payment created by a
7
+ * non-deterministic paymentId. None of them can be caught by logging an
8
+ * action, because no action occurs. This reconcile is the substitute, and it
9
+ * is called from two places: the `ledger-reconcile` tool (on demand) and the
10
+ * standing interval in the UI server. Both share this function so the two
11
+ * forms cannot report different things.
12
+ *
13
+ * Pure: no I/O, and `now` is a parameter rather than a Date.now() call.
14
+ */
15
+ /** The index names schema.cypher declares for the ledger. Their absence from a
16
+ * live database means schema.cypher was edited but seed-neo4j.sh never re-ran
17
+ * on that install, which emits nothing on its own. Defined here because both
18
+ * the on-demand tool and the standing census check the same list; two copies
19
+ * would let them disagree about whether the schema is applied. */
20
+ export const LEDGER_INDEX_NAMES = [
21
+ "invoice_account_confirmation_unique",
22
+ "cash_entry_account_entryid_unique",
23
+ "cash_entry_account",
24
+ "invoice_line_account",
25
+ "invoice_payment_account",
26
+ "credit_account",
27
+ ];
28
+ /**
29
+ * Counts payments that share (invoiceId, amount, date) across two or more
30
+ * distinct paymentId values. For a group of n distinct ids the contribution is
31
+ * n-1, because exactly one member of the group is the legitimate payment.
32
+ *
33
+ * Grouping on distinct ids, not on row count, is what keeps a genuine retry
34
+ * quiet: a retry that MERGEs onto the same deterministic paymentId appears
35
+ * twice in the rows but once in the id set, and is not a duplicate.
36
+ */
37
+ function countSuspectDuplicates(keys) {
38
+ const groups = new Map();
39
+ for (const k of keys) {
40
+ const composite = `${k.invoiceId}|${k.amount}|${k.date}`;
41
+ const ids = groups.get(composite) ?? new Set();
42
+ ids.add(k.paymentId);
43
+ groups.set(composite, ids);
44
+ }
45
+ let total = 0;
46
+ for (const ids of groups.values())
47
+ total += Math.max(0, ids.size - 1);
48
+ return total;
49
+ }
50
+ export function reconcileLedger(rows, _now) {
51
+ const suspectDuplicates = countSuspectDuplicates(rows.paymentKeys);
52
+ const alarm = rows.orphanPayments > 0 ||
53
+ rows.storedBalanceProps > 0 ||
54
+ suspectDuplicates > 0 ||
55
+ rows.declaredNotLive.length > 0;
56
+ return {
57
+ invoices: rows.invoices,
58
+ payments: rows.payments,
59
+ orphanPayments: rows.orphanPayments,
60
+ storedBalanceProps: rows.storedBalanceProps,
61
+ suspectDuplicates,
62
+ declaredNotLive: rows.declaredNotLive,
63
+ alarm,
64
+ };
65
+ }
66
+ /**
67
+ * The one `[ledger-census]` line format. Shared by the on-demand tool and the
68
+ * standing interval so a log search finds both, and so a new counter cannot be
69
+ * added to one emission and missed by the other.
70
+ */
71
+ export function formatCensusLine(f) {
72
+ return (`[ledger-census] invoices=${f.invoices} payments=${f.payments} ` +
73
+ `orphanPayments=${f.orphanPayments} storedBalanceProps=${f.storedBalanceProps} ` +
74
+ `suspectDuplicates=${f.suspectDuplicates} ` +
75
+ `declaredNotLive=${f.declaredNotLive.length === 0 ? "none" : f.declaredNotLive.join(",")}`);
76
+ }
77
+ //# sourceMappingURL=reconcile.js.map
@@ -0,0 +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"}
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "@maxy/ledger-core",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "test": "vitest run"
10
+ },
11
+ "devDependencies": {
12
+ "@types/node": "^22.0.0",
13
+ "typescript": "^5.7.0",
14
+ "vitest": "^4.1.2"
15
+ }
16
+ }
@@ -0,0 +1,42 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { ageBuckets } from '../ageing.js'
3
+
4
+ const DAY = 86_400_000
5
+ const NOW = Date.parse('2026-07-18T00:00:00Z')
6
+ const inv = (ref: string, ageDays: number, outstanding: number) => ({
7
+ ref,
8
+ dueDateMs: NOW - ageDays * DAY,
9
+ outstanding,
10
+ })
11
+
12
+ describe('ageBuckets', () => {
13
+ it('sums money, not invoice counts', () => {
14
+ const b = ageBuckets([inv('a', 5, 100), inv('b', 10, 250)], NOW)
15
+ expect(b.current).toBe(350)
16
+ expect(b.total).toBe(350)
17
+ })
18
+ it('places a not-yet-due invoice in current', () => {
19
+ expect(ageBuckets([inv('a', -10, 100)], NOW).current).toBe(100)
20
+ })
21
+ it('puts each boundary day in the lower bucket', () => {
22
+ expect(ageBuckets([inv('a', 30, 1)], NOW).current).toBe(1)
23
+ expect(ageBuckets([inv('a', 31, 1)], NOW).d31to60).toBe(1)
24
+ expect(ageBuckets([inv('a', 60, 1)], NOW).d31to60).toBe(1)
25
+ expect(ageBuckets([inv('a', 61, 1)], NOW).d61to90).toBe(1)
26
+ expect(ageBuckets([inv('a', 90, 1)], NOW).d61to90).toBe(1)
27
+ expect(ageBuckets([inv('a', 91, 1)], NOW).d90plus).toBe(1)
28
+ })
29
+ it('returns all zeroes for no invoices', () => {
30
+ expect(ageBuckets([], NOW)).toEqual({
31
+ current: 0,
32
+ d31to60: 0,
33
+ d61to90: 0,
34
+ d90plus: 0,
35
+ total: 0,
36
+ })
37
+ })
38
+ it('does not drift when summing many decimal amounts', () => {
39
+ const rows = Array.from({ length: 3 }, (_, i) => inv(`r${i}`, 1, 33.33))
40
+ expect(ageBuckets(rows, NOW).total).toBe(99.99)
41
+ })
42
+ })