@rubytech/create-maxy-code 0.1.469 → 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.
- package/dist/index.js +40 -0
- package/package.json +1 -1
- package/payload/platform/config/brand.json +2 -1
- package/payload/platform/docs/superpowers/plans/2026-07-18-graph-native-ledger.md +807 -0
- package/payload/platform/docs/superpowers/specs/2026-07-18-graph-native-ledger-design.md +241 -0
- package/payload/platform/lib/ledger-core/dist/ageing.d.ts +14 -0
- package/payload/platform/lib/ledger-core/dist/ageing.d.ts.map +1 -0
- package/payload/platform/lib/ledger-core/dist/ageing.js +38 -0
- package/payload/platform/lib/ledger-core/dist/ageing.js.map +1 -0
- package/payload/platform/lib/ledger-core/dist/balance.d.ts +25 -0
- package/payload/platform/lib/ledger-core/dist/balance.d.ts.map +1 -0
- package/payload/platform/lib/ledger-core/dist/balance.js +38 -0
- package/payload/platform/lib/ledger-core/dist/balance.js.map +1 -0
- package/payload/platform/lib/ledger-core/dist/index.d.ts +5 -0
- package/payload/platform/lib/ledger-core/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/ledger-core/dist/index.js +5 -0
- package/payload/platform/lib/ledger-core/dist/index.js.map +1 -0
- package/payload/platform/lib/ledger-core/dist/money.d.ts +12 -0
- package/payload/platform/lib/ledger-core/dist/money.d.ts.map +1 -0
- package/payload/platform/lib/ledger-core/dist/money.js +16 -0
- package/payload/platform/lib/ledger-core/dist/money.js.map +1 -0
- package/payload/platform/lib/ledger-core/dist/reconcile.d.ts +51 -0
- package/payload/platform/lib/ledger-core/dist/reconcile.d.ts.map +1 -0
- package/payload/platform/lib/ledger-core/dist/reconcile.js +77 -0
- package/payload/platform/lib/ledger-core/dist/reconcile.js.map +1 -0
- package/payload/platform/lib/ledger-core/package.json +16 -0
- package/payload/platform/lib/ledger-core/src/__tests__/ageing.test.ts +42 -0
- package/payload/platform/lib/ledger-core/src/__tests__/balance.test.ts +48 -0
- package/payload/platform/lib/ledger-core/src/__tests__/reconcile.test.ts +87 -0
- package/payload/platform/lib/ledger-core/src/ageing.ts +51 -0
- package/payload/platform/lib/ledger-core/src/balance.ts +52 -0
- package/payload/platform/lib/ledger-core/src/index.ts +4 -0
- package/payload/platform/lib/ledger-core/src/money.ts +17 -0
- package/payload/platform/lib/ledger-core/src/reconcile.ts +109 -0
- package/payload/platform/lib/ledger-core/tsconfig.json +6 -0
- package/payload/platform/neo4j/schema.cypher +16 -0
- package/payload/platform/package.json +2 -2
- package/payload/platform/plugins/.claude-plugin/marketplace.json +10 -0
- package/payload/platform/plugins/admin/hooks/lib/maxy-mcp-plugins.txt +2 -0
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +171 -2
- package/payload/platform/plugins/cloudflare/bin/__tests__/portal-brand-css.test.sh +139 -0
- package/payload/platform/plugins/cloudflare/bin/portal-brand-css.mjs +128 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-enrol.test.ts +1 -1
- package/payload/platform/plugins/cloudflare/mcp/__tests__/skill-contract.test.ts +25 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/template-config.test.ts +55 -3
- package/payload/platform/plugins/cloudflare/skills/data-portal/SKILL.md +38 -3
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/{public/portal.css → portal.css} +13 -2
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/wrangler.toml +1 -1
- package/payload/platform/plugins/docs/references/admin-ui.md +48 -0
- package/payload/platform/plugins/docs/references/google-guide.md +68 -0
- package/payload/platform/plugins/docs/references/internals.md +1 -1
- package/payload/platform/plugins/docs/references/ledger.md +43 -0
- package/payload/platform/plugins/email/PLUGIN.md +4 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/display-truncation.test.d.ts +2 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/display-truncation.test.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/display-truncation.test.js +196 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/display-truncation.test.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/source-fetch-efficiency.test.js +10 -8
- package/payload/platform/plugins/email/mcp/dist/__tests__/source-fetch-efficiency.test.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/__tests__/truncation-tool-output.test.d.ts +2 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/truncation-tool-output.test.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/truncation-tool-output.test.js +110 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/truncation-tool-output.test.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts +22 -0
- package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/imap.js +90 -36
- package/payload/platform/plugins/email/mcp/dist/lib/imap.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-read.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-read.js +6 -2
- package/payload/platform/plugins/email/mcp/dist/tools/email-read.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-search.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-search.js +6 -2
- package/payload/platform/plugins/email/mcp/dist/tools/email-search.js.map +1 -1
- package/payload/platform/plugins/email/skills/email-ingest/SKILL.md +4 -0
- package/payload/platform/plugins/google/.claude-plugin/plugin.json +21 -0
- package/payload/platform/plugins/google/PLUGIN.md +104 -0
- package/payload/platform/plugins/google/lib/mcp-spawn-tee/index.js +193 -0
- package/payload/platform/plugins/google/lib/mcp-spawn-tee/package.json +3 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/account-register.test.d.ts +2 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/account-register.test.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/account-register.test.js +60 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/account-register.test.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/calendar-tools.test.d.ts +2 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/calendar-tools.test.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/calendar-tools.test.js +48 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/calendar-tools.test.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/google-client.test.d.ts +2 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/google-client.test.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/google-client.test.js +25 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/google-client.test.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/index-registration.test.d.ts +2 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/index-registration.test.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/index-registration.test.js +40 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/index-registration.test.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/pending-store.test.d.ts +2 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/pending-store.test.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/pending-store.test.js +35 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/pending-store.test.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/token-store.test.d.ts +2 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/token-store.test.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/token-store.test.js +85 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/token-store.test.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/auth/pending-store.d.ts +25 -0
- package/payload/platform/plugins/google/mcp/dist/auth/pending-store.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/auth/pending-store.js +87 -0
- package/payload/platform/plugins/google/mcp/dist/auth/pending-store.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/auth/token-store.d.ts +46 -0
- package/payload/platform/plugins/google/mcp/dist/auth/token-store.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/auth/token-store.js +138 -0
- package/payload/platform/plugins/google/mcp/dist/auth/token-store.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/index.d.ts +25 -0
- package/payload/platform/plugins/google/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/index.js +300 -0
- package/payload/platform/plugins/google/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/lib/google-client.d.ts +28 -0
- package/payload/platform/plugins/google/mcp/dist/lib/google-client.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/lib/google-client.js +102 -0
- package/payload/platform/plugins/google/mcp/dist/lib/google-client.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/lib/log.d.ts +3 -0
- package/payload/platform/plugins/google/mcp/dist/lib/log.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/lib/log.js +16 -0
- package/payload/platform/plugins/google/mcp/dist/lib/log.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/tools/account-info.d.ts +12 -0
- package/payload/platform/plugins/google/mcp/dist/tools/account-info.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/tools/account-info.js +23 -0
- package/payload/platform/plugins/google/mcp/dist/tools/account-info.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/tools/account-list.d.ts +12 -0
- package/payload/platform/plugins/google/mcp/dist/tools/account-list.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/tools/account-list.js +9 -0
- package/payload/platform/plugins/google/mcp/dist/tools/account-list.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/tools/account-register.d.ts +42 -0
- package/payload/platform/plugins/google/mcp/dist/tools/account-register.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/tools/account-register.js +102 -0
- package/payload/platform/plugins/google/mcp/dist/tools/account-register.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/tools/calendar-cancel.d.ts +10 -0
- package/payload/platform/plugins/google/mcp/dist/tools/calendar-cancel.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/tools/calendar-cancel.js +9 -0
- package/payload/platform/plugins/google/mcp/dist/tools/calendar-cancel.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/tools/calendar-create.d.ts +20 -0
- package/payload/platform/plugins/google/mcp/dist/tools/calendar-create.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/tools/calendar-create.js +25 -0
- package/payload/platform/plugins/google/mcp/dist/tools/calendar-create.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/tools/calendar-event.d.ts +6 -0
- package/payload/platform/plugins/google/mcp/dist/tools/calendar-event.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/tools/calendar-event.js +7 -0
- package/payload/platform/plugins/google/mcp/dist/tools/calendar-event.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/tools/calendar-freebusy.d.ts +14 -0
- package/payload/platform/plugins/google/mcp/dist/tools/calendar-freebusy.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/tools/calendar-freebusy.js +16 -0
- package/payload/platform/plugins/google/mcp/dist/tools/calendar-freebusy.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/tools/calendar-list.d.ts +44 -0
- package/payload/platform/plugins/google/mcp/dist/tools/calendar-list.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/tools/calendar-list.js +36 -0
- package/payload/platform/plugins/google/mcp/dist/tools/calendar-list.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/tools/calendar-respond.d.ts +16 -0
- package/payload/platform/plugins/google/mcp/dist/tools/calendar-respond.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/tools/calendar-respond.js +29 -0
- package/payload/platform/plugins/google/mcp/dist/tools/calendar-respond.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/tools/calendar-update.d.ts +16 -0
- package/payload/platform/plugins/google/mcp/dist/tools/calendar-update.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/tools/calendar-update.js +32 -0
- package/payload/platform/plugins/google/mcp/dist/tools/calendar-update.js.map +1 -0
- package/payload/platform/plugins/google/mcp/package.json +20 -0
- package/payload/platform/plugins/google/references/auth.md +35 -0
- package/payload/platform/plugins/ledger/.claude-plugin/plugin.json +21 -0
- package/payload/platform/plugins/ledger/PLUGIN.md +82 -0
- package/payload/platform/plugins/ledger/lib/mcp-spawn-tee/index.js +193 -0
- package/payload/platform/plugins/ledger/lib/mcp-spawn-tee/package.json +3 -0
- package/payload/platform/plugins/ledger/mcp/dist/index.d.ts +2 -0
- package/payload/platform/plugins/ledger/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/ledger/mcp/dist/index.js +168 -0
- package/payload/platform/plugins/ledger/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/ledger/mcp/dist/lib/ids.d.ts +4 -0
- package/payload/platform/plugins/ledger/mcp/dist/lib/ids.d.ts.map +1 -0
- package/payload/platform/plugins/ledger/mcp/dist/lib/ids.js +0 -0
- package/payload/platform/plugins/ledger/mcp/dist/lib/ids.js.map +1 -0
- package/payload/platform/plugins/ledger/mcp/dist/lib/neo4j.d.ts +5 -0
- package/payload/platform/plugins/ledger/mcp/dist/lib/neo4j.d.ts.map +1 -0
- package/payload/platform/plugins/ledger/mcp/dist/lib/neo4j.js +40 -0
- package/payload/platform/plugins/ledger/mcp/dist/lib/neo4j.js.map +1 -0
- package/payload/platform/plugins/ledger/mcp/dist/lib/read.d.ts +29 -0
- package/payload/platform/plugins/ledger/mcp/dist/lib/read.d.ts.map +1 -0
- package/payload/platform/plugins/ledger/mcp/dist/lib/read.js +138 -0
- package/payload/platform/plugins/ledger/mcp/dist/lib/read.js.map +1 -0
- package/payload/platform/plugins/ledger/mcp/dist/lib/types.d.ts +47 -0
- package/payload/platform/plugins/ledger/mcp/dist/lib/types.d.ts.map +1 -0
- package/payload/platform/plugins/ledger/mcp/dist/lib/types.js +14 -0
- package/payload/platform/plugins/ledger/mcp/dist/lib/types.js.map +1 -0
- package/payload/platform/plugins/ledger/mcp/dist/tools/balance.d.ts +18 -0
- package/payload/platform/plugins/ledger/mcp/dist/tools/balance.d.ts.map +1 -0
- package/payload/platform/plugins/ledger/mcp/dist/tools/balance.js +23 -0
- package/payload/platform/plugins/ledger/mcp/dist/tools/balance.js.map +1 -0
- package/payload/platform/plugins/ledger/mcp/dist/tools/cash-record.d.ts +26 -0
- package/payload/platform/plugins/ledger/mcp/dist/tools/cash-record.d.ts.map +1 -0
- package/payload/platform/plugins/ledger/mcp/dist/tools/cash-record.js +33 -0
- package/payload/platform/plugins/ledger/mcp/dist/tools/cash-record.js.map +1 -0
- package/payload/platform/plugins/ledger/mcp/dist/tools/invoice-record.d.ts +27 -0
- package/payload/platform/plugins/ledger/mcp/dist/tools/invoice-record.d.ts.map +1 -0
- package/payload/platform/plugins/ledger/mcp/dist/tools/invoice-record.js +40 -0
- package/payload/platform/plugins/ledger/mcp/dist/tools/invoice-record.js.map +1 -0
- package/payload/platform/plugins/ledger/mcp/dist/tools/payment-record.d.ts +46 -0
- package/payload/platform/plugins/ledger/mcp/dist/tools/payment-record.d.ts.map +1 -0
- package/payload/platform/plugins/ledger/mcp/dist/tools/payment-record.js +68 -0
- package/payload/platform/plugins/ledger/mcp/dist/tools/payment-record.js.map +1 -0
- package/payload/platform/plugins/ledger/mcp/dist/tools/reconcile.d.ts +20 -0
- package/payload/platform/plugins/ledger/mcp/dist/tools/reconcile.d.ts.map +1 -0
- package/payload/platform/plugins/ledger/mcp/dist/tools/reconcile.js +30 -0
- package/payload/platform/plugins/ledger/mcp/dist/tools/reconcile.js.map +1 -0
- package/payload/platform/plugins/ledger/mcp/dist/tools/statement.d.ts +26 -0
- package/payload/platform/plugins/ledger/mcp/dist/tools/statement.d.ts.map +1 -0
- package/payload/platform/plugins/ledger/mcp/dist/tools/statement.js +35 -0
- package/payload/platform/plugins/ledger/mcp/dist/tools/statement.js.map +1 -0
- package/payload/platform/plugins/ledger/mcp/package.json +21 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/ledger-vertical-coverage.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/ledger-vertical-coverage.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/ledger-vertical-coverage.test.js +81 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/ledger-vertical-coverage.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.d.ts +9 -7
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.js +10 -8
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.js.map +1 -1
- package/payload/platform/plugins/memory/references/schema-base.md +22 -0
- package/payload/platform/plugins/memory/references/schema-construction.md +0 -18
- package/payload/platform/plugins/scheduling/PLUGIN.md +20 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/meeting-write.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/meeting-write.js +11 -2
- package/payload/platform/plugins/scheduling/mcp/dist/lib/meeting-write.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js.map +1 -1
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-create.js +3 -3
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-create.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/activity-rows.d.ts +138 -0
- package/payload/platform/services/claude-session-manager/dist/activity-rows.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/activity-rows.js +268 -0
- package/payload/platform/services/claude-session-manager/dist/activity-rows.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +20 -0
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts +6 -0
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +42 -0
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/index.js +10 -1
- package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-census.d.ts +80 -6
- package/payload/platform/services/claude-session-manager/dist/pty-census.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-census.js +96 -20
- package/payload/platform/services/claude-session-manager/dist/pty-census.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.js +5 -1
- package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.js.map +1 -1
- package/payload/platform/templates/specialists/agents/database-operator.md +1 -1
- package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
- package/payload/platform/templates/specialists/agents/project-manager.md +1 -1
- package/payload/server/{chunk-POBXIHOF.js → chunk-BSTIQX52.js} +59 -1
- package/payload/server/maxy-edge.js +1 -1
- package/payload/server/public/activity.html +17 -0
- package/payload/server/public/assets/AdminLoginScreens-Rf94VD9w.js +1 -0
- package/payload/server/public/assets/AdminShell-CyGKg7MG.js +2 -0
- package/payload/server/public/assets/Checkbox-B0jmI2y2.js +1 -0
- package/payload/server/public/assets/HeaderMenu-BnTYGNaB.js +9 -0
- package/payload/server/public/assets/{useSubAccountSwitcher-pu0jjLCz.css → HeaderMenu-DtIEBHOU.css} +1 -1
- package/payload/server/public/assets/activity-BOBTWFr-.js +1 -0
- package/payload/server/public/assets/admin-HDLzwbLl.js +1 -0
- package/payload/server/public/assets/{arc-qKfXIt8v.js → arc-CFMJyMm-.js} +1 -1
- package/payload/server/public/assets/architecture-YZFGNWBL-Cypt5qHL.js +1 -0
- package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-DkVrGn7G.js → architectureDiagram-Q4EWVU46-CmOpXLsH.js} +1 -1
- package/payload/server/public/assets/{blockDiagram-DXYQGD6D-DqavswGH.js → blockDiagram-DXYQGD6D-CmS8f9YF.js} +1 -1
- package/payload/server/public/assets/browser-D4_XLSSm.js +1 -0
- package/payload/server/public/assets/{c4Diagram-AHTNJAMY-B7fyKiXe.js → c4Diagram-AHTNJAMY-BXIjMl6A.js} +1 -1
- package/payload/server/public/assets/calendar-s5V_gbI7.js +1 -0
- package/payload/server/public/assets/channel-DhrFAm7m.js +1 -0
- package/payload/server/public/assets/chat-40WyKzGb.js +1 -0
- package/payload/server/public/assets/chevron-left-C35qGP5f.js +1 -0
- package/payload/server/public/assets/{chunk-2KRD3SAO-DsgMtGkv.js → chunk-2KRD3SAO-D4fktAwW.js} +1 -1
- package/payload/server/public/assets/{chunk-336JU56O-CkdISfNt.js → chunk-336JU56O-C2MKnRTj.js} +2 -2
- package/payload/server/public/assets/chunk-426QAEUC-BhU7-TyS.js +1 -0
- package/payload/server/public/assets/{chunk-4BX2VUAB-CYpiF2bl.js → chunk-4BX2VUAB-C13Wz3zS.js} +1 -1
- package/payload/server/public/assets/{chunk-4TB4RGXK-CWyc_F7v.js → chunk-4TB4RGXK-CalYahTR.js} +1 -1
- package/payload/server/public/assets/{chunk-55IACEB6-BbzKMw2Z.js → chunk-55IACEB6-TmOKdy8a.js} +1 -1
- package/payload/server/public/assets/{chunk-5FUZZQ4R-u19Nai9e.js → chunk-5FUZZQ4R-C-i0BVag.js} +1 -1
- package/payload/server/public/assets/{chunk-5PVQY5BW-Ba8by6Uy.js → chunk-5PVQY5BW-D6bJMLnf.js} +1 -1
- package/payload/server/public/assets/{chunk-67CJDMHE-BwUuxtyb.js → chunk-67CJDMHE-Cif9Uu5C.js} +1 -1
- package/payload/server/public/assets/{chunk-7N4EOEYR-BRH9iM9L.js → chunk-7N4EOEYR-WjTHCXnd.js} +1 -1
- package/payload/server/public/assets/{chunk-AA7GKIK3-BGoztgLL.js → chunk-AA7GKIK3-CwCfdQmb.js} +1 -1
- package/payload/server/public/assets/{chunk-BSJP7CBP-C5GDAZTl.js → chunk-BSJP7CBP-9A67yXwT.js} +1 -1
- package/payload/server/public/assets/{chunk-CIAEETIT-ChY32VPS.js → chunk-CIAEETIT-DPMiWSBi.js} +1 -1
- package/payload/server/public/assets/{chunk-EDXVE4YY-CPSD3p6p.js → chunk-EDXVE4YY-B9PBdwiT.js} +1 -1
- package/payload/server/public/assets/{chunk-ENJZ2VHE-D1oL-iUr.js → chunk-ENJZ2VHE-CM5OqcQ5.js} +1 -1
- package/payload/server/public/assets/{chunk-FMBD7UC4-CPvYEr54.js → chunk-FMBD7UC4-Bmt_ZRlV.js} +1 -1
- package/payload/server/public/assets/{chunk-FOC6F5B3-9Yyj7Vbz.js → chunk-FOC6F5B3-DfGJGxpX.js} +1 -1
- package/payload/server/public/assets/{chunk-ICPOFSXX-BaYgh8FR.js → chunk-ICPOFSXX-D6M89DA0.js} +2 -2
- package/payload/server/public/assets/{chunk-K5T4RW27-5ucpeRvm.js → chunk-K5T4RW27-DMpoaqw2.js} +1 -1
- package/payload/server/public/assets/{chunk-KGLVRYIC-DW-FHk7T.js → chunk-KGLVRYIC-DYUFgri2.js} +1 -1
- package/payload/server/public/assets/{chunk-LIHQZDEY-CGGVfAyG.js → chunk-LIHQZDEY-x3LmeKmx.js} +1 -1
- package/payload/server/public/assets/{chunk-ORNJ4GCN-Bv8EUa5d.js → chunk-ORNJ4GCN-BeuNAWkW.js} +1 -1
- package/payload/server/public/assets/{chunk-OYMX7WX6-C1mXJnyw.js → chunk-OYMX7WX6-Bv6gisQ6.js} +1 -1
- package/payload/server/public/assets/chunk-QZHKN3VN-7_kS3RBy.js +1 -0
- package/payload/server/public/assets/{chunk-U2HBQHQK-PwSQz6EP.js → chunk-U2HBQHQK-C2iT9Av2.js} +1 -1
- package/payload/server/public/assets/{chunk-X2U36JSP--NPHTBlU.js → chunk-X2U36JSP-Bd6bv87A.js} +1 -1
- package/payload/server/public/assets/{chunk-XPW4576I-Bbc7Da32.js → chunk-XPW4576I-ByFnN4JO.js} +1 -1
- package/payload/server/public/assets/{chunk-YZCP3GAM-D-9v8e--.js → chunk-YZCP3GAM-Bt8I6ZDb.js} +1 -1
- package/payload/server/public/assets/{chunk-ZZ45TVLE-CMPg670Q.js → chunk-ZZ45TVLE-DBMPgZ_A.js} +1 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-BrWqm88p.js +1 -0
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-D0wzG5Fd.js +1 -0
- package/payload/server/public/assets/clone-DQA-nNrl.js +1 -0
- package/payload/server/public/assets/{cose-bilkent-S5V4N54A-NVSb1Bhy.js → cose-bilkent-S5V4N54A-DgLEaApT.js} +1 -1
- package/payload/server/public/assets/{dagre-KV5264BT-V2Ai1DFl.js → dagre-KV5264BT-kWVPgmmL.js} +1 -1
- package/payload/server/public/assets/{dagre-QfLcO-1j.js → dagre-mSazqXgG.js} +1 -1
- package/payload/server/public/assets/data-Bq0vU3qJ.js +1 -0
- package/payload/server/public/assets/{diagram-5BDNPKRD-Caw21693.js → diagram-5BDNPKRD-BHe_omJD.js} +1 -1
- package/payload/server/public/assets/{diagram-G4DWMVQ6-CgwVkT2q.js → diagram-G4DWMVQ6-fUFsmM3x.js} +1 -1
- package/payload/server/public/assets/{diagram-MMDJMWI5-DFUrLZhs.js → diagram-MMDJMWI5-DmujPLlM.js} +1 -1
- package/payload/server/public/assets/{diagram-TYMM5635-CEEiEuZJ.js → diagram-TYMM5635-B7Aoas89.js} +1 -1
- package/payload/server/public/assets/{dist-Cvgxm7qv.js → dist-C9D_xz81.js} +1 -1
- package/payload/server/public/assets/{erDiagram-SMLLAGMA-CG10NrT3.js → erDiagram-SMLLAGMA-6hS1rGiO.js} +1 -1
- package/payload/server/public/assets/{flatten-DAO0sfCM.js → flatten-BXzaHgtL.js} +1 -1
- package/payload/server/public/assets/{flowDiagram-DWJPFMVM-BqkV9Okv.js → flowDiagram-DWJPFMVM-Bqhr_r5b.js} +1 -1
- package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-DRiCDQF6.js → ganttDiagram-T4ZO3ILL-B8kjyN4o.js} +1 -1
- package/payload/server/public/assets/gitGraph-7Q5UKJZL-C_-XjxLd.js +1 -0
- package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-BZV2voyy.js → gitGraphDiagram-UUTBAWPF--TTRq4ir.js} +1 -1
- package/payload/server/public/assets/{graph-BZiudUGr.js → graph-C252ZHjp.js} +3 -3
- package/payload/server/public/assets/graph-labels-DzPbbhLc.js +1 -0
- package/payload/server/public/assets/{graphlib-DGPThX8M.js → graphlib-CWkjawc5.js} +1 -1
- package/payload/server/public/assets/info-OMHHGYJF-BH8yDCfL.js +1 -0
- package/payload/server/public/assets/infoDiagram-42DDH7IO-D8U4M0pK.js +2 -0
- package/payload/server/public/assets/{isEmpty-Cb_U81C6.js → isEmpty-uB1dEu_Z.js} +1 -1
- package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-BKsAxSvp.js → ishikawaDiagram-UXIWVN3A-CR8B7VzM.js} +1 -1
- package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-CmliuOd-.js → journeyDiagram-VCZTEJTY-45lWAvtq.js} +1 -1
- package/payload/server/public/assets/{kanban-definition-6JOO6SKY-DAlElMb-.js → kanban-definition-6JOO6SKY-DkrPXLVb.js} +1 -1
- package/payload/server/public/assets/{line-DKZqLKAC.js → line-Db0H446a.js} +1 -1
- package/payload/server/public/assets/{linear-CbSGdh1x.js → linear-X3O7lwK3.js} +1 -1
- package/payload/server/public/assets/maximize-2-CEbXFpQT.js +1 -0
- package/payload/server/public/assets/{mermaid-parser.core-DFvbao9C.js → mermaid-parser.core-CixBHOya.js} +2 -2
- package/payload/server/public/assets/{mermaid.core-C0vEBxDm.js → mermaid.core-BGQq98L7.js} +3 -3
- package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-CShASUKd.js → mindmap-definition-QFDTVHPH-v9cJl1mj.js} +1 -1
- package/payload/server/public/assets/operator-zKk4HMdX.js +1 -0
- package/payload/server/public/assets/{ordinal-H0tVmBDP.js → ordinal-CZY9laxg.js} +1 -1
- package/payload/server/public/assets/packet-4T2RLAQJ-Bajcns4r.js +1 -0
- package/payload/server/public/assets/page-BrmN_vzV.js +1 -0
- package/payload/server/public/assets/page-Cts-ESBe.js +32 -0
- package/payload/server/public/assets/{pdf-render-jAQU0nXt.js → pdf-render-7HZBTZ0_.js} +1 -1
- package/payload/server/public/assets/pie-ZZUOXDRM-D7hjM5w1.js +1 -0
- package/payload/server/public/assets/{pieDiagram-DEJITSTG-CBZHpDAJ.js → pieDiagram-DEJITSTG-DQ0b6edX.js} +1 -1
- package/payload/server/public/assets/public-BItzHBc0.js +1 -0
- package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-n3bB3K91.js → quadrantDiagram-34T5L4WZ-B64mj7Ei.js} +1 -1
- package/payload/server/public/assets/radar-PYXPWWZC-B46d5x7a.js +1 -0
- package/payload/server/public/assets/{reduce-DAvwat-a.js → reduce-BGQzAenn.js} +1 -1
- package/payload/server/public/assets/{requirementDiagram-MS252O5E-DiDvQK3c.js → requirementDiagram-MS252O5E-CE2JIEWo.js} +1 -1
- package/payload/server/public/assets/rotate-ccw-B0EYGhGx.js +1 -0
- package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-DWDQQZmd.js → sankeyDiagram-XADWPNL6-Cmt5cfCz.js} +1 -1
- package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-DmAqRlBT.js → sequenceDiagram-FGHM5R23-B5vVn_sw.js} +1 -1
- package/payload/server/public/assets/{src-DV4LKv2E.js → src-BTKk8KAG.js} +1 -1
- package/payload/server/public/assets/{stateDiagram-FHFEXIEX-D4R-n6bZ.js → stateDiagram-FHFEXIEX-DiUEJ-RZ.js} +1 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-DIm3XuAD.js +1 -0
- package/payload/server/public/assets/tasks-ClE8-be8.js +1 -0
- package/payload/server/public/assets/time-entry-format-BwjGLnXu.js +1 -0
- package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-CjiHGP4l.js → timeline-definition-GMOUNBTQ-C9jprDVw.js} +1 -1
- package/payload/server/public/assets/treeView-SZITEDCU-3XQh5xQ1.js +1 -0
- package/payload/server/public/assets/treemap-W4RFUUIX-DwR-_l5z.js +1 -0
- package/payload/server/public/assets/triangle-alert-DejWYn1x.js +1 -0
- package/payload/server/public/assets/useCopyFeedback-CpSgOavK.js +1 -0
- package/payload/server/public/assets/useSelectionMode-ZQZQUgze.js +1 -0
- package/payload/server/public/assets/useSubAccountSwitcher-DuRLvCfO.js +1 -0
- package/payload/server/public/assets/useVoiceRecorder-BTYYeUJW.js +2 -0
- package/payload/server/public/assets/{vennDiagram-DHZGUBPP-CBdDQ7c-.js → vennDiagram-DHZGUBPP-DWqQJ9X0.js} +1 -1
- package/payload/server/public/assets/wardley-RL74JXVD-DZp9oCGv.js +1 -0
- package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-BlIQPKLb.js → wardleyDiagram-NUSXRM2D-Dg6c2Voh.js} +1 -1
- package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-CslKgh9B.js → xychartDiagram-5P7HB3ND-EhN-zeDj.js} +1 -1
- package/payload/server/public/browser.html +7 -6
- package/payload/server/public/calendar.html +8 -7
- package/payload/server/public/chat.html +15 -14
- package/payload/server/public/data.html +14 -12
- package/payload/server/public/graph.html +13 -12
- package/payload/server/public/index.html +17 -15
- package/payload/server/public/operator.html +17 -16
- package/payload/server/public/public.html +15 -14
- package/payload/server/public/tasks.html +7 -6
- package/payload/server/server.js +1064 -536
- package/payload/server/public/assets/AdminLoginScreens-Bed8SDxY.js +0 -1
- package/payload/server/public/assets/AdminShell-BLh9O6dn.js +0 -2
- package/payload/server/public/assets/Checkbox-BC_KSbpS.js +0 -1
- package/payload/server/public/assets/admin-DfWgJVI3.js +0 -1
- package/payload/server/public/assets/architecture-YZFGNWBL-FPHT7s-C.js +0 -1
- package/payload/server/public/assets/browser-C0vkv-hH.js +0 -1
- package/payload/server/public/assets/calendar-_pfHw0Hp.js +0 -1
- package/payload/server/public/assets/channel-j70eMvcL.js +0 -1
- package/payload/server/public/assets/chat-DYT5H81o.js +0 -1
- package/payload/server/public/assets/chevron-left-BnpVBu51.js +0 -1
- package/payload/server/public/assets/chunk-426QAEUC-D-NcAlTW.js +0 -1
- package/payload/server/public/assets/chunk-QZHKN3VN-CAwbQBtf.js +0 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-Ct6Rn0vt.js +0 -1
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-B_2XCIFv.js +0 -1
- package/payload/server/public/assets/clone-DjPWA_NH.js +0 -1
- package/payload/server/public/assets/data-CR-fbPWr.js +0 -1
- package/payload/server/public/assets/gitGraph-7Q5UKJZL-B8Hx7f-D.js +0 -1
- package/payload/server/public/assets/graph-labels-BzmmJwCR.js +0 -1
- package/payload/server/public/assets/info-OMHHGYJF-BicCJTqU.js +0 -1
- package/payload/server/public/assets/infoDiagram-42DDH7IO-tf-8rOez.js +0 -2
- package/payload/server/public/assets/maximize-2-Bhdsv0U-.js +0 -1
- package/payload/server/public/assets/operator-gm2eP4r6.js +0 -1
- package/payload/server/public/assets/packet-4T2RLAQJ-Ckn_5hq0.js +0 -1
- package/payload/server/public/assets/page-8AB3jUa_.js +0 -1
- package/payload/server/public/assets/page-BoPorLg-.js +0 -32
- package/payload/server/public/assets/pie-ZZUOXDRM-DzlGQW3J.js +0 -1
- package/payload/server/public/assets/public-DqJk6qgW.js +0 -1
- package/payload/server/public/assets/radar-PYXPWWZC-BHo2YD8b.js +0 -1
- package/payload/server/public/assets/rotate-ccw-D_5cETGt.js +0 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-BcFRbGRu.js +0 -1
- package/payload/server/public/assets/tasks-EyKLXINi.js +0 -1
- package/payload/server/public/assets/time-entry-format-DAwmmR3i.js +0 -1
- package/payload/server/public/assets/treeView-SZITEDCU-BtRr5vXp.js +0 -1
- package/payload/server/public/assets/treemap-W4RFUUIX-D8p0sbyo.js +0 -1
- package/payload/server/public/assets/useCopyFeedback-rOWd_lzl.js +0 -1
- package/payload/server/public/assets/useSelectionMode-DDmLmPph.js +0 -1
- package/payload/server/public/assets/useSubAccountSwitcher-CU-lOxTd.js +0 -9
- package/payload/server/public/assets/useVoiceRecorder-azG8zSUI.js +0 -2
- package/payload/server/public/assets/wardley-RL74JXVD-DgMn-9ZJ.js +0 -1
- /package/payload/platform/plugins/cloudflare/skills/data-portal/{template/schema.sql → schema.sql} +0 -0
- /package/payload/platform/plugins/cloudflare/skills/data-portal/template/{public/index.html → index.html} +0 -0
- /package/payload/platform/plugins/cloudflare/skills/data-portal/template/{public/portal.js → portal.js} +0 -0
- /package/payload/server/public/assets/{_baseFor-D3BqAOBr.js → _baseFor-CMWvz0uA.js} +0 -0
- /package/payload/server/public/assets/{array-iYP9fzJp.js → array-Cny_NT6T.js} +0 -0
- /package/payload/server/public/assets/{chunk-Ck5AOu5v.js → chunk-TPjIAO9U.js} +0 -0
- /package/payload/server/public/assets/{cytoscape.esm-BQSBzPsB.js → cytoscape.esm-De0CCmB2.js} +0 -0
- /package/payload/server/public/assets/{defaultLocale-BrdA1gDi.js → defaultLocale-BahRht8U.js} +0 -0
- /package/payload/server/public/assets/{init-Bx0S6pkp.js → init-DoIvjoJ_.js} +0 -0
- /package/payload/server/public/assets/{katex-BsiH_a6d.js → katex-Bfn1OZEl.js} +0 -0
- /package/payload/server/public/assets/{path-BLSgTy3w.js → path-CTezU8L5.js} +0 -0
- /package/payload/server/public/assets/{preload-helper-ClBMtM1g.js → preload-helper-CT1Z6Pdu.js} +0 -0
- /package/payload/server/public/assets/{rough.esm-BMOtDaaS.js → rough.esm-BJo4pvXu.js} +0 -0
|
@@ -0,0 +1,807 @@
|
|
|
1
|
+
# Graph-native ledger — implementation plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** A small business or sole trader records invoices raised, bills received, payments, credits and loose cash movements in the graph on any vertical, and asks what is outstanding, getting an answer computed from graph state.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Three units. `platform/lib/ledger-core/` holds the money arithmetic as pure functions with no I/O. `platform/plugins/ledger/` is a seven-tool MCP that does all graph reads and writes and delegates every arithmetic decision to ledger-core. `platform/ui/app/lib/ledger-census.ts` is a 15-minute `setInterval` in the always-running UI server that calls the same pure reconcile as the `ledger-reconcile` tool, so the on-demand and standing forms cannot drift apart.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** TypeScript (ESM, `"type": "module"`), Neo4j via `neo4j-driver ^5.28.1`, `@modelcontextprotocol/sdk ^1.12.1`, vitest ^4.1.2 for pure and tool tests, `node:test` for the per-vertical coverage test (matching its neighbours in that directory).
|
|
10
|
+
|
|
11
|
+
Spec: [`../specs/2026-07-18-graph-native-ledger-design.md`](../specs/2026-07-18-graph-native-ledger-design.md). Task: [`.tasks/1771-graph-native-cash-book-ledger.md`](../../../../.tasks/1771-graph-native-cash-book-ledger.md).
|
|
12
|
+
|
|
13
|
+
## Global Constraints
|
|
14
|
+
|
|
15
|
+
- Node 22. Run `node -v` and confirm `v22.x` before any build or test. The default shell node is v20 and `node --test` globbing needs >= 22.6.
|
|
16
|
+
- Never run `npm install` anywhere inside this worktree. Deps are symlinked from the parent tree. A fresh install resolves `zod@4.4.3` and breaks three unrelated plugin builds with TS2589.
|
|
17
|
+
- Build from `maxy-code/platform`. Binaries are at `maxy-code/platform/node_modules/.bin/`.
|
|
18
|
+
- Money arithmetic is computed in **integer minor units** inside ledger-core. Graph storage stays decimal `amount` / `totalPaymentDue` numbers, unchanged from the existing construction rows.
|
|
19
|
+
- `outstanding`, `balance` and `amountPaid` are forbidden properties on `:Invoice` and `:InboundInvoice`. Outstanding is always computed, never stored.
|
|
20
|
+
- Payment direction is derived from the parent label. No `direction` property on `:InvoicePayment`.
|
|
21
|
+
- Every tool is registered with `eagerTool`, never `server.tool` or `server.registerTool`. `platform/scripts/check-no-raw-mcp-registrations.mjs` fails the build otherwise.
|
|
22
|
+
- `accountId` is read once from `process.env.ACCOUNT_ID` in `mcp/src/index.ts` and passed into each tool. Never derived inside a tool. It is a plain property, never an edge.
|
|
23
|
+
- MCP module init must never throw on missing per-account env. Plugin-system spawn enumerates with empty env; a boot throw empties the registry.
|
|
24
|
+
- Do not touch `plugins/memory/mcp/src/lib/typed-edge-schema.ts`. See spec correction 3.
|
|
25
|
+
- Do not hand-edit `packages/create-maxy-code/payload/`. It is gitignored and wiped by every bundle.
|
|
26
|
+
- Commit messages carry `Session: a23163ae-3b0e-4eae-8f84-b3d60ca4b3db` and the Co-Authored-By trailer.
|
|
27
|
+
|
|
28
|
+
**Baseline before any change:** 508 vitest tests pass across the memory plugin; 56 `node:test` tests pass. Three suites report failed under vitest because they are `node:test` files vitest cannot collect. Pre-existing, unrelated, do not fix here.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## File structure
|
|
33
|
+
|
|
34
|
+
| File | Responsibility |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `platform/lib/ledger-core/src/money.ts` | Decimal to minor-unit conversion, both directions |
|
|
37
|
+
| `platform/lib/ledger-core/src/balance.ts` | `computeOutstanding`, `applyPayment` |
|
|
38
|
+
| `platform/lib/ledger-core/src/ageing.ts` | `ageBuckets` |
|
|
39
|
+
| `platform/lib/ledger-core/src/reconcile.ts` | `reconcileLedger`, the census finding |
|
|
40
|
+
| `platform/lib/ledger-core/src/index.ts` | Re-export surface |
|
|
41
|
+
| `platform/plugins/ledger/mcp/src/lib/neo4j.ts` | Driver singleton, `getSession` |
|
|
42
|
+
| `platform/plugins/ledger/mcp/src/lib/read.ts` | Invoice + children reads shared by the tools |
|
|
43
|
+
| `platform/plugins/ledger/mcp/src/tools/*.ts` | One file per tool, seven files |
|
|
44
|
+
| `platform/plugins/ledger/mcp/src/index.ts` | Server, env reads, `eagerTool` registrations |
|
|
45
|
+
| `platform/plugins/ledger/PLUGIN.md` | Tool-name source of truth, MCP spawn block |
|
|
46
|
+
| `platform/ui/app/lib/ledger-census.ts` | Impure census tick and interval factory |
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
### Task 1: `platform/lib/ledger-core` — money and balance
|
|
51
|
+
|
|
52
|
+
**Files:**
|
|
53
|
+
- Create: `platform/lib/ledger-core/package.json`, `tsconfig.json`, `vitest.config.ts`
|
|
54
|
+
- Create: `platform/lib/ledger-core/src/money.ts`, `src/balance.ts`, `src/index.ts`
|
|
55
|
+
- Test: `platform/lib/ledger-core/src/__tests__/balance.test.ts`
|
|
56
|
+
|
|
57
|
+
**Interfaces:**
|
|
58
|
+
- Produces: `toMinor(n: number): number`, `fromMinor(n: number): number`, `computeOutstanding(totalPaymentDue: number, payments: readonly number[], credits: readonly number[]): number`, `applyPayment(totalPaymentDue: number, payments: readonly number[], credits: readonly number[], candidate: number): ApplyResult` where `type ApplyResult = { accepted: true; outstandingAfter: number } | { accepted: false; reason: 'over-application'; remaining: number }`. All amounts in and out are decimal, not minor units; minor units are internal.
|
|
59
|
+
|
|
60
|
+
- [ ] **Step 1: Write the failing test**
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
// platform/lib/ledger-core/src/__tests__/balance.test.ts
|
|
64
|
+
import { describe, it, expect } from 'vitest'
|
|
65
|
+
import { computeOutstanding, applyPayment } from '../balance.js'
|
|
66
|
+
|
|
67
|
+
describe('computeOutstanding', () => {
|
|
68
|
+
it('reports the full total when nothing is applied', () => {
|
|
69
|
+
expect(computeOutstanding(6400, [], [])).toBe(6400)
|
|
70
|
+
})
|
|
71
|
+
it('leaves the exact remainder after a part payment', () => {
|
|
72
|
+
expect(computeOutstanding(6400, [2000], [])).toBe(4400)
|
|
73
|
+
})
|
|
74
|
+
it('reaches zero when payments sum to the total', () => {
|
|
75
|
+
expect(computeOutstanding(6400, [2000, 4400], [])).toBe(0)
|
|
76
|
+
})
|
|
77
|
+
it('treats a credit identically to a payment', () => {
|
|
78
|
+
expect(computeOutstanding(6400, [], [2000])).toBe(4400)
|
|
79
|
+
expect(computeOutstanding(6400, [1000], [1000])).toBe(computeOutstanding(6400, [2000], []))
|
|
80
|
+
})
|
|
81
|
+
it('does not drift on repeating decimals', () => {
|
|
82
|
+
expect(computeOutstanding(100, [33.33, 33.33, 33.34], [])).toBe(0)
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
describe('applyPayment', () => {
|
|
87
|
+
it('accepts a payment within the outstanding amount', () => {
|
|
88
|
+
expect(applyPayment(6400, [], [], 2000)).toEqual({ accepted: true, outstandingAfter: 4400 })
|
|
89
|
+
})
|
|
90
|
+
it('accepts a payment that exactly settles the invoice', () => {
|
|
91
|
+
expect(applyPayment(6400, [2000], [], 4400)).toEqual({ accepted: true, outstandingAfter: 0 })
|
|
92
|
+
})
|
|
93
|
+
it('rejects over-application and reports the remaining amount', () => {
|
|
94
|
+
expect(applyPayment(6400, [2000], [], 5000)).toEqual({
|
|
95
|
+
accepted: false, reason: 'over-application', remaining: 4400,
|
|
96
|
+
})
|
|
97
|
+
})
|
|
98
|
+
it('rejects any payment against a zero-total invoice', () => {
|
|
99
|
+
expect(applyPayment(0, [], [], 1)).toEqual({
|
|
100
|
+
accepted: false, reason: 'over-application', remaining: 0,
|
|
101
|
+
})
|
|
102
|
+
})
|
|
103
|
+
it('rejects a zero or negative candidate', () => {
|
|
104
|
+
expect(applyPayment(6400, [], [], 0).accepted).toBe(false)
|
|
105
|
+
expect(applyPayment(6400, [], [], -5).accepted).toBe(false)
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
- [ ] **Step 2: Run it and watch it fail**
|
|
111
|
+
|
|
112
|
+
Run from `platform`: `./node_modules/.bin/vitest run lib/ledger-core/src/__tests__/balance.test.ts`
|
|
113
|
+
Expected: FAIL, cannot resolve `../balance.js`.
|
|
114
|
+
|
|
115
|
+
- [ ] **Step 3: Implement**
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
// platform/lib/ledger-core/src/money.ts
|
|
119
|
+
/** Money is compared and summed in integer minor units so the outstanding
|
|
120
|
+
* invariant is exact. Graph storage stays decimal; conversion happens here. */
|
|
121
|
+
export function toMinor(n: number): number {
|
|
122
|
+
return Math.round(n * 100)
|
|
123
|
+
}
|
|
124
|
+
export function fromMinor(n: number): number {
|
|
125
|
+
return n / 100
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
// platform/lib/ledger-core/src/balance.ts
|
|
131
|
+
import { toMinor, fromMinor } from './money.js'
|
|
132
|
+
|
|
133
|
+
export type ApplyResult =
|
|
134
|
+
| { accepted: true; outstandingAfter: number }
|
|
135
|
+
| { accepted: false; reason: 'over-application'; remaining: number }
|
|
136
|
+
|
|
137
|
+
/** outstanding = totalPaymentDue - sum(payments) - sum(credits). The single
|
|
138
|
+
* definition of the invariant; never stored on a node. */
|
|
139
|
+
export function computeOutstanding(
|
|
140
|
+
totalPaymentDue: number,
|
|
141
|
+
payments: readonly number[],
|
|
142
|
+
credits: readonly number[],
|
|
143
|
+
): number {
|
|
144
|
+
const applied = [...payments, ...credits].reduce((acc, a) => acc + toMinor(a), 0)
|
|
145
|
+
return fromMinor(toMinor(totalPaymentDue) - applied)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Validates one candidate payment or credit. Never clamps: a candidate that
|
|
149
|
+
* would take applied-to-date above the total is rejected outright so the
|
|
150
|
+
* caller reports it rather than silently recording a wrong figure. */
|
|
151
|
+
export function applyPayment(
|
|
152
|
+
totalPaymentDue: number,
|
|
153
|
+
payments: readonly number[],
|
|
154
|
+
credits: readonly number[],
|
|
155
|
+
candidate: number,
|
|
156
|
+
): ApplyResult {
|
|
157
|
+
const remaining = computeOutstanding(totalPaymentDue, payments, credits)
|
|
158
|
+
if (toMinor(candidate) <= 0) return { accepted: false, reason: 'over-application', remaining }
|
|
159
|
+
if (toMinor(candidate) > toMinor(remaining)) {
|
|
160
|
+
return { accepted: false, reason: 'over-application', remaining }
|
|
161
|
+
}
|
|
162
|
+
return { accepted: true, outstandingAfter: fromMinor(toMinor(remaining) - toMinor(candidate)) }
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
`package.json`: name `@maxy/ledger-core`, `version 0.1.0`, `private: true`, `type: module`, `main: dist/index.js`, scripts `build: tsc`, `test: vitest run`, devDeps `typescript ^5.7.0`, `@types/node ^22.0.0`, `vitest ^4.1.2`. No runtime deps; this package must stay I/O free.
|
|
167
|
+
|
|
168
|
+
`tsconfig.json`: `{ "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "dist", "rootDir": "src" }, "include": ["src"], "exclude": ["src/__tests__"] }`
|
|
169
|
+
|
|
170
|
+
`src/index.ts` re-exports from `./money.js` and `./balance.js`.
|
|
171
|
+
|
|
172
|
+
- [ ] **Step 4: Run and watch it pass**
|
|
173
|
+
|
|
174
|
+
Run: `./node_modules/.bin/vitest run lib/ledger-core/src/__tests__/balance.test.ts`
|
|
175
|
+
Expected: PASS, 10 tests.
|
|
176
|
+
|
|
177
|
+
- [ ] **Step 5: Commit**
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
git add maxy-code/platform/lib/ledger-core
|
|
181
|
+
git commit -m "feat(ledger-core): outstanding invariant and payment validation"
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
### Task 2: `ledger-core` — ageing and reconcile
|
|
187
|
+
|
|
188
|
+
**Files:**
|
|
189
|
+
- Create: `platform/lib/ledger-core/src/ageing.ts`, `src/reconcile.ts`
|
|
190
|
+
- Modify: `platform/lib/ledger-core/src/index.ts`
|
|
191
|
+
- Test: `platform/lib/ledger-core/src/__tests__/ageing.test.ts`, `src/__tests__/reconcile.test.ts`
|
|
192
|
+
|
|
193
|
+
**Interfaces:**
|
|
194
|
+
- Consumes: `computeOutstanding` from Task 1.
|
|
195
|
+
- Produces:
|
|
196
|
+
- `ageBuckets(invoices: readonly OpenInvoice[], now: number): AgeBuckets` where `interface OpenInvoice { ref: string; dueDateMs: number; outstanding: number }` and `interface AgeBuckets { current: number; d31to60: number; d61to90: number; d90plus: number; total: number }`. Each field is a summed money amount, not a count.
|
|
197
|
+
- `reconcileLedger(rows: LedgerCensusRows, now: number): LedgerCensusFinding` where
|
|
198
|
+
`interface LedgerCensusRows { invoices: number; payments: number; orphanPayments: number; storedBalanceProps: number; paymentKeys: readonly PaymentKey[]; declaredNotLive: readonly string[]; toolsResolved: number }`,
|
|
199
|
+
`interface PaymentKey { invoiceId: string; paymentId: string; amount: number; date: string }`,
|
|
200
|
+
`interface LedgerCensusFinding { invoices: number; payments: number; orphanPayments: number; storedBalanceProps: number; suspectDuplicates: number; declaredNotLive: readonly string[]; toolsResolved: number; alarm: boolean }`.
|
|
201
|
+
- `LEDGER_TOOL_COUNT = 7`.
|
|
202
|
+
|
|
203
|
+
Bucket boundaries are inclusive-lower on age in whole days: `current` is 0 to 30, `d31to60` is 31 to 60, `d61to90` is 61 to 90, `d90plus` is 91 and above. An invoice not yet due has age 0 and lands in `current`.
|
|
204
|
+
|
|
205
|
+
`suspectDuplicates` counts payment keys sharing `(invoiceId, amount, date)` across two or more distinct `paymentId` values. For a group of size n the count contributed is n-1, because one member of the group is legitimate.
|
|
206
|
+
|
|
207
|
+
`alarm` is true when any of `orphanPayments`, `storedBalanceProps`, `suspectDuplicates` is above zero, or `declaredNotLive` is non-empty, or `toolsResolved !== LEDGER_TOOL_COUNT`.
|
|
208
|
+
|
|
209
|
+
- [ ] **Step 1: Write the failing tests**
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
// platform/lib/ledger-core/src/__tests__/ageing.test.ts
|
|
213
|
+
import { describe, it, expect } from 'vitest'
|
|
214
|
+
import { ageBuckets } from '../ageing.js'
|
|
215
|
+
|
|
216
|
+
const DAY = 86_400_000
|
|
217
|
+
const NOW = Date.parse('2026-07-18T00:00:00Z')
|
|
218
|
+
const inv = (ref: string, ageDays: number, outstanding: number) =>
|
|
219
|
+
({ ref, dueDateMs: NOW - ageDays * DAY, outstanding })
|
|
220
|
+
|
|
221
|
+
describe('ageBuckets', () => {
|
|
222
|
+
it('sums money, not invoice counts', () => {
|
|
223
|
+
const b = ageBuckets([inv('a', 5, 100), inv('b', 10, 250)], NOW)
|
|
224
|
+
expect(b.current).toBe(350)
|
|
225
|
+
expect(b.total).toBe(350)
|
|
226
|
+
})
|
|
227
|
+
it('places a not-yet-due invoice in current', () => {
|
|
228
|
+
expect(ageBuckets([inv('a', -10, 100)], NOW).current).toBe(100)
|
|
229
|
+
})
|
|
230
|
+
it('puts each boundary day in the lower bucket', () => {
|
|
231
|
+
expect(ageBuckets([inv('a', 30, 1)], NOW).current).toBe(1)
|
|
232
|
+
expect(ageBuckets([inv('a', 31, 1)], NOW).d31to60).toBe(1)
|
|
233
|
+
expect(ageBuckets([inv('a', 60, 1)], NOW).d31to60).toBe(1)
|
|
234
|
+
expect(ageBuckets([inv('a', 61, 1)], NOW).d61to90).toBe(1)
|
|
235
|
+
expect(ageBuckets([inv('a', 90, 1)], NOW).d61to90).toBe(1)
|
|
236
|
+
expect(ageBuckets([inv('a', 91, 1)], NOW).d90plus).toBe(1)
|
|
237
|
+
})
|
|
238
|
+
it('returns all zeroes for no invoices', () => {
|
|
239
|
+
expect(ageBuckets([], NOW)).toEqual({ current: 0, d31to60: 0, d61to90: 0, d90plus: 0, total: 0 })
|
|
240
|
+
})
|
|
241
|
+
})
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
// platform/lib/ledger-core/src/__tests__/reconcile.test.ts
|
|
246
|
+
import { describe, it, expect } from 'vitest'
|
|
247
|
+
import { reconcileLedger, LEDGER_TOOL_COUNT } from '../reconcile.js'
|
|
248
|
+
|
|
249
|
+
const clean = {
|
|
250
|
+
invoices: 12, payments: 20, orphanPayments: 0, storedBalanceProps: 0,
|
|
251
|
+
paymentKeys: [], declaredNotLive: [], toolsResolved: LEDGER_TOOL_COUNT,
|
|
252
|
+
}
|
|
253
|
+
const NOW = Date.parse('2026-07-18T00:00:00Z')
|
|
254
|
+
|
|
255
|
+
describe('reconcileLedger', () => {
|
|
256
|
+
it('does not alarm on a clean ledger', () => {
|
|
257
|
+
expect(reconcileLedger(clean, NOW).alarm).toBe(false)
|
|
258
|
+
})
|
|
259
|
+
it('alarms on an orphan payment', () => {
|
|
260
|
+
expect(reconcileLedger({ ...clean, orphanPayments: 1 }, NOW).alarm).toBe(true)
|
|
261
|
+
})
|
|
262
|
+
it('alarms on a stored balance property', () => {
|
|
263
|
+
expect(reconcileLedger({ ...clean, storedBalanceProps: 1 }, NOW).alarm).toBe(true)
|
|
264
|
+
})
|
|
265
|
+
it('alarms when a declared label is not live', () => {
|
|
266
|
+
expect(reconcileLedger({ ...clean, declaredNotLive: ['CashEntry'] }, NOW).alarm).toBe(true)
|
|
267
|
+
})
|
|
268
|
+
it('alarms when a tool failed to resolve', () => {
|
|
269
|
+
expect(reconcileLedger({ ...clean, toolsResolved: 6 }, NOW).alarm).toBe(true)
|
|
270
|
+
})
|
|
271
|
+
it('counts n-1 duplicates per colliding group', () => {
|
|
272
|
+
const keys = [
|
|
273
|
+
{ invoiceId: 'i1', paymentId: 'p1', amount: 100, date: '2026-07-01' },
|
|
274
|
+
{ invoiceId: 'i1', paymentId: 'p2', amount: 100, date: '2026-07-01' },
|
|
275
|
+
{ invoiceId: 'i1', paymentId: 'p3', amount: 100, date: '2026-07-01' },
|
|
276
|
+
]
|
|
277
|
+
const f = reconcileLedger({ ...clean, paymentKeys: keys }, NOW)
|
|
278
|
+
expect(f.suspectDuplicates).toBe(2)
|
|
279
|
+
expect(f.alarm).toBe(true)
|
|
280
|
+
})
|
|
281
|
+
it('does not flag the same paymentId seen once', () => {
|
|
282
|
+
const keys = [{ invoiceId: 'i1', paymentId: 'p1', amount: 100, date: '2026-07-01' }]
|
|
283
|
+
expect(reconcileLedger({ ...clean, paymentKeys: keys }, NOW).suspectDuplicates).toBe(0)
|
|
284
|
+
})
|
|
285
|
+
it('does not flag two genuine same-day payments of different amounts', () => {
|
|
286
|
+
const keys = [
|
|
287
|
+
{ invoiceId: 'i1', paymentId: 'p1', amount: 100, date: '2026-07-01' },
|
|
288
|
+
{ invoiceId: 'i1', paymentId: 'p2', amount: 250, date: '2026-07-01' },
|
|
289
|
+
]
|
|
290
|
+
expect(reconcileLedger({ ...clean, paymentKeys: keys }, NOW).suspectDuplicates).toBe(0)
|
|
291
|
+
})
|
|
292
|
+
})
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
- [ ] **Step 2: Run and watch both fail**
|
|
296
|
+
|
|
297
|
+
Run: `./node_modules/.bin/vitest run lib/ledger-core`
|
|
298
|
+
Expected: FAIL on the two new files, PASS on `balance.test.ts`.
|
|
299
|
+
|
|
300
|
+
- [ ] **Step 3: Implement `ageing.ts` and `reconcile.ts`**
|
|
301
|
+
|
|
302
|
+
`ageBuckets` computes `ageDays = Math.floor((now - dueDateMs) / 86_400_000)`, clamps negatives to 0, and adds `outstanding` into the matching bucket, summing in minor units via `toMinor`/`fromMinor` so the totals do not drift.
|
|
303
|
+
|
|
304
|
+
`reconcileLedger` groups `paymentKeys` by the composite string `${invoiceId}|${amount}|${date}`, counts distinct `paymentId` per group, and sums `max(0, distinct - 1)`. It then computes `alarm` per the rule above and returns the finding. It performs no I/O and takes `now` as a parameter, never calling `Date.now()`.
|
|
305
|
+
|
|
306
|
+
Export `LEDGER_TOOL_COUNT = 7` from `reconcile.ts`. Re-export both modules from `src/index.ts`.
|
|
307
|
+
|
|
308
|
+
- [ ] **Step 4: Run and watch all pass**
|
|
309
|
+
|
|
310
|
+
Run: `./node_modules/.bin/vitest run lib/ledger-core`
|
|
311
|
+
Expected: PASS, 22 tests across three files.
|
|
312
|
+
|
|
313
|
+
- [ ] **Step 5: Add to the build chain and commit**
|
|
314
|
+
|
|
315
|
+
Insert `tsc -p lib/ledger-core/tsconfig.json && ` into both `build` and `build:lib` in `platform/package.json`, immediately after the `lib/graph-write` entry. Then:
|
|
316
|
+
|
|
317
|
+
```bash
|
|
318
|
+
cd maxy-code/platform && ./node_modules/.bin/tsc -p lib/ledger-core/tsconfig.json
|
|
319
|
+
ls lib/ledger-core/dist/index.js
|
|
320
|
+
git add maxy-code/platform/lib/ledger-core maxy-code/platform/package.json
|
|
321
|
+
git commit -m "feat(ledger-core): ageing buckets and census reconcile"
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
### Task 3: Ontology promotion
|
|
327
|
+
|
|
328
|
+
**Files:**
|
|
329
|
+
- Modify: `platform/neo4j/schema.cypher` (financial block around `:1550-1640`, fulltext union `:465-481`)
|
|
330
|
+
- Modify: `platform/plugins/memory/references/schema-base.md` (Node Types `:11-75`, Forbidden Properties `:119`, Property Naming Rules `:129`, Relationship Patterns `:168`)
|
|
331
|
+
- Modify: `platform/plugins/memory/references/schema-construction.md` (child table `:52-70`, natural keys `:104-121`)
|
|
332
|
+
- Test: `platform/plugins/memory/mcp/src/lib/__tests__/ledger-vertical-coverage.test.ts` (new, `node:test`)
|
|
333
|
+
|
|
334
|
+
**Interfaces:**
|
|
335
|
+
- Produces: the labels `:InboundInvoice`, `:InvoiceLine`, `:InvoicePayment`, `:Credit`, `:CashEntry` resolvable on every vertical. Task 4 onward depends on this.
|
|
336
|
+
|
|
337
|
+
- [ ] **Step 1: Write the failing coverage test**
|
|
338
|
+
|
|
339
|
+
New file, `node:test` to match `resolve-active-vertical.test.ts` in the same directory. It reads each `schema-<vertical>.md` plus `schema-base.md` through the same loader the validator uses, and asserts the five labels resolve for all ten `businessType` values including the `trades` alias.
|
|
340
|
+
|
|
341
|
+
```ts
|
|
342
|
+
// platform/plugins/memory/mcp/src/lib/__tests__/ledger-vertical-coverage.test.ts
|
|
343
|
+
import test from "node:test";
|
|
344
|
+
import assert from "node:assert/strict";
|
|
345
|
+
import { readFileSync, readdirSync } from "node:fs";
|
|
346
|
+
import { resolve, join } from "node:path";
|
|
347
|
+
|
|
348
|
+
const REFS = resolve(import.meta.dirname, "../../../../references");
|
|
349
|
+
const LEDGER_LABELS = ["InboundInvoice", "InvoiceLine", "InvoicePayment", "Credit", "CashEntry"];
|
|
350
|
+
|
|
351
|
+
function labelsIn(file: string): Set<string> {
|
|
352
|
+
const text = readFileSync(join(REFS, file), "utf8");
|
|
353
|
+
const out = new Set<string>();
|
|
354
|
+
for (const m of text.matchAll(/^\|[^|]*\|\s*`([A-Z][A-Za-z]*)`\s*\|/gm)) out.add(m[1]);
|
|
355
|
+
return out;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
test("every ledger label is declared in schema-base, not a vertical", () => {
|
|
359
|
+
const base = labelsIn("schema-base.md");
|
|
360
|
+
for (const label of LEDGER_LABELS) {
|
|
361
|
+
assert.ok(base.has(label), `${label} must be declared in schema-base.md`);
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
test("no vertical redeclares a ledger label", () => {
|
|
366
|
+
const verticals = readdirSync(REFS).filter((f) => /^schema-.+\.md$/.test(f) && f !== "schema-base.md");
|
|
367
|
+
for (const file of verticals) {
|
|
368
|
+
const declared = labelsIn(file);
|
|
369
|
+
for (const label of LEDGER_LABELS) {
|
|
370
|
+
assert.ok(!declared.has(label), `${file} redeclares ${label}; base owns it`);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
test("balance fields are forbidden on invoice labels", () => {
|
|
376
|
+
const text = readFileSync(join(REFS, "schema-base.md"), "utf8");
|
|
377
|
+
for (const prop of ["outstanding", "balance", "amountPaid"]) {
|
|
378
|
+
assert.match(text, new RegExp(`\\b${prop}\\b`), `${prop} must appear in Forbidden Properties`);
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
- [ ] **Step 2: Build and run it, watch it fail**
|
|
384
|
+
|
|
385
|
+
```bash
|
|
386
|
+
cd maxy-code/platform/plugins/memory/mcp && ../../../node_modules/.bin/tsc
|
|
387
|
+
node --test 'dist/lib/__tests__/ledger-vertical-coverage.test.js'
|
|
388
|
+
```
|
|
389
|
+
Expected: FAIL. `InboundInvoice must be declared in schema-base.md`.
|
|
390
|
+
|
|
391
|
+
- [ ] **Step 3: Edit `schema-base.md`**
|
|
392
|
+
|
|
393
|
+
Add five rows to the Node Types table with the required properties and natural keys from the spec. Add `outstanding`, `balance` and `amountPaid` to § Forbidden Properties with the reason that outstanding is computed. Move the line-item `amount` carve-out prose from `schema-construction.md:66-70` into § Property Naming Rules. Add the five edge patterns to § Relationship Patterns.
|
|
394
|
+
|
|
395
|
+
- [ ] **Step 4: Edit `schema-construction.md`**
|
|
396
|
+
|
|
397
|
+
Delete the `InboundInvoice`, `InvoiceLine`, `InvoicePayment` and `Credit` rows from the child table and their natural-key bullets. Leave `JobCost`, `PurchaseOrder`, `PurchaseOrderLine`, `Quote`, `QuoteLine`, `Valuation`, `Milestone`, `VariationNote`, `Contact`, `Note` untouched. Delete the `amount` carve-out paragraph now living in base.
|
|
398
|
+
|
|
399
|
+
- [ ] **Step 5: Edit `schema.cypher`**
|
|
400
|
+
|
|
401
|
+
Add, in the financial block:
|
|
402
|
+
|
|
403
|
+
```cypher
|
|
404
|
+
CREATE CONSTRAINT invoice_account_confirmation_unique IF NOT EXISTS
|
|
405
|
+
FOR (i:Invoice) REQUIRE (i.accountId, i.confirmationNumber) IS UNIQUE;
|
|
406
|
+
|
|
407
|
+
CREATE CONSTRAINT cash_entry_account_entryid_unique IF NOT EXISTS
|
|
408
|
+
FOR (ce:CashEntry) REQUIRE (ce.accountId, ce.entryId) IS UNIQUE;
|
|
409
|
+
CREATE INDEX cash_entry_account IF NOT EXISTS
|
|
410
|
+
FOR (ce:CashEntry) ON (ce.accountId);
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
Add `CashEntry` to the `entity_search_admin` fulltext union at `:465-481`, matching the existing entries' shape.
|
|
414
|
+
|
|
415
|
+
- [ ] **Step 6: Run the coverage test and the drift gate**
|
|
416
|
+
|
|
417
|
+
```bash
|
|
418
|
+
cd maxy-code/platform/plugins/memory/mcp && ../../../node_modules/.bin/tsc
|
|
419
|
+
node --test 'dist/lib/__tests__/ledger-vertical-coverage.test.js'
|
|
420
|
+
cd maxy-code/platform && ./node_modules/.bin/vitest run plugins/memory/mcp/src/lib/__tests__/schema-cypher-drift.test.ts
|
|
421
|
+
```
|
|
422
|
+
Expected: coverage 3 pass; drift gate PASS with no new `PROSE_ONLY_LABELS` entries.
|
|
423
|
+
|
|
424
|
+
- [ ] **Step 7: Confirm no new account file-schema bucket is minted**
|
|
425
|
+
|
|
426
|
+
`schema-base.md` has no `## Top-level node types` section, so the projection should be unaffected. Prove it:
|
|
427
|
+
|
|
428
|
+
```bash
|
|
429
|
+
cd maxy-code/platform && bash scripts/__tests__/account-schema-owned-dirs.test.sh
|
|
430
|
+
```
|
|
431
|
+
Expected: PASS, unchanged bucket list.
|
|
432
|
+
|
|
433
|
+
- [ ] **Step 8: Regression and commit**
|
|
434
|
+
|
|
435
|
+
```bash
|
|
436
|
+
cd maxy-code/platform/plugins/memory/mcp && node --test 'dist/lib/__tests__/resolve-active-vertical.test.js' 'dist/lib/__tests__/schema-validator.test.js' 'dist/lib/__tests__/schema-loader.test.js'
|
|
437
|
+
cd maxy-code/platform && ./node_modules/.bin/vitest run plugins/memory/mcp
|
|
438
|
+
```
|
|
439
|
+
Expected: 56 node:test pass unchanged; vitest 508 pass, same 3 uncollectable suites as baseline.
|
|
440
|
+
|
|
441
|
+
```bash
|
|
442
|
+
git add maxy-code/platform/neo4j/schema.cypher maxy-code/platform/plugins/memory/references maxy-code/platform/plugins/memory/mcp/src/lib/__tests__/ledger-vertical-coverage.test.ts
|
|
443
|
+
git commit -m "feat(schema): promote ledger labels to base, add CashEntry"
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
---
|
|
447
|
+
|
|
448
|
+
### Task 4: Plugin scaffold and the first write tool
|
|
449
|
+
|
|
450
|
+
**Files:**
|
|
451
|
+
- Create: `platform/plugins/ledger/PLUGIN.md`, `mcp/package.json`, `mcp/tsconfig.json`, `mcp/vitest.config.ts`
|
|
452
|
+
- Create: `platform/plugins/ledger/mcp/src/lib/neo4j.ts`, `src/lib/read.ts`, `src/index.ts`
|
|
453
|
+
- Create: `platform/plugins/ledger/mcp/src/tools/invoice-record.ts`
|
|
454
|
+
- Test: `platform/plugins/ledger/mcp/src/tools/__tests__/invoice-record.test.ts`
|
|
455
|
+
|
|
456
|
+
**Interfaces:**
|
|
457
|
+
- Consumes: `computeOutstanding` from Task 1; the base-declared labels from Task 3.
|
|
458
|
+
- Produces:
|
|
459
|
+
- `getSession(): Session` from `./lib/neo4j.js`.
|
|
460
|
+
- `readInvoice(session, accountId, ref): Promise<InvoiceRead | null>` from `./lib/read.js`, where `interface InvoiceRead { label: 'Invoice' | 'InboundInvoice'; invoiceId: string; totalPaymentDue: number; dueDateMs: number; payments: number[]; credits: number[]; paymentIds: string[] }`. Tasks 5 and 6 both call this.
|
|
461
|
+
- `invoiceRecord(params: InvoiceRecordParams): Promise<ToolResult>`.
|
|
462
|
+
|
|
463
|
+
Copy the structure of `platform/plugins/contacts/` exactly: `mcp/src/lib/neo4j.ts` is the same driver singleton with the same loud refusal when `NEO4J_URI` is unset (`[ledger] NEO4J_URI unset — refusing to default to bolt://localhost:7687`), and `mcp/src/index.ts` reads `ACCOUNT_ID`, `AGENT_SLUG`, `SESSION_ID` at module scope without throwing when they are absent.
|
|
464
|
+
|
|
465
|
+
`PLUGIN.md` frontmatter follows `platform/plugins/contacts/PLUGIN.md:1-48` with `name: ledger`, all seven tools listed with `adminAllowlist: true` and `publicAllowlist: false`, `always: false`, `embed: false`, `metadata: {"platform":{}}`, and the `mcp` spawn block routed through `mcp-spawn-tee` with `MCP_SPAWN_TEE_NAME: ledger`.
|
|
466
|
+
|
|
467
|
+
- [ ] **Step 1: Write the failing test**
|
|
468
|
+
|
|
469
|
+
The test stubs a session so no Neo4j is needed. It asserts the direction argument selects the label, and that a missing required field rejects without writing.
|
|
470
|
+
|
|
471
|
+
```ts
|
|
472
|
+
// platform/plugins/ledger/mcp/src/tools/__tests__/invoice-record.test.ts
|
|
473
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
474
|
+
import { invoiceRecord } from '../invoice-record.js'
|
|
475
|
+
|
|
476
|
+
const stubSession = () => ({ run: vi.fn().mockResolvedValue({ records: [] }), close: vi.fn() })
|
|
477
|
+
|
|
478
|
+
describe('invoice-record', () => {
|
|
479
|
+
it('writes an :Invoice when direction is receivable', async () => {
|
|
480
|
+
const write = vi.fn().mockResolvedValue({ nodesCreated: 1, edgesCreated: 1 })
|
|
481
|
+
await invoiceRecord({
|
|
482
|
+
session: stubSession() as never, accountId: 'acct1', write,
|
|
483
|
+
createdBy: { agent: 'a', session: 's', tool: 'ledger-invoice-record' },
|
|
484
|
+
direction: 'receivable', confirmationNumber: 'INV-1', counterparty: 'Acme',
|
|
485
|
+
totalPaymentDue: 6400, dueDate: '2026-08-01',
|
|
486
|
+
})
|
|
487
|
+
expect(write).toHaveBeenCalledWith(expect.objectContaining({ labels: ['Invoice'] }))
|
|
488
|
+
})
|
|
489
|
+
|
|
490
|
+
it('writes an :InboundInvoice when direction is payable', async () => {
|
|
491
|
+
const write = vi.fn().mockResolvedValue({ nodesCreated: 1, edgesCreated: 1 })
|
|
492
|
+
await invoiceRecord({
|
|
493
|
+
session: stubSession() as never, accountId: 'acct1', write,
|
|
494
|
+
createdBy: { agent: 'a', session: 's', tool: 'ledger-invoice-record' },
|
|
495
|
+
direction: 'payable', confirmationNumber: 'BILL-1', counterparty: 'Supplier Ltd',
|
|
496
|
+
totalPaymentDue: 900, dueDate: '2026-08-01',
|
|
497
|
+
})
|
|
498
|
+
expect(write).toHaveBeenCalledWith(expect.objectContaining({ labels: ['InboundInvoice'] }))
|
|
499
|
+
})
|
|
500
|
+
|
|
501
|
+
it('rejects a non-positive total without writing', async () => {
|
|
502
|
+
const write = vi.fn()
|
|
503
|
+
const res = await invoiceRecord({
|
|
504
|
+
session: stubSession() as never, accountId: 'acct1', write,
|
|
505
|
+
createdBy: { agent: 'a', session: 's', tool: 'ledger-invoice-record' },
|
|
506
|
+
direction: 'receivable', confirmationNumber: 'INV-2', counterparty: 'Acme',
|
|
507
|
+
totalPaymentDue: 0, dueDate: '2026-08-01',
|
|
508
|
+
})
|
|
509
|
+
expect(write).not.toHaveBeenCalled()
|
|
510
|
+
expect(res.isError).toBe(true)
|
|
511
|
+
})
|
|
512
|
+
})
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
`write` is injected rather than imported so the test never loads `graph-write`'s Neo4j dependency. The production `index.ts` passes the real `writeNodeWithEdges`.
|
|
516
|
+
|
|
517
|
+
- [ ] **Step 2: Run and watch it fail**
|
|
518
|
+
|
|
519
|
+
Run from `platform`: `./node_modules/.bin/vitest run plugins/ledger`
|
|
520
|
+
Expected: FAIL, cannot resolve `../invoice-record.js`.
|
|
521
|
+
|
|
522
|
+
- [ ] **Step 3: Implement the scaffold and the tool**
|
|
523
|
+
|
|
524
|
+
`invoice-record.ts` maps `direction: 'receivable' | 'payable'` to `labels: ['Invoice']` or `['InboundInvoice']`, builds props including `accountId`, `confirmationNumber`, `totalPaymentDue`, `paymentStatus: 'open'`, `dueDate`, and for payable also `supplier`. It builds the `BILLED_TO` or `FROM_SUPPLIER` relationship, validates that `totalPaymentDue > 0`, and returns an `isError` payload on rejection without calling `write`.
|
|
525
|
+
|
|
526
|
+
Register with `eagerTool` in `src/index.ts`, passing the real `writeNodeWithEdges` and a `getSession()` per call, closed in `finally`.
|
|
527
|
+
|
|
528
|
+
- [ ] **Step 4: Run and watch it pass**
|
|
529
|
+
|
|
530
|
+
Run: `./node_modules/.bin/vitest run plugins/ledger`
|
|
531
|
+
Expected: PASS, 3 tests.
|
|
532
|
+
|
|
533
|
+
- [ ] **Step 5: Typecheck and commit**
|
|
534
|
+
|
|
535
|
+
```bash
|
|
536
|
+
cd maxy-code/platform && ./node_modules/.bin/tsc -p plugins/ledger/mcp/tsconfig.json
|
|
537
|
+
git add maxy-code/platform/plugins/ledger
|
|
538
|
+
git commit -m "feat(ledger): plugin scaffold and invoice-record tool"
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
---
|
|
542
|
+
|
|
543
|
+
### Task 5: `payment-record`, `credit-record`, `cash-record`
|
|
544
|
+
|
|
545
|
+
**Files:**
|
|
546
|
+
- Create: `platform/plugins/ledger/mcp/src/tools/payment-record.ts`, `credit-record.ts`, `cash-record.ts`
|
|
547
|
+
- Modify: `platform/plugins/ledger/mcp/src/index.ts`, `src/lib/read.ts`
|
|
548
|
+
- Test: `platform/plugins/ledger/mcp/src/tools/__tests__/payment-record.test.ts`, `cash-record.test.ts`
|
|
549
|
+
|
|
550
|
+
**Interfaces:**
|
|
551
|
+
- Consumes: `applyPayment` from Task 1, `readInvoice` from Task 4.
|
|
552
|
+
- Produces: `paymentRecord`, `creditRecord`, `cashRecord`.
|
|
553
|
+
|
|
554
|
+
`payment-record` and `credit-record` share one code path parameterised by child label and edge type, because the spec requires a credit to reduce outstanding identically to a payment. `credit-record` accepts only `direction: 'receivable'` parents.
|
|
555
|
+
|
|
556
|
+
The full lifecycle from the spec is emitted here: `op=resolve`, `op=arithmetic`, `op=write` or `op=reject`, `op=verify`, `op=exit`. `op=request` and `op=result` come free from `eagerTool`. The `op=verify` line re-reads outstanding from the graph after the write and is not optional.
|
|
557
|
+
|
|
558
|
+
- [ ] **Step 1: Write the failing tests**
|
|
559
|
+
|
|
560
|
+
```ts
|
|
561
|
+
// platform/plugins/ledger/mcp/src/tools/__tests__/payment-record.test.ts
|
|
562
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
563
|
+
import { paymentRecord } from '../payment-record.js'
|
|
564
|
+
|
|
565
|
+
const base = {
|
|
566
|
+
accountId: 'acct1',
|
|
567
|
+
createdBy: { agent: 'a', session: 's', tool: 'ledger-payment-record' },
|
|
568
|
+
invoiceRef: 'INV-1', amount: 2000, date: '2026-07-18', paymentType: 'bank-transfer',
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
const readStub = (over = {}) => vi.fn().mockResolvedValue({
|
|
572
|
+
label: 'Invoice', invoiceId: 'INV-1', totalPaymentDue: 6400,
|
|
573
|
+
dueDateMs: Date.parse('2026-08-01'), payments: [], credits: [], paymentIds: [], ...over,
|
|
574
|
+
})
|
|
575
|
+
|
|
576
|
+
describe('payment-record', () => {
|
|
577
|
+
it('applies a part payment and leaves the exact remainder', async () => {
|
|
578
|
+
const write = vi.fn().mockResolvedValue({ nodesCreated: 1, edgesCreated: 1 })
|
|
579
|
+
const read = readStub()
|
|
580
|
+
const verify = vi.fn().mockResolvedValue(4400)
|
|
581
|
+
const res = await paymentRecord({ ...base, session: {} as never, read, write, verify })
|
|
582
|
+
expect(write).toHaveBeenCalled()
|
|
583
|
+
expect(res.isError).toBeFalsy()
|
|
584
|
+
expect(res.content[0].text).toContain('4400')
|
|
585
|
+
})
|
|
586
|
+
|
|
587
|
+
it('rejects over-application and writes nothing', async () => {
|
|
588
|
+
const write = vi.fn()
|
|
589
|
+
const read = readStub({ payments: [2000] })
|
|
590
|
+
const res = await paymentRecord({
|
|
591
|
+
...base, amount: 5000, session: {} as never, read, write,
|
|
592
|
+
verify: vi.fn(),
|
|
593
|
+
})
|
|
594
|
+
expect(write).not.toHaveBeenCalled()
|
|
595
|
+
expect(res.isError).toBe(true)
|
|
596
|
+
expect(res.content[0].text).toContain('4400')
|
|
597
|
+
})
|
|
598
|
+
|
|
599
|
+
it('rejects an unresolvable invoice reference', async () => {
|
|
600
|
+
const write = vi.fn()
|
|
601
|
+
const read = vi.fn().mockResolvedValue(null)
|
|
602
|
+
const res = await paymentRecord({ ...base, session: {} as never, read, write, verify: vi.fn() })
|
|
603
|
+
expect(write).not.toHaveBeenCalled()
|
|
604
|
+
expect(res.isError).toBe(true)
|
|
605
|
+
})
|
|
606
|
+
|
|
607
|
+
it('reports a verify mismatch as an error', async () => {
|
|
608
|
+
const write = vi.fn().mockResolvedValue({ nodesCreated: 1, edgesCreated: 1 })
|
|
609
|
+
const res = await paymentRecord({
|
|
610
|
+
...base, session: {} as never, read: readStub(), write,
|
|
611
|
+
verify: vi.fn().mockResolvedValue(9999),
|
|
612
|
+
})
|
|
613
|
+
expect(res.isError).toBe(true)
|
|
614
|
+
expect(res.content[0].text).toContain('verify')
|
|
615
|
+
})
|
|
616
|
+
})
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
`cash-record.test.ts` asserts a `:CashEntry` is written with `direction: 'in'` or `'out'`, that a missing `description` rejects, and that no invoice edge is created.
|
|
620
|
+
|
|
621
|
+
- [ ] **Step 2: Run and watch them fail**
|
|
622
|
+
|
|
623
|
+
Run: `./node_modules/.bin/vitest run plugins/ledger`
|
|
624
|
+
Expected: FAIL on the two new files, PASS on `invoice-record.test.ts`.
|
|
625
|
+
|
|
626
|
+
- [ ] **Step 3: Implement the three tools**
|
|
627
|
+
|
|
628
|
+
`paymentRecord` sequence: `read` the invoice; if null emit `op=reject reason=unknown-invoice` and return `isError`. Call `applyPayment`; if rejected emit `op=reject reason=over-application` with the remaining figure and return `isError` naming that figure. Otherwise emit `op=arithmetic`, `write` the `:InvoicePayment` child with edge `HAS_PAYMENT` from the parent label, emit `op=write`, call `verify` to re-read outstanding, emit `op=verify`, and if the re-read differs from `outstandingAfter` return `isError` naming both figures. Always emit `op=exit` with `outcome`.
|
|
629
|
+
|
|
630
|
+
`paymentId` must be deterministic from `(invoiceId, amount, date, paymentType)` so a retry MERGEs to the same node rather than creating a duplicate. This is what keeps `suspectDuplicates` at zero.
|
|
631
|
+
|
|
632
|
+
- [ ] **Step 4: Run and watch all pass**
|
|
633
|
+
|
|
634
|
+
Run: `./node_modules/.bin/vitest run plugins/ledger`
|
|
635
|
+
Expected: PASS, 11 tests across three files.
|
|
636
|
+
|
|
637
|
+
- [ ] **Step 5: Commit**
|
|
638
|
+
|
|
639
|
+
```bash
|
|
640
|
+
cd maxy-code/platform && ./node_modules/.bin/tsc -p plugins/ledger/mcp/tsconfig.json
|
|
641
|
+
git add maxy-code/platform/plugins/ledger
|
|
642
|
+
git commit -m "feat(ledger): payment, credit and cash write tools"
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
---
|
|
646
|
+
|
|
647
|
+
### Task 6: `balance`, `statement`, `reconcile` read tools
|
|
648
|
+
|
|
649
|
+
**Files:**
|
|
650
|
+
- Create: `platform/plugins/ledger/mcp/src/tools/balance.ts`, `statement.ts`, `reconcile.ts`
|
|
651
|
+
- Modify: `platform/plugins/ledger/mcp/src/index.ts`
|
|
652
|
+
- Test: `platform/plugins/ledger/mcp/src/tools/__tests__/statement.test.ts`
|
|
653
|
+
|
|
654
|
+
**Interfaces:**
|
|
655
|
+
- Consumes: `computeOutstanding`, `ageBuckets`, `reconcileLedger` from Tasks 1 and 2; `readInvoice` from Task 4.
|
|
656
|
+
- Produces: `balance`, `statement`, `reconcile`.
|
|
657
|
+
|
|
658
|
+
`balance` returns total, applied, credits and outstanding for one invoice, all computed. `statement` returns receivables in ageing buckets, payables, and net cash position from `:CashEntry` rows. `reconcile` gathers the census rows with Cypher, calls `reconcileLedger`, and emits the `[ledger-census]` line, returning the finding.
|
|
659
|
+
|
|
660
|
+
The census Cypher must gather: invoice and payment counts; payments with no incoming `HAS_PAYMENT` edge (orphans); nodes carrying any forbidden balance property; payment keys for duplicate detection; and `declaredNotLive` from the existing `declared-not-live` bucket in `plugins/memory/mcp/src/lib/live-schema-source.ts:180`.
|
|
661
|
+
|
|
662
|
+
- [ ] **Step 1: Write the failing test**
|
|
663
|
+
|
|
664
|
+
`statement.test.ts` stubs the session to return two open receivables at 10 and 45 days, one payable, and two cash entries, then asserts the buckets, the payables total, and that net cash equals money in minus money out.
|
|
665
|
+
|
|
666
|
+
- [ ] **Step 2: Run and watch it fail**
|
|
667
|
+
|
|
668
|
+
Run: `./node_modules/.bin/vitest run plugins/ledger`
|
|
669
|
+
|
|
670
|
+
- [ ] **Step 3: Implement the three tools**
|
|
671
|
+
|
|
672
|
+
- [ ] **Step 4: Run and watch all pass**
|
|
673
|
+
|
|
674
|
+
Run: `./node_modules/.bin/vitest run plugins/ledger`
|
|
675
|
+
Expected: PASS, all ledger tests.
|
|
676
|
+
|
|
677
|
+
- [ ] **Step 5: Commit**
|
|
678
|
+
|
|
679
|
+
```bash
|
|
680
|
+
git add maxy-code/platform/plugins/ledger
|
|
681
|
+
git commit -m "feat(ledger): balance, statement and reconcile read tools"
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
---
|
|
685
|
+
|
|
686
|
+
### Task 7: The standing census
|
|
687
|
+
|
|
688
|
+
**Files:**
|
|
689
|
+
- Create: `platform/ui/app/lib/ledger-census.ts`
|
|
690
|
+
- Modify: `platform/ui/server/index.ts` (near the `startTimeEntryCensus` block at `:2208-2215`)
|
|
691
|
+
- Test: `platform/ui/app/lib/__tests__/ledger-census.test.ts`
|
|
692
|
+
|
|
693
|
+
**Interfaces:**
|
|
694
|
+
- Consumes: `reconcileLedger` from Task 2, imported as `../../../lib/ledger-core/dist/index.js` following `app/lib/graph-health.ts:25`.
|
|
695
|
+
- Produces: `runLedgerCensusOnce(session, now)`, `startLedgerCensus(openSession, opts)`.
|
|
696
|
+
|
|
697
|
+
This file is the reason the ledger has real observability rather than documented log lines nothing emits. Mirror `platform/ui/app/lib/timeentry-census.ts` exactly: `unref()`'d interval, boot tick, best-effort Neo4j that logs and returns null instead of throwing into the timer, `console.error` with ` alarm=true` appended when the finding alarms and `console.log` otherwise. Default interval 15 minutes, matching its sibling.
|
|
698
|
+
|
|
699
|
+
- [ ] **Step 1: Write the failing test**
|
|
700
|
+
|
|
701
|
+
Assert that a clean session produces a `[ledger-census]` line on stdout with every counter present, that an alarming finding goes to stderr with `alarm=true`, that a rejecting session logs an error line and returns null rather than throwing, and that the returned stop function clears the interval.
|
|
702
|
+
|
|
703
|
+
- [ ] **Step 2: Run and watch it fail**
|
|
704
|
+
|
|
705
|
+
Run from `platform/ui`: `../node_modules/.bin/vitest run app/lib/__tests__/ledger-census.test.ts`
|
|
706
|
+
|
|
707
|
+
- [ ] **Step 3: Implement and wire**
|
|
708
|
+
|
|
709
|
+
Add to `platform/ui/server/index.ts` beside the existing census, using the same session getter pattern:
|
|
710
|
+
|
|
711
|
+
```ts
|
|
712
|
+
import { startLedgerCensus } from '../app/lib/ledger-census.js'
|
|
713
|
+
startLedgerCensus(() => getTimeEntryCensusSession())
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
Reuse the existing session getter rather than adding a second one; it opens a plain Neo4j session with no census-specific state.
|
|
717
|
+
|
|
718
|
+
- [ ] **Step 4: Run and watch it pass**
|
|
719
|
+
|
|
720
|
+
Run: `../node_modules/.bin/vitest run app/lib/__tests__/ledger-census.test.ts`
|
|
721
|
+
|
|
722
|
+
- [ ] **Step 5: Prove the line actually emits**
|
|
723
|
+
|
|
724
|
+
A passing unit test does not prove the wiring runs. Boot the UI server and confirm the boot tick appears:
|
|
725
|
+
|
|
726
|
+
```bash
|
|
727
|
+
cd maxy-code/platform/ui && npm run build 2>&1 | tail -5
|
|
728
|
+
```
|
|
729
|
+
Then start the server and grep its output for `[ledger-census]`. If the line is absent, the wiring is wrong regardless of the unit test. This step is the difference between this census and the SiteDesk ones.
|
|
730
|
+
|
|
731
|
+
- [ ] **Step 6: Commit**
|
|
732
|
+
|
|
733
|
+
```bash
|
|
734
|
+
git add maxy-code/platform/ui/app/lib/ledger-census.ts maxy-code/platform/ui/app/lib/__tests__ maxy-code/platform/ui/server/index.ts
|
|
735
|
+
git commit -m "feat(ledger): standing [ledger-census] wired into the UI server"
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
---
|
|
739
|
+
|
|
740
|
+
### Task 8: Registration and documentation
|
|
741
|
+
|
|
742
|
+
**Files:**
|
|
743
|
+
- Modify: `platform/services/claude-session-manager/src/canonical-tool-names.generated.ts` (regenerated, not hand-edited)
|
|
744
|
+
- Modify: `brands/maxy-code/brand.json`, `brands/realagent-code/brand.json`, `brands/sitedesk-code/brand.json`, `brands/property-administrators/brand.json`
|
|
745
|
+
- Modify: `platform/templates/specialists/agents/database-operator.md`, `project-manager.md`
|
|
746
|
+
- Create: `platform/plugins/docs/references/ledger.md`
|
|
747
|
+
- Modify: `.docs/` architecture reference, `.tasks/LANES.md`
|
|
748
|
+
|
|
749
|
+
- [ ] **Step 1: Regenerate the canonical tool names**
|
|
750
|
+
|
|
751
|
+
```bash
|
|
752
|
+
cd maxy-code/platform && npm run gen:canonical-tools
|
|
753
|
+
git diff --stat services/claude-session-manager/src/canonical-tool-names.generated.ts
|
|
754
|
+
```
|
|
755
|
+
Expected: seven `mcp__plugin_ledger_ledger__*` names added. Never hand-edit this file.
|
|
756
|
+
|
|
757
|
+
- [ ] **Step 2: Add `ledger` to `plugins.core[]` in all four brands**
|
|
758
|
+
|
|
759
|
+
All four, not a subset. A plugin absent from a brand's list does not ship to that brand.
|
|
760
|
+
|
|
761
|
+
```bash
|
|
762
|
+
cd maxy-code && python3 -c "
|
|
763
|
+
import json,glob
|
|
764
|
+
for f in sorted(glob.glob('brands/*/brand.json')):
|
|
765
|
+
d=json.load(open(f)); print(f, 'ledger' in d['plugins']['core'])
|
|
766
|
+
"
|
|
767
|
+
```
|
|
768
|
+
Expected: all four `True`.
|
|
769
|
+
|
|
770
|
+
- [ ] **Step 3: Add the ledger tools to the two specialist templates**
|
|
771
|
+
|
|
772
|
+
`database-operator.md:6` and `project-manager.md:6`, single-line comma-separated `tools:` frontmatter with the full canonical names.
|
|
773
|
+
|
|
774
|
+
- [ ] **Step 4: Write the user guide**
|
|
775
|
+
|
|
776
|
+
`platform/plugins/docs/references/ledger.md`, written for the operator running the product, not the developer. It states what the seven tools do, that outstanding is always computed, that over-application is refused rather than clamped, and what `[ledger-census] alarm=true` means when they see it.
|
|
777
|
+
|
|
778
|
+
Every `references/<file>.md` citation must resolve or `scripts/bundle.js:219-262` fails the bundle. Check any link added.
|
|
779
|
+
|
|
780
|
+
- [ ] **Step 5: Run the enforcement gates**
|
|
781
|
+
|
|
782
|
+
```bash
|
|
783
|
+
cd maxy-code/platform && node scripts/check-canonical-tool-names.mjs \
|
|
784
|
+
&& node scripts/check-specialist-tool-surface.mjs \
|
|
785
|
+
&& node scripts/check-plugin-tools-mcp-consistency.mjs \
|
|
786
|
+
&& node scripts/check-no-raw-mcp-registrations.mjs
|
|
787
|
+
```
|
|
788
|
+
Expected: all four exit 0.
|
|
789
|
+
|
|
790
|
+
- [ ] **Step 6: Commit**
|
|
791
|
+
|
|
792
|
+
```bash
|
|
793
|
+
git add maxy-code/platform maxy-code/brands maxy-code/.docs
|
|
794
|
+
git commit -m "feat(ledger): register across four brands, specialists and docs"
|
|
795
|
+
```
|
|
796
|
+
|
|
797
|
+
---
|
|
798
|
+
|
|
799
|
+
## Self-review
|
|
800
|
+
|
|
801
|
+
**Spec coverage.** ledger-core money and balance is Task 1. Ageing and reconcile is Task 2. Ontology promotion, forbidden properties, the `amount` carve-out, relationship patterns, schema.cypher constraints and the fulltext union are Task 3, with the per-vertical coverage test and the no-new-bucket check. The seven tools are Tasks 4 to 6. The wired census is Task 7. Registration across four brands, two specialist templates, the regenerated artifact and both doc targets are Task 8. The `typed-edge-schema.ts` change is deliberately absent per spec correction 3.
|
|
802
|
+
|
|
803
|
+
**Placeholders.** None. Every code step carries the code. Task 6 steps 1 and 3 describe the tests and implementation in prose rather than showing full listings, because the three read tools follow the shape already established by Tasks 4 and 5 and their Cypher depends on the exact property names settled in Task 3. This is the one deliberate departure from full-code steps in the plan.
|
|
804
|
+
|
|
805
|
+
**Type consistency.** `computeOutstanding` and `applyPayment` keep the same signatures in Tasks 1, 5 and 6. `InvoiceRead` is defined in Task 4 and consumed unchanged in Tasks 5 and 6. `LedgerCensusFinding` is defined in Task 2 and consumed in Tasks 6 and 7. `LEDGER_TOOL_COUNT` is 7 in both the reconcile alarm rule and the registration count.
|
|
806
|
+
|
|
807
|
+
**Risk.** Task 3 is the highest risk step: it edits the shared ontology that every vertical reads, and a mistake there fails writes on accounts unrelated to the ledger. Its regression check runs the full memory-plugin suite against the recorded baseline of 508 vitest and 56 node:test.
|