MONEY: Rust Money plugin (model, maths, codec, API) + first screens (budget month, accounts, simple transactions) #462
Open
opened 2026-09-29 15:25:50 +00:00 by kayg
·
111 comments
No Branch/Tag specified
dev
wip/kbpreview-1118
wip/kanban-1092
wip/importhang-1121
wip/hiderev-1153
wip/hide4-1153
wip/hide3-1153
wip/hide2-1153
wip/hide-1153
wip/editreg-1132
wip/editorrail3-1113
wip/editorrail2-1113
wip/editorrail-1113
wip/e2e-b2-1071
wip/e2e-b-1071
wip/draw4-1101
wip/draw3-1101
wip/draw2-1101
wip/draw-1101
wip/directory-1199
wip/delete-1119
wip/collabloss-1197
wip/cards2-1083
wip/cards-1083
wip/canvas-visual
wip/canvasvis2-976
wip/calhdr-1112
wip/calcards-1115
wip/browserfix
wip/blocks-1125
wip/allday-1107
wip/agenda-decks
wip/agenda-1086
wip/adv7c-1105
wip/txentry-1198
wip/trayicons2-1095
wip/trayicons-1095
wip/tagperf-1186
wip/sidebar3-1094
wip/segmented-1200
wip/rev2-webperf
wip/rev2-money-ident
wip/previewcard-1098
job/collabloss-1197
wip/palette2-1123
wip/palette-1093
wip/onboard2-1141
job/restyle-settings
wip/onboard-1141.aborted-early
wip/onboard-1141
wip/notifloop-1194
wip/nlpchip-1127
wip/morph-1104
wip/merge-round-7c5
wip/merge-round-7c4
wip/merge-round-7c3
wip/merge-round-7c2
wip/merge-round-7c
wip/mchrome-1084
wip/mailghost2-1094
wip/mailghost-1094
wip/kbpreview2-1118
job/restyle-files
job/tagdnd-1187
job/merge30
job/perf-1124
job/tocrail-1191
job/cards-1179
wip/cards2-1179
wip/cards-1179
job/segmented-1200
wip/tocrail-1191
job/hide-1153
wip/tagdnd-1187
wip/restyle-files
wip/perf-1124
wip/merge30j
job/onboard-1141
job/restyle-notes
wip/restyle-notes
job/wizchoices-1140
job/adv-1202
job/notifloop-1194
wip/wizchoices-1140
wip/restyle-1190
job/moneyfmt-1180
job/txentry-1198
wip/moneyfmt2-1180
wip/moneyfmt-1180-r
wip/moneyfmt-1180
job/tagperf-1186
job/pillglass-1189
job/flags-1181
wip/flags-1181
job/restyle-1190
job/restyle-mailmoney
job/restyle-search
job/settingsreg-1195
job/wizard-1140
site/website
wip/wizardrev2-1140
wip/wizardrev-1140
wip/wizard5-1140
wip/wizard4-1140
wip/wizard3-1140
wip/wizard2-1140
wip/wizard-1140
wip/pillglass-1189
wip/settingsreg-1195
job/merge29
job/fu-1171
wip/merge29j
wip/fu-1171
job/fu-1166
job/directory-1199
job/txresearch-1188
wip/fu-1166
job/merge28
job/search-1066
wip/search-1066
wip/merge28j
job/gateslot-1182
job/bulkimport-1157
job/mailnet-1160
wip/mailnetrev-1160
wip/mailnet-1160
wip/bulkrev-1157
wip/bulkimport-1157
job/startup-1161
wip/startup-1161
job/merge27
job/linkcards-1151
wip/linkcards3-1151
wip/linkcards2-1151
wip/linkcards-1151
job/traydate-1144
wip/traydate3-1144
wip/traydate2-1144
wip/traydate-1144
job/draw-1101
wip/merge27j
job/blockpill-1152
wip/blockpill3-1152
wip/blockpill2-1152
wip/blockpill-1152
job/minihover-1149
wip/minihover2-1149
wip/minihover-1149
job/merge25
wip/merge25-r
wip/merge25b
wip/merge25
job/inspector-1129
job/tags-1110
wip/inspector3-1129
wip/inspector2-1129
wip/inspector-1129
wip/tagsrev-1110
wip/tags2-1110
wip/tags-1110
job/dates-1148
wip/datesrev-1148
wip/dates2-1148
wip/dates-1148
job/licence-1145
wip/licence2-1145
wip/licence-1145
job/selfhost-1156
job/merge23
wip/merge23
job/tagfilter-1109
wip/tagfilter2-1109
wip/tagfilter-1109
job/kbd-1134
wip/kbd2-1134
wip/kbd-1134
job/palfoot-1137
wip/selfhost-1156
wip/palfoot2-1137
wip/palfoot-1137
job/toggle-1158
wip/toggle-1158
job/kbpreview-1118
job/docratchet-1155
job/perflint-1133
job/devtests-1159
wip/docratchet-1155
wip/devtests-1159
job/segv-1136
wip/toast-1142
wip/segv-1136
job/toast-1142
job/blockreload-1147
wip/blockreload-1147
job/font-1150
wip/font-1150
job/importui-1120
job/minimonth-1149
wip/importui-1120
wip/minimonth-1149
job/depcheck-1146
wip/perflint-1133
wip/depcheck-1146
job/calcards-1115
job/blocks-1125
job/plus-1128
job/shift-1138
wip/plus2-1128
wip/plus-1128
wip/shift-1138
job/moneyfid-1130
job/editorrail-1113
wip/moneyrev-1130
wip/moneyfid-1130
job/noext-851
wip/noext-851
wip/noext3-851
wip/noext2-851
job/week-1135
wip/week-1135
job/editreg-1132
job/smoke-1122
wip/smoke-1122
job/docs-1143
job/palette2-1123
job/calhdr-1112
job/nlpchip-1127
job/mailghost-1094
job/reconnect-1131
wip/reconnect-1131
job/trayicons-1095
job/delete-1119
job/importhang-1121
job/cards-1083
job/palette-1093
job/mchrome-1084
job/e2e-a-1071
job/canvas-visual
job/previewcard-1098
job/allday-1107
wip/e2e-a2-1071
wip/e2e-a-1071
job/e2e-b-1071
job/adv7c-1105
job/kanban-1092
job/agenda-1086
job/merge-round-7c
job/morph-1104
wip/surfaces-p2
job/merge-round-9
wip/merge-round-9
job/7cfix-small
wip/7cfix-small
job/mailui-1078
job/merge-round-8
wip/merge-round-8
wip/mailui-1078
job/mailround-1038
job/applemail-accept
wip/settitle-1068
wip/mailround2-1038
wip/mailround-1038
wip/e2e-7b
job/crash-1069
wip/crash-1069
job/searchlost-1066
wip/searchlost-1066
job/7b-reconcile
job/flake-1065
wip/flake-1065
wip/merge-round-7b7
wip/merge-round-7b6
wip/merge-round-7b5
wip/merge-round-7b4
wip/7b-reconcile
job/appupdate-1059
job/nfd-1044
wip/appupdate-1059
job/e2e-7b
job/loop-1062
wip/loop-1062
job/pdfprev-1045
job/invtoggle-1053
wip/pdfprev-1045
wip/nfd-1044
wip/invtoggle-1053
job/7bfix-e2e
job/mailstress-b
wip/7bfix-e2e
wip/mailstress-b
job/7bfix-adv
wip/7bfix-adv
job/mailstress-a
job/stack-1054
wip/stack-1054
wip/mailstress-a
job/mailstress-1038
wip/mailstress-1038
job/upload500-1051
wip/upload500-1051
job/share-1034
wip/share-1034
job/syncerr-1037
job/7bfix-photos
wip/7bfix-photos
job/paste-1036
job/setside-1039
wip/setside-1039
wip/paste-1036
job/lease-1042
wip/syncerr-1037
wip/lease-1042
job/7bfix-data
job/passkeybind-1043
wip/apprevoke-1041
job/invite-1035
wip/invite-1035
job/merge-round-7b2
wip/merge-round-7b2
job/mailproxy-486
job/apprevoke-1041
job/rebuild-1033
job/pillborder-1029
wip/pillborder-1029
wip/mailproxy-486
wip/applemail-486
job/headless-998
wip/headless-998
job/groups-1028
wip/groups-1028
job/rebuildwarn-1016
wip/rebuildwarn-1016
job/startup-1011
wip/startup-1011
job/monthpill-1009
job/bgthumb-1025
job/sharetitle-1012
wip/monthpill-1009
wip/bgthumb-1025
wip/sharetitle-1012
job/canvas-cards-977
wip/canvas-cards-977
job/canvas-pencil-978
job/canvas-sketch-990
wip/canvas-sketch-990
wip/canvas-pencil-978
job/canvas-files-989
wip/canvas-files-989
job/canvas-collab-991
wip/canvas-collab-991
job/weekscroll-1018
wip/weekscroll-1018
wip/canvas-core-976
job/canvas-core-976
job/round-drag
wip/round-drag
job/round-settings
job/browserfix
wip/oapi-974
job/oapi-974
job/hist2-integrate
job/mailhtml-726
wip/mailhtml-726
wip/hist2-integrate
job/moneyfu-984
job/drag-1015
wip/drag-1015
job/rename-1017
wip/rename-1017
job/hist2-api
wip/hist2-api
job/oneacct-1014
wip/oneacct-1014
wip/moneyfu-984
job/hist2-bench
job/hist2-restore
wip/hist2-bench
job/hist2-write
job/hotfix-724
wip/hotfix-724
wip/hist2-write
wip/hist2-restore
job/hist2-store
job/hist2-ui
wip/hist2-ui
wip/hist2-store
job/searchstarve-965
job/shutdown-963
wip/shutdown-963
wip/pubedit-981
job/pubedit-981
job/analytics-973
wip/searchstarve-965
job/authflash-850
job/weeklane-969
job/pvtitle-1004
job/hist-975
wip/authflash-850
job/voicepill-617
wip/pvtitle-1004
job/headring-1003
wip/weeklane-969
wip/voicepill-617
wip/headring-1003
wip/analytics-973
job/agentscope-980
wip/thumbsandbox-988
job/thumbsandbox-988
wip/hist-975
job/links-856
wip/links-856
job/davetag-966
wip/davetag-966
job/filesstorm-1000
job/hoverpad-725
wip/filesstorm-1000
job/ffmpegblas-993
job/merge-round-7a
wip/hoverpad-725
wip/ffmpegblas-993
job/nowdot-1002
wip/verify-7a
job/noteid-857
wip/nowdot-1002
wip/noteid-857
wip/merge-round-7a
wip/agentscope-980
job/imapedge
job/a11yfix2
wip/imapedge-941
wip/imapedge
wip/a11yfix2
job/notetask-986
job/logheading
wip/logheading-998
job/textthumb-652
job/photolive-987
wip/photolive-987
job/davactive-983
job/savefix-985
job/tabicons-607
wip/davactive-983
wip/tabicons-607
wip/notetask-986
wip/savefix-985
job/dirid-627
job/buildspeed-1007
wip/dirid-627
job/agenda-decks
job/perfguards-impl
job/undo-a11y
wip/undo-a11y
job/mailperf
job/wal-824
wip/settings-50
job/settings-50
job/notesfilter-606
wip/notesfilter-606
job/surfaces-p2
wip/wal-824
job/maillayouts
wip/mailperf
wip/maillayouts
job/taskmeta-659
job/money-ident
wip/money-ident
wip/taskmeta-659
job/errstates
wip/perfguards-impl
job/headings-881
wip/headings-881
wip/errstates
job/voice-619
job/gaps-827
job/notesperf
wip/notesperf
wip/voice-619
job/hddsql-549
job/perf-stream-668
wip/perf-stream-668
wip/deeplinks-fix
job/deeplinks-fix
job/authfix
job/docsfix-rust
wip/docsfix-rust
job/webperf
job/docsfix-web
job/datafix2
job/webdav-lock-476
job/copyfix
wip/copyfix
wip/webperf
job/focus-658
wip/protofix
job/mediafix
job/protofix
wip/mediafix
job/agentfix
job/hhmm-724
wip/agentfix
job/undo-722
job/reuse
wip/webdav-lock-476
wip/reuse
job/scopefix
job/datafix
wip/hhmm-724
wip/undo-722
job/surfaces-p1
wip/hddsql-549
job/voicememos-618
wip/datafix2
wip/surfaces-p1
job/fix-940
wip/fix-940
job/blaze-surfaces
wip/datafix
wip/blaze-surfaces
job/taskday-655
job/linknav-639
wip/linknav-639
wip/gaps-827
job/isolation-707
job/audiophotos-720
wip/audiophotos-720
job/advfind-664
wip/voicememos-618
wip/taskday-655
wip/isolation-707
wip/advfind-664
wip/scopefix
wip/focus-658
job/testgaps
wip/testgaps
job/overscroll-718
wip/authfix
job/deps
wip/overscroll-718
job/rev2-agentfix
job/rev2-money-ident
job/rev2-mailperf
wip/deps
job/hardening-728
wip/hardening-728
job/searchgen-832
wip/searchgen-832
job/photopw-849
job/mailsql-825
wip/photopw-849
job/sharefix
wip/sharefix
job/rev2-mailhtml-726
job/rev2-perfguards
job/copyval-723
job/lightglass-r2
wip/lightglass-r2
wip/docsfix-web
job/copy-audit
job/macinterop-staging-r2
job/design-sync
job/rev2-taskmeta-659
job/rev2-webperf
job/docs-audit
job/rev2-advfind-664
job/rev2-mailproxy-486
job/states-audit
job/rev2-datafix
job/design-drift
job/test-gaps
job/rev2-voicememos-618
job/rev2-mediafix
job/rev2-deps
job/rev2-datafix2
job/licence-audit
job/issue-hygiene
job/rev2-protofix
job/rev2-voice-619
job/rev2-isolation-707
job/rev2-surfaces-p1
job/deeplink-audit2
job/rev2-audiophotos-720
wip/test-gaps
job/rev2-overscroll-718
job/rev2-undo-722
wip/states-audit
job/rev2-dropmd-719
job/rev2-linknav-639
job/merge-7b-plan
wip/merge-7b-plan
job/rev2-taskday-655
wip/mailsql-825
job/rev2-webdav-lock-476
job/rev2-browserfix
wip/design-drift
job/rev2-hddsql-549
wip/deeplink-audit2
job/rev2-scopefix
job/rev2-authfix
job/rev2-hardening-728
job/rev2-wal-824
job/rev2-sharefix
job/calsidebar-638
job/chrome-audit
job/ioperf
wip/ioperf
wip/chrome-audit
wip/calsidebar-638
job/dropmd-719
wip/dropmd-719
job/ocr-build
wip/ocr-build
job/blaze-settings
wip/copyval-723
job/toastring-721
wip/toastring-721
job/deployfix-732
wip/deployfix-732
wip/blaze-settings
job/money-import-recheck
job/rev-a11y
job/perf-arch-db
job/rev-7b-data
wip/textthumb-652
wip/perf-arch-db
job/sec-protocols
job/sidehdr-660
job/rev-7b-security
job/research-surfaces
job/rev-design-gaps
job/rev-mcp-api
wip/sidehdr-660
job/perf-arch-memory
wip/sec-protocols
job/perf-arch-bundle
job/snapedge-714
wip/rev-mcp-api
job/sec-supplychain
wip/research-surfaces
job/perf-arch-sync
job/rev-consistency
job/perf-arch-server
wip/perf-arch-server
wip/perf-arch-memory
job/perf-arch-io
job/perf-arch-client
job/sec-fs
job/sec-mcp-scopes
job/sec-sharing
job/perf-guards
job/sec-browser
job/sec-admin-deploy
job/sec-auth
wip/snapedge-714
job/bgpicker-717
wip/perf-arch-bundle
wip/money-import-recheck
job/advsetup-654
wip/bgpicker-717
wip/advsetup-654
job/burst-709
job/kbdcaps-710
job/app-pw-chooser
wip/burst-709
wip/app-pw-chooser
job/imaptest-625
wip/kbdcaps-710
job/fix-499
wip/fix-499
job/perf-mut-667
job/calimg-589
job/perf-snap-666
wip/calimg-589
wip/perf-snap-666
wip/perf-mut-667
job/perf-cache-665
wip/perf-cache-665
job/voicefiles-620
wip/voicefiles-620
job/admin-burst-705
wip/admin-burst-705
job/voicememos-review
wip/voicememos-review
wip/ryw-653
job/ryw-653
job/writeonopen-661
job/instant-663
wip/writeonopen-661
job/money-import-review
wip/money-import-review
wip/importjs-610
review/integrations-407-round6
wip/integrations-review
job/dragghost-612
wip/dragghost-612
job/integrations
wip/integrations
job/decider-656
job/merge-round-6
job/perf-rerun
wip/merge-round-6
job/integrations-review-round5
job/selalign-576
wip/selalign-576
job/mcp-events-491
job/files-631
job/cal-e2e-569
wip/cal-e2e-569
job/reload-423
wip/reload-423
wip/mcp-events-491
wip/files-631
job/notesbridge-644
wip/notesbridge-644
job/editor-series
job/calcard-series
wip/calcard-series
job/mcp-events-review-491
wip/mcp-events-review
wip/editor-series
job/quirks-546
job/integrations-recheck
job/tocrail-636
wip/tocrail-636
wip/quirks-546
wip/reminders-643
job/reminders-643
wip/davscale-573
job/davscale-573
job/integrations-review
wip/ocr-eval-584
job/ocr-eval-584
job/esc-537
wip/esc-537
job/toastname-586
wip/toastname-586
job/submenu-579
wip/submenu-579
job/tasks-mode
wip/tasks-mode
job/agentdocs-630
job/dupwrite-634
wip/agentdocs-630
wip/dupwrite-634
job/lightglass-588
wip/lightglass-588
job/tabswitch-549
job/ghosttask-623
wip/ghosttask-623
job/toaststack-616
job/weekstate-609
job/mailsync-613
wip/mailsync-613
wip/weekstate-609
job/maildup-626
wip/tabswitch-549
wip/maildup-626
wip/toaststack-616
job/motion-611
wip/motion-611
job/tlstest-601
wip/tlstest-601
job/perf-495
job/floating-sheet
wip/floating-sheet
job/remdup-585
wip/remdup-585
job/fix-502
wip/fix-502
job/attachplay-622
job/perf-batch
wip/perf-batch-563
wip/perf-495
hotfix/mail-sync-diag
job/mail-m3
wip/mail-m3
job/attach-poof-603
job/calhover-608
job/editorbar-604
job/mentions-605
job/merge-round-4
job/allday-514
wip/merge-round-4
wip/allday-514
job/merge-round-4a
wip/merge-round-4a
job/sharestack-580
job/fix-501
wip/sharestack-580
wip/fix-501
job/perf-batch-563
job/apw-cache-review
wip/apw-cache-review
job/probe-520
wip/probe-520
job/mac-393
wip/mac-393
job/header-571
job/flake-513
wip/flake-513
job/docs-thumb-547
wip/header-571
job/webcal-572
wip/webcal-572
wip/shortcuts-542
job/shortcuts-542
wip/docs-thumb-547
job/caldav-stress
wip/caldav-stress
wip/sweep-478
job/apw-cache-512
wip/apw-cache-512
job/money-empty-540
wip/restart-505
wip/money-empty-540
wip/fix-510
job/restart-505
job/fix-503
job/perf-496
wip/perf-496
job/fix-498
wip/fix-498
job/info-inspector-465
wip/info-inspector-465
job/fix-510
job/fix-507
wip/fix-507
wip/fix-503
job/fix-493
job/money-kinds
wip/money-kinds
job/hygiene-548
job/merge-round-3
wip/fix-493
job/drag-snap-536
wip/merge-round-3
wip/merge-round-0930
wip/drag-snap-536
job/align-538
wip/align-538
job/bg-flash
wip/bg-flash
job/money-import
job/search-count-544
wip/search-count-544
wip/money-import
job/settings-key-541
wip/settings-key-541
job/toast-539
job/preview-421
wip/preview-421
wip/toast-539
job/tasks-500-531
job/title-plain-526
wip/title-plain-526
wip/tasks-500-531
job/notes-bridge
wip/parity-484
job/parity-484
job/files-slow
job/crash-525
wip/notes-bridge
wip/files-slow
wip/crash-525
job/kbd-motion-527
wip/bg-422
job/analytics-504
wip/analytics-504
wip/kbd-motion-527
job/upload-pill-523
wip/upload-pill-523
wip/tray-order
job/tray-order
wip/overflow-mid
wip/merge-round-2
job/perf-494
wip/perf-494
wip/mcp-fast-492
wip/motion-477
wip/asr-ab-489
wip/theme-variants-506
wip/overflow-511
wip/week-header-508
wip/attach-427
job/dav-delete-471
job/iso-435
wip/iso-435
wip/files-sel-keys
wip/dav-delete-471
job/align-253
job/siwc-490
wip/siwc-490
job/money-kinds-review
wip/align-253
wip/money-kinds-review
job/small-bugs-3
wip/overlay-title-487
wip/multiget-500
wip/hidden-420
wip/webcal-ui
wip/webcal-431
job/perf-367
job/location
wip/small-bugs-3
wip/location
wip/perf-367
wip/admin-deny-483
job/tag-unicode-473
wip/tag-unicode-473
job/blur-436
wip/photos-470
wip/blur-436
wip/small-bugs-4
wip/hunt-20260930
wip/settings-hdr-482
wip/chips-416
job/dedup-375
wip/dedup-375
job/doc-stack
wip/doc-stack
job/tokens-literals
wip/tokens-literals
job/jobs-leftovers
wip/send-fast
wip/paste-467
wip/money-numbers
job/money-plugin
wip/money-plugin
job/break-dav
wip/merge-batch
wip/crossday-469
wip/mac-verify
wip/mail-m2
wip/break-dav
wip/money-review2
job/money-md
job/modes-424
wip/money-md
wip/jobs-leftovers
job/agenda-413
wip/agenda-413
wip/modes-424
job/recog-417
wip/recog-417
wip/bounce-425
wip/ab-384-luna
job/webdav-perf
wip/webdav-perf
job/toast-ring
wip/toast-ring
job/money-review
wip/money-review
wip/micro-motion
wip/settings-card
wip/minical
job/notes-imap-428
job/least-priv
wip/ui-small-2
wip/flaky-426
wip/drag-end-418
job/jank
wip/jank
wip/least-priv
wip/docs-site
job/agenda
job/sec-batch
wip/sec-batch
wip/per-user-index
job/area-calendars
wip/area-calendars
job/parity
wip/parity
job/documents-research
wip/documents-research
job/test-infra
job/reminders-sync
wip/small-bugs-2
wip/reminders-sync
wip/gestures
job/google-oauth
wip/tags-merge
wip/tags
job/e2e-theme
wip/e2e-theme
job/icon-align
wip/test-infra
wip/select-align
wip/editor-385
job/voice
wip/webdav
job/webdav
job/app-pw-ui
job/editor-integrity
wip/editor-integrity
wip/voice
wip/quota
wip/cal-followups
wip/icon-align
job/composer-scale
wip/composer-scale
job/jobs-page
wip/jobs-page
job/hig-type
wip/hig-type
wip/app-pw-ui
job/motion-spring
job/mcp
wip/motion-spring
wip/mcp
job/small-bugs
wip/push-hosts
job/profile-sign
wip/touch-369
wip/profile-sign
job/mobile-focus
wip/mobile-focus
wip/ui-polish-354
wip/small-bugs
wip/dup-task
job/toast-polish
job/app-pw-scopes
wip/toast-polish
wip/app-pw-scopes
wip/cli-agent
wip/selection-pills
job/preview-attach
wip/preview-attach
job/dav-proppatch
wip/dav-proppatch
wip/cal-switcher
job/atomic-race
wip/atomic-race
job/photos-shared
wip/photos-shared
wip/cal-grid
wip/note-rewrite
wip/search-rebuild
job/mail-m1
job/paperless-import
wip/paperless-import
wip/mail-m1
wip/hidden-activity
wip/search-d
wip/pricing-research
wip/cursors
wip/auto-scheme
job/single-pills
wip/single-pills
wip/xuser-matrix
wip/money-format
wip/app-pw-setup
wip/purge-dos
wip/vault-health
wip/caldav-apple
wip/xuser-audit
wip/e2e-green
wip/tabbar
wip/adv-harness
wip/maple-mono
job/search-fix
wip/search-fix
wip/search-perf-c
job/adv-harness
wip/sidebar-headers
job/glass
wip/temp-index
job/polish
wip/polish
wip/file-protocols
wip/money-research
wip/glass
wip/voice-models
wip/collab-redo
job/voice-research
wip/hunt-20260928
wip/notes-actions-research
wip/search-pad
wip/search-perf
wip/search-sticky
wip/editor-undo
wip/chrome-rules
wip/motion
wip/appearance-research
wip/appearance
wip/audit-bugs
wip/cal-glass
wip/block-actions
wip/authz-order
wip/event-stripes
wip/chrome-sidebar
wip/auth-flaky
wip/robust-2
wip/gate-fix
wip/menu-blur
wip/import-calternaljs
wip/tray-fix
job/import-calternaljs
wip/index-order
wip/audit-fixes
wip/search-chevrons
research/mail
wip/phone-chrome
wip/dedup-break
wip/csp
wip/ui-audit
wip/select-toast
wip/perf
wip/flat-layout
wip/fonts
wip/event-tint
wip/sync-converge
wip/data-split
wip/glass-audit
wip/robustness
wip/sync-chaos
wip/search-thumbs
wip/fuzz
wip/menu-icons
wip/search-pill
wip/sync-changing
wip/heading-links
wip/date-formats
wip/a11y
wip/break-editor
wip/e2e-fix
wip/settings-sections
wip/sync-root-guard
wip/search-palette
wip/share-edit
job/toasts
wip/toasts
wip/cont-analytics
wip/authz-review
wip/popovers
wip/overlay-glass
wip/change-feed
wip/editor-modes
wip/composer-align
wip/cont-agenda
wip/agenda-merge
job/agent-conventions
wip/agent-conventions
wip/backend-misc
job/route-audit
wip/route-audit
wip/ui-batch
wip/heif-hardening
wip/grid-resize
wip/ask-page
wip/webmcp
job/deeplink-audit
wip/deeplinks
wip/shortcuts
wip/cont-tz-days
main
No results found.
Labels
Clear labels
No items
No labels
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
No due date set.
Dependencies
No dependencies set
Reference
kayg/calternal#462
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Goal (owner, 2026-09-29): build the Money plugin and its first screens now, in parallel with the format's round 3
Owner: "Can't we build the UI before the format is sound? I'm genuinely asking." Answer agreed: the UI talks only to the Money API, and only the codec knows the file syntax. So build the Rust Money plugin (model, budget maths, codec, API) and the screens whose meaning is settled now. Split, general transfer and FX editing UI wait for #404's round 3 plus the second independent review.
Read first:
^ids, ", " separator, YAML frontmatter for file-level properties,key:: valueinside, the five accepted reviewer decisions);docs/research/money-plugin.mdanddocs/research/money-markdown.mdonjob/money-md;spikes/money-markdown/andspikes/money-format/onjob/money-md(Python), whose tests are the reference oracle.1.
crates/plugins/money(a new plugin, optional and on by default per the plugin rule, hidden from the tray by default per the owner's T9 decision: added from Settings → Plugins)job/money-mdas it progresses; coordinate by keeping the codec behind one module boundary so format changes stay local.tests/money/that runs both). Add property tests withproptestover random ledgers: no floats, the category sum equals the account totals, and assign then unassign is the identity.Money/<Budget>/in the User's Home via calternal-fs (openat2, RESOLVE_BENEATH). All paths go through the fs crate. There is no cross-user state: derived data sits in per-User files (#435 rule).2. API (OpenAPI; parity #395: web, CLI, MCP and WebMCP adapters, recorded in the parity matrix)
List budgets; get a month (categories with assigned, activity and available, plus Ready to Assign); assign or move money between categories; list accounts with balances; list transactions per account and month; create, edit and delete a simple transaction (one account, one category); mark cleared. Split, transfer and FX writes return a clear "not yet supported" error (reads work).
3. First screens (Money mode; port the calternal.js design language: shared Settings card and row, glass tokens, sidebar rules)
^id). Keyboard, screen reader, reduced motion and touch.Proof
artifacts/money/.Gates per crate (
calternal-plugin-money,calternal-server), plus the web gates andpackages/api-client/check-generated.sh. Every number-handling function carries a doc comment stating its invariant.Started #462 on branch
job/money-pluginfromdevat43c1377c8fd3b1b495841446989a64156f8ae1e4. I read the repository contract, DESIGN §48 and the #404 Markdown spike. The codec will be isolated from Money API and maths so round 3 can change file syntax without changing the screens. Current spike still useskey:: valuefor file properties; I will check the format branch before final gates.On hold by owner order (2026-09-29): 'try to break our format one more time and then get started on money'. The sequence: #404 round 3 (running), then a second independent breakage review, then fix its findings, then this job starts.
Owner direction (2026-09-29 night): Money screens aim for the YNAB look (budget table layout, Assigned / Activity / Available columns, the Available pill colours for funded, underfunded and overspent, Ready to Assign banner, the month switcher, the category group rows, inspector-style target and bill details) rendered in calternal's aesthetics (glass tokens #436, shared Settings-style rows and cards #402, the type scale #370, springy motion #291, no stock styling). The ordering is unchanged: the format must first be solid (#404 round 3, then the second breakage review against the real statements, then its fixes); only then does this job start.
Started the Money plugin build on
job/money-plugin(base: the #404 spike merged withorigin/devat369ab6a2f).Step 1 is committed at
95569257c:crates/calternal-money, a pure Rust port of the reviewed oracle. It has exact minor units, the lossless codec, the projection, the envelope maths, bills, statements and edits. The oracle's own test suite now records its inputs and results incontracts/vectors/money/money.v1.json(625 vectors). Rust matches every vector except 2 deliberate range divergences. Next: the plugin backend (calternal-fs, per-User derived index, API routes, adversarial probe), then the web mode.#462 Money plugin: steps 1–3 done on
job/money-plugin(head9e42d934a1dfc6f34f904459d279422cc9e1152c)Base: the #404 spike merged with
origin/dev.origin/dev(06b1b5c73) is merged once before the final gates. No pushes, merges into dev or deploys.What is done
crates/calternal-moneyis the pure Rust port of the reviewed oracle. It has:Budget.md,Accounts.mdandYYYY-MM.md;Shared vectors:
tests/money/record_vectors.pyruns the spike's own test suite and records each oracle input and result incontracts/vectors/money/money.v1.json(625 vectors).tests/vectors.rsreplays all of them.tests/money/differential.shregenerates the vectors, diffs them and replays them. Property tests:crates/plugins/moneyis optional and on by default. It reads and writesMoney/<Budget>/only throughcalternal-fs. Every path is built withRelPath::user_home(user).join(..).replace_if; a race returns 409.Money/folder, so another User's ID can only be a 404.authz_matrix.pyandxuser_matrix.pyclassify every Money field, and A's budget fixture is seeded.tests/adversarial/money_api.mjs, hooked intorun.sh.PluginRequestContext::data_useris the one shared data-scope check. Calendar'sdata_scope_usernow calls it.The web mode has:
Deep links and Copy link are on every row. The DESIGN §33 grammar and
docs/deep-links.mdare updated. Money helpers have fast-check property tests. Production e2eapps/web/e2e/money.mjs: create a budget, add a category and an account, assign, add a transaction, see Available change, and check that the Markdown on disk is exact.Gate output (verbatim)
Adversarial round, one pass. The first run flagged that the budget name
CONis accepted. This is the Files naming policy on Linux, not a bug, so the probe expectation is fixed. The cross-user matrix did not run (XUSER_MATRIX_ONLY=1 run.sh). It stops before any request ondevitself, because 20 routes that already exist on dev have noROUTE_ID_FIELDSentry: the admin jobs, quota, my jobs and Mail{id}routes, for exampleadmin_stop_job. This needs its own fix. The Money routes have no{id}path slot, and every Money field is classified.Screenshots (production build, 390/820/1440, light and dark)
/home/kayg/Developer/calternal-wt/money-plugin/artifacts/money/:money-{empty,budget,register,register-card,quick-entry}-{light,dark}-{390,820,1440}.png(30 files).Decisions not covered by DESIGN (please confirm with the owner)
Accounts.mdwithout accounts is valid;#clearedtag, as in the #404 examples. The opening balance row is written as cleared.Remaining
subscription_charge_candidates,write_subscription_charge_match) and cancel-by and trial reminders are not ported yet.For the separate break-the-numbers review
budget.rs(Replay::month), plus theCardQueuesarena that shares one purchase between the per-card and per-category FIFO views.Minorrange bound and the digit accumulation inparse_minor(leading zeros, trailing zero fractions).continuous_months: months without a file and their assignments.edit::set_assigned: several rows absorb into the first; the byte surgery inreplace_assignment_amount.views::transfer_leg(FX transfer destination legs) and the cleared balances.parseMoneyInputheuristics:.as the decimal point in comma locales, parentheses, trailing signs, currency symbols.Independent number review started on job/money-numbers, base
9e42d934a. Scope: core replay, exact parsing, API projections and write integrity, web input, oracle differential and invariants. No production data is used.Independent review findings so far (base
9e42d934a).1,2,3becomes different stored digits; a sign inside negative parentheses can reverse the sign.AssertionError: 1,2,3: expected 12300 to be null. Regression commitd015d18b2; fix in progress.Tests use synthetic integer values only. No owner financial data is read or published.
Independent review findings so far (base
9e42d934a).1,2,3becomes different stored digits; a sign inside negative parentheses can reverse the sign.AssertionError: 1,2,3: expected 12300 to be null. Regression commitd015d18b2; fix in progress.Tests use synthetic integer values only. No owner financial data is read or published.
Additional finding: the Money handle LRU can evict a handle while a request holds it. A later request for that User then gets a different handle and a different write mutex. A cache-pressure identity test covers this case. The fix will retain active handles until all callers release them; idle handles remain bounded by the LRU.
Additional high-severity number finding: locale-formatted Arabic and Persian amounts could not be read back. The expanded round-trip property failed with
[0,"ar-EG",["USD",2],false](formatted zero parsed as null). The fix translates the selected locale's digit glyphs and removes Intl's direction marks before exact integer parsing. File syntax remains ASCII. Locale digit and grouping templates are cached with a 32-locale bound. Fix commit follows test commit4aa6d9c6c. The same property now passes for eight locales and six currency/scale pairs, including the exact integer range edges.Review fixes are in at head
ef67d55260a489c5dda9edce179c2344bed7b350.trayModeIds(packages/uitabOrder.ts) gives the active mode the last slot. A unit test (lib/trayModes.test.ts) and an e2e assertion check that the Money tab is selected and shows its label.money-budget-end-*.memoandtags, parsed with the Notes tag grammar (extract_inline_tag_refs). The register renders tags with the sharedTagPill. The Date column usesformatShortDatefrom@calternal/ui, the shared helper that follows Settings → date format; "9/30/26" is its system-locale output.{id}routes, listed with the owning jobs.Gates:
Screenshots:
/home/kayg/Developer/calternal-wt/money-plugin/artifacts/money/money-{empty,budget,budget-end,register,register-card,quick-entry}-{light,dark}-{390,820,1440}.png(36 files, viewport-sized). I checked icon and label alignment in zoomed crops of the sidebar account rows, the tray and the register tag chip.Independent break-the-numbers review: final report (branch
job/money-numbers)Head:
0e5d74b3523323af16d716e56a325b12f03523d7. The branch mergesjob/money-pluginatef67d5526, so this is a review of the current code (memo and tags split included). There are no pushes and no merges into dev.Findings
fdd4548c963ea86a36: a move journal (users/<id>/.calternal/money-move.json, CreateNew) is written before the destination. Every request settles a leftover journal under the write lock: a row in both files is removed from the destination. An unsettled journal makes the next move a 409.amount = 2^53-1on top of a balance, a cleared subset, or a category sum was stored first, and after that every read of the budget failed.c1aa32d455f99d7d14:project_withreplays every month and builds the month and account responses before the write. The request gets a 400 and nothing is written.2200-01or1900-01wrote the row, and after that every replay exceeded the 1200-month bound, so the budget could not be read.343c1bd9f(it fails when the replay is disabled)5f99d7d14c1aa32d45b1a44b1f3: only idle handles are evicted.1,2,3was stored as 12300, and a sign inside parentheses flipped the sign.d015d18b21d63b00774aa6d9c6c4044db7421 2became 12.00 in en-US, and12 34in fr-FR skipped the group-width check.1f51beee265fea3929: a space inside the digits is accepted only as the group mark of a locale that groups with spaces.1.234was read as 1.234, but it is also the grouping of 1234.1f51beee265fea3929: the input is refused, not guessed.0e5d74b350e5d74b35: the per-User lock is now an RwLock, and the four read handlers take it shared.bun run checkfailed on1d63b0077(groups[0]possibly undefined).c2a240384Not fixed (Low, UX; left for the Money owner):
Math.abs, so a minus typed in Inflow is silently dropped.Checked with no defect found
tests/money/fuzz.{py,sh}, run bydifferential.sh). 500 seeded ledgers, 2000 month snapshots, with no divergence allowances. They cover cash and card spending, card refunds, opening card debt, several assignment rows, partial and excess payments, splits on cash and card, transfers to tracking, card cash advances and a year boundary. Every number matches the Python oracle exactly. Mutation check: ifCardQueues::releasereleases only the first purchase, the fuzz fails at seed-0, while the 625 committed vectors still pass.parse_minor. The ±(2^53−1) edges are exact at scales 0, 2 and 3 with 4096 padding zeros. Unicode digits, exponents,++,+-, NBSP and ZWSP are refused.continuous_monthshandles gap months, a year boundary and a mid-year start. Assignments in a gap month count.i64(a value such as1.5or1e2is a 400).Gates (at
0e5d74b35)The live adversarial probe (
tests/adversarial/money_api.mjs, which gained a derived-range check) was not run on a real server in this review, because the server build is too heavy for the shared host now. Run it in the merge round.Decisions (please confirm)
users/<id>/.calternal/money-move.json. Recovery rolls back: the destination copy is removed, because the move never reported success.1.234at 3 decimals in a.-group locale, and a stray space in a non-space-group locale). It does not guess.Verdict
Yes: after these fixes, the numbers are correct enough to merge. The replay maths matches the oracle exactly on 625 vectors plus 500 strict random ledgers, and the invariants hold. The blocking data-corruption path (finding 1) and every High finding are fixed on this branch, each with a regression test.
job/money-pluginmust take this branch (mergejob/money-numbers) before it merges into dev. The plugin atef67d5526without these commits is not fit to merge.Merge round: ready to fast-forward
devBranch
job/money-numbers, head99288dcfe9433ffd07fb1b886a947fd66ef8201d(not pushed).origin/dev412ad2136is an ancestor (checked withgit merge-base --is-ancestor). The reviewed head0e5d74b35is also an ancestor.Two merges:
17a8f75e9mergesorigin/devcefff9134(133 commits). One conflict:tests/adversarial/authz_matrix.pyDATA_PREFIXES. Resolution keeps both/api/v1/mail/and/api/v1/money/. The mode tray,app-sidebar.svelte,run.sh,xuser_matrix.py,contracts/openapi.jsonandgenerated.tsauto-merged. The generated check regenerated both files with no diff.99288dcfemergesorigin/dev412ad2136(#467 Finder paste). It landed during the round. Web files only, no conflicts.Migrations: the Money plugin adds no SQL migration. It stores its data as Markdown in the User's Home, so nothing can collide with dev.
Cross-User guard (#472): every Money
{id}field is classified (budget_id,account_id,category_id,transaction_id, and the payment/transfer/card/from/to variants). The matrix replays all 11 Money identifier operations against a real A-owned budget, category, account and transaction.No fixes were needed. Nothing produced a 5xx, a crash, accepted hostile input or a cross-User leak.
Gates (verbatim)
bun run build(apps/web):build=0cargo fmt --check:fmt=0(no output)clippy (
-p <crate> --all-targets -- -D warnings,CARGO_PROFILE_DEV_DEBUG=line-tables-only):cargo test (per crate):
tests/money/differential.sh:differential_exit=0bun run check(final head):bun run test(final head):web_test_exit=0bash packages/api-client/check-generated.sh(final head):generated=0(no diff after regenerating)python3 scripts/parity_matrix.py --check:Parity matrix: 187 web API actions, 113 shortcuts, 2 static commands, 131 menu actions, 30 settings groups, 168 actions with adapter gaps,parity=0XUSER_CLASSIFY_ONLY=1 python3 tests/adversarial/xuser_matrix.py:Cross-User classification gate: 307 operations classified.test_xuser_classification.py:Ran 3 tests ... OKLive probes (real local server from this tree)
bun tests/adversarial/money_api.mjs:money_api_exit=0XUSER_MATRIX_ONLY=1 tests/adversarial/run.sh:xuser_exit=0The 15 routes without a seeded object are the same non-Money routes as on dev (admin config and plugins, Unsplash, auth setup/invite/app-password, unified Mail inbox, note templates, journal fix-line, per-User plugin toggle). dev replayed 142 operations; this branch replays 153, and the difference is the 11 Money identifier operations.
bun e2e/money.mjs(apps/web, final head):money_e2e_exit=0,PASS captured 36 Money screenshots in .../artifacts/money. The mode tray shows Mail and Money side by side after the merge.Rust gates, the differential test and the two server probes ran on
17a8f75e9. The #467 merge changes only web files, so those results still apply. The web gates, the generated-contract check, the parity check and the Money e2e ran again on99288dcfe.First Money slice merged into dev (fast-forward to
99288dcfe) after the independent number review (#462 findings table) and the merge round (all gates and live probes green). Remaining for this issue: subscription FX matching, cancel-by/trial reminders, bills and statements in API/UI (statement Unrecorded waits on the owner), editing splits/transfers/card payments/FX rows, category/account rename/hide/delete, CLI/MCP/WebMCP adapters, deep-link e2e.Owner decisions (2026-09-30 morning):
Owner decision (2026-09-30): the account kinds are Debit (cash, checking, savings), Credit (cards and credit lines you spend from), Loan (EMIs and loans, paid down but never spent from; the balance sits outside Ready to Assign and the EMI is a monthly target on a payment category) and Tracking (off-budget: investments, assets). Loans are awkward in Actual Budget too, and calternal should solve them properly. Running EMIs are detected from statements and suggested as Loan accounts plus a payment plan. This replaces the spike's cash/card/tracking. It needs a format change in
spikes/money-markdownandcrates/calternal-money, with the kinds renamed and Loan added, and a number review for the Loan maths.Started money-kinds on
job/money-kinds, base268b657451808355c1eecd32bf1c808ac079aa71. Read CLAUDE.md, CONTEXT.md, DESIGN §48 and all #462 owner comments. Scope: four account kinds, exact Loan payment maths, lossless legacy-kind migration, shared oracle vectors, API and Add Account form. No new dependency is planned.Loan maths and migration committed at
ed346b19fafter core clippy/tests passed. DESIGN §48 and research were committed first at343f926d6. Mergedorigin/devonce (22a75ab56), no conflicts.Evidence: a new property initially rejected
debit; the readers now accept canonical kinds and old aliases. Loan-only interest does not change Category Activity or Ready to Assign. Full payments consume the linked Category once. Conversion and billed principal preserve Credit reserves. The monthly cash-priority pre-pass now includes Loan payment transfers, so Debit/Credit payments to one Category cannot change reserve funding when file order changes. Properties and recorded shared vectors cover both orders.Decisions: principal/interest use a full payment transfer plus a separate Loan charge; no new split grammar. Overpayment may leave a positive Loan balance without funding the Budget. Raw serialization retains legacy bytes; account writes migrate only the kind values. The API/form will accept an optional positive monthly Loan target; no target posts transactions or assigns money. Loan totals will be distinct from on-budget and Tracking totals. Transfer/FX editing and EMI suggestion import remain later work already listed on #462.
Core gate output (verbatim):
Gate environment finding: plugin clippy passed, but the plugin test compilation failed before running tests:
This job exports its own
target/tmp, but the shared sccache daemon used another worktree's removed temporary directory. The failed gate chain was stopped. The remaining Rust gates are being rerun withRUSTC_WRAPPER=for this job only; no shared daemon was stopped and no source/test expectation was changed.The differential script passed 712 committed vectors plus 500 strict random ledgers (2000 month snapshots). Web check, 881 tests and the production build passed. Core commit is now
78fcd358c(the amendment includes the plugin's mechanical Cash/Card enum renames, so that slice builds independently); web slice is361d7f453.The new account API regression test found a writer error: Loan creation with a monthly target returned 400, with
expected one "target" property under ^loan-payment, instead of 201.codec::set_propertyreplaces one existing property and does not insert a missing property.Fix: extend the existing
edit::add_categorywriter with an optional positive monthly target. Loan creation passes the target when it creates the payment Category. Existing Category and Credit creation passNone. The writer keeps existing line endings and unknown spans and does not add assignments. A new core regression checks the exact target, CRLF retention, no assignment rows and rejection of a zero target. Existing test expectations are unchanged. Core and plugin gates are being rerun after this change.Loan validation previously scanned accounts and Categories for each posting. I replaced those scans with borrowed ID maps and a set of Credit payment Categories. The final core clippy check passes with warnings denied. The full core test run is queued behind the cold production server build. No new dependency is needed.
The server clippy gate also passes. Shared sccache remains disabled only for this job because its daemon used another worktree's removed temporary directory; the shared daemon was not changed.
Production Money browser flow passed against the real local server and production web build. Loan creation keeps Ready to Assign unchanged and writes the monthly target. Bounded invalid-plan requests return 400 and leave the Money files byte-identical.
The review set has 72 screenshots: 390, 820 and 1440 px, light and dark, including all four kinds, Loans navigation, Loan register, Budget, other registers and quick entry. Fixtures are synthetic test data only. The screenshot readiness race is fixed in
67731f38310a8776182e6900f10cc9a60d256a4b; no product styling was changed. Original-resolution Loan screenshots were checked for clipping and icon placement. The desktop Loan icon/cap-height centers differ by 0.5 CSS px. Claude visual review remains required.Server tests:
85 passed; 0 failed; 2 ignored. Current-source core tests pass. Final server clippy and the queued Plugin/generated-contract checks remain in progress.Built Debit, Credit, Loan and Tracking for Money. Branch:
job/money-kinds. Head:67731f38310a8776182e6900f10cc9a60d256a4b. Base:268b657451808355c1eecd32bf1c808ac079aa71. Fetched and mergedorigin/devonce, in22a75ab56. No push, deployment or merge into dev/main was performed.Loan debt stays outside Ready to Assign. Debit payments consume the payment Category once. Credit payments move only funded money into Credit reserves. Explicit interest rows and card/Loan EMI transfers preserve the statement amounts. Prepayment uses the same payment rule. Loan creation can write a monthly target. Readers accept cash/card aliases; account writes emit debit/credit and keep all other source bytes. Tracking is unchanged. The implementation uses indexed account and Category lookups.
Files:
docs/DESIGN.mddocs/research/money-plugin.mdcrates/calternal-money/src/budget.rscrates/calternal-money/src/edit.rscrates/calternal-money/src/ledger.rscrates/calternal-money/src/schedule.rscrates/calternal-money/tests/loans.rscrates/calternal-money/tests/vectors.rscrates/plugins/money/src/routes.rscrates/plugins/money/src/views.rscrates/plugins/money/src/tests.rsspikes/money-format/budget_math.pyspikes/money-markdown/money_markdown.pyspikes/money-markdown/test_money_loans.pytests/money/generate-vectors.shtests/money/record_vectors.pycontracts/vectors/money/money.v1.jsoncontracts/openapi.jsonpackages/api-client/src/generated.tsapps/web/src/lib/components/money/AddAccountForm.svelteapps/web/src/lib/components/money/MoneySidebar.svelteapps/web/e2e/money.mjsNo dependency, lockfile or SQL migration change was needed. Loan property tests cover conservation, interest, Credit reserves, mixed-payment order and byte-stable migration. The reference has 712 vectors, plus 500 seeded random ledgers with 2000 month snapshots.
Gates (verbatim output excerpts; all required commands exited 0):
cargo fmt --checkproduced no output and exited 0.cargo clippy -p calternal-money --all-targets -- -D warningscargo test -p calternal-moneycargo clippy -p calternal-plugin-money --all-targets -- -D warningscargo test -p calternal-plugin-moneycargo clippy -p calternal-server --all-targets -- -D warningscargo test -p calternal-servertests/money/differential.shbun run check (apps/web)bun run test (apps/web)bun run build (apps/web)bun e2e/money.mjs (production web build and real server)The API client also passed 9 tests. The built CLI regenerated OpenAPI and TypeScript with
git diff --exit-codeclean; all 308 operation IDs are unique. The official Cargo-based contract check was stopped when the revision stamp triggered a redundant full relink. The existing CLI contains the same API source.Review evidence: 72 original screenshots at 390, 820 and 1440 px, in light and dark. Each kind, Loans navigation, Loan register, Budget, registers, quick entry and empty state are included. Fixtures are synthetic test data only. No screenshot was committed. Original-resolution Loan images were checked for clipping and icon placement. The desktop Loan icon/cap-height centers differ by 0.5 CSS px. Claude must perform the visual review.
Known gaps: general transfer, split and FX editing retain the existing API 422 boundary; statement rows and Loan maths can be read from Markdown. Automatic EMI import suggestions and interest estimates are not implemented. The two existing JSON exact-range differential allowances remain; no Loan allowance was added. Server tests retain two ignored tests. The real-server round covers bounded normal invalid-plan requests; the full hostile-input/storm probe was not run. Separate number review and Claude visual review remain before merge.
Decisions: the monthly payment at creation is optional; interest/principal use a full transfer plus an explicit debt-charge row; overpayment can leave a positive Loan balance outside Ready to Assign; raw parse/serialize remains byte-stable, with aliases normalized on account writes; Tracking accounts are never promoted automatically. These choices are recorded in DESIGN §48 and research §10, with YNAB and Actual citations.
Separate number review targets for #462:
Cleanup:
cargo cleanremoved 15545 files and 7.6 GiB. Web build output was deleted. Review artifacts remain in the worktree and on #462. The worktree is clean. Module and changed-function documentation was re-read before this report.Independent Money account-kind and Loan number review started on
job/money-kinds-review, base67731f38310a8776182e6900f10cc9a60d256a4b. Read CLAUDE.md, CONTEXT.md, DESIGN §48, research §10 and the author report. Review uses synthetic fixtures only. Scope: exact replay, Loan/EMI differential coverage, public projections and existing write-integrity regressions. No UI change planned.Independent account-kind review finding (test commit
f93bb48f4):kind_migration_preserves_whitespace_around_the_valuefails against the author head; raw codec round trips are unchanged.Strict differential fuzz now generates Loan opening debt, Debit/Credit-to-Loan payments, explicit debt charges, both written Credit/Loan EMI directions and exact FX charges (JPY/KWD/CLF) across four months: 500 ledgers / 2000 snapshots passed without allowances. Independent properties for mixed-payment rollover, earlier-month charge edits and EMI FIFO/refunds also passed. All data is synthetic.
Independent account-kind review finding (regression
17ecff840):payment category::accepts a link to an expense Category. That Category can then hold both spending Available and a card reserve. Rustcredit_payment_link_cannot_reuse_an_expense_categoryfails withCredit reserve accepted an expense Category; Python fails withDID NOT RAISE ValueError. Loan already rejects this link.The migration fix is committed at
984fea1ca;ce343db91corrects a case-sensitive lookup caught in my Python token-migration change. Case-insensitive property keys now retain their original spelling and Unicode whitespace. This follow-up has its own failed test first (193218b15).Independent account-kind review found two off-budget Tracking failures in both implementations. Test commit:
fbae001ce.Decision for DESIGN §48, where the Tracking transfer boundary is not stated explicitly: general transfers keep Category totals unchanged, as required; a Debit/Tracking crossing changes Ready to Assign by its exact Debit leg. Loan payment transfers retain their separate linked-Category rule. Credit/Loan EMI reclassification is unchanged. No existing assertion is edited and no allowance is added.
Independent account-kind number review complete on
job/money-kinds-review. Author base:67731f38310a8776182e6900f10cc9a60d256a4b. Reviewed head:9c7555637d020c878e05de407dfdbf07471feb93. Fetched and mergedorigin/devonce (6c87f5ff9); no conflicts. No push, deployment or merge into dev/main.Verdict: the original head had four defects. With the committed fixes, the numbers are correct enough to merge. This verdict covers the Money maths and number/API boundaries. Claude visual review and the wider production security gates remain separate.
Built: byte-preserving account-kind migration; shared Credit/Loan payment-link validation for file projection and direct replay; off-budget Tracking maths; independent Loan/EMI properties; strict Loan/EMI/FX/Tracking differential fuzz; API/core, cleared-register and local HTTP number regressions. Rust and Python receive the same corrections, backed by new independent assertions. No existing test assertion or divergence allowance changed. All 712 author vector inputs and results remain; 20 review cases are added. One deduplicated parse case has a new test-attribution label, with identical input and result.
Findings
kind_migration_preserves_whitespace_around_the_value; test commitf93bb48f4.984fea1ca: replace only the alias token; retain BOM, endings, whitespace, opaque text and identities.credit_payment_link_cannot_reuse_an_expense_category(Rust/Python) anddirect_replay_validates_payment_links_before_indexing;17ecff840/6bb1adc88.3137d3d19: one shared kind/uniqueness validator before either entry point builds maps.tracking_only_rows_cannot_change_budget_money;fbae001ce.b6337f165: off-budget rows change account balances only.debit_tracking_transfers_move_unassigned_money_only, both directions and signs;fbae001ce.b6337f165: Ready to Assign follows the Debit leg; Categories stay unchanged.kind_migration_keeps_case_insensitive_property_names;193218b15.ce343db91. Key spelling and Unicode whitespace remain exact.Review coverage
The strict random corpus has 500 seeded ledgers and 2000 month snapshots. It now includes Loan openings, Debit/Credit payments, explicit Loan charges, both EMI directions, exact foreign Loan charges, Tracking-only gains/charges and Debit/Tracking crossings. No allowances apply to the random comparison. The recorded vector suite keeps the two existing JSON exact-range allowances.
The bounded local HTTP round runs the real Money router over a localhost listener and real calternal-fs data directory. It rejects positive Loan charges, derived Loan overflow and zero/negative targets with 400 and identical files, then accepts a zero charge without changing Budget money. Authentication is supplied by the Plugin test context. It does not test production authentication or the full hostile-input/storm matrix.
Review files
contracts/vectors/money/money.v1.jsoncrates/calternal-money/src/budget.rscrates/calternal-money/src/edit.rscrates/calternal-money/src/ledger.rscrates/calternal-money/tests/loans.rscrates/plugins/money/src/tests.rsspikes/money-format/budget_math.pyspikes/money-markdown/money_markdown.pyspikes/money-markdown/test_money_loans.pytests/money/fuzz.pyNo dependency, lockfile or SQL migration change. No frontend change in this review; web gates and new screenshots were not needed. The upstream merge retains the other jobs' UI changes. Module and changed-function comments were re-read.
Gate output (verbatim excerpts; all six commands exited 0)
cargo fmt --check: no output, exit 0.cargo clippy -p calternal-money --all-targets -- -D warningscargo test -p calternal-moneycargo clippy -p calternal-plugin-money --all-targets -- -D warningscargo test -p calternal-plugin-moneytests/money/differential.shDecisions where DESIGN is silent
kind:: payment, matching Loan and the meaning of a payment Category. Legacy Credit with no link remains readable.Known gaps
Cleanup:
Web build output is absent. Review evidence stays in the worktree. No review artifact was committed.
Starting importer follow-up on branch job/money-import, based on
cd3cea7575(origin/dev). I am tracing the existing Money codec, route and client parity surfaces before adding format adapters. All private Actual checks will report aggregates only.Finding (aggregate-only): the private Actual export has 515 paired cross-budget transfers whose on-budget leg has a Category. A plain Money Transfer marker preserves account balances but loses that Category activity. I added an optional
transfer category::child on the on-budget-to-off-budget transfer and exposed it in the register view. No row-level values are included here.Actual export compatibility finding: current exports store the currency preference under
defaultCurrencyCodeand budget month keys as integer YYYYMM. The adapter now reads that explicit preference and normalizes month keys in SQL. Aggregate differential counts: account month-end 4,902/4,902; category budgeted, activity and balance each 14,250/14,250; Ready to Assign 1/57, still under investigation. No source labels or transaction values are included.Differential finding: an independent replay of Actual’s budget formula shows Ready to Assign also differs from the rendered Money projection in 56 of 57 months. Account month-end and all Category budgeted, activity and balance checks remain exact. The private export has 9 nonzero month-buffer rows and no enabled Category carryover rows. I am treating Ready to Assign as unresolved until the import can preserve the source behavior or report a specific format gap.
Private Actual export aggregate verification update (#462): account_month_end_balance passed 5,160/5,160; category_month_budgeted, category_month_activity, and category_month_balance each passed 15,060/15,060. ready_to_assign passed 2/60 after adding reconciliation rows. This exposes a remaining RTA reconciliation defect; I am investigating it before accepting the importer. No row-level data is included.
The Ready to Assign mismatch is fixed. The correction in one month carries into later months, so each generated adjustment now removes prior corrections from the next month's delta. A two-month synthetic Actual archive test covers this case. The private export differential now passes: account_month_end_balance 5,160/5,160; category_month_budgeted, category_month_activity, and category_month_balance 15,060/15,060 each; ready_to_assign 60/60. Output contains aggregate counts only.
YNAB importer progress (#462), commit
cc303ac281: the public Actual YNAB5 fixture now passes aggregate verification. account_month_end_balance: 14/14; category_month_budgeted, category_month_activity, category_month_balance: 36/36 each; ready_to_assign: 2/2. The importer supports the API data.plan wrapper and the legacy root budget object, including payees, split children, split transfers, Starting Balance rows, and scheduled-transaction gap counts. The public fixture is MIT-licensed and includes its notice and license text. Decision: keep the legacy Inflow: Ready to Assign system Category hidden and report it as a format gap; reconcile the source to_be_budgeted values through Money Ready to Assign. No source row data is included.Finding for #462: the existing MCP Streamable HTTP transport caps each JSON-RPC request at 128 KiB (
MAX_REQUEST_BYTES). The Money MCP preview tool uses the same preview API and keeps the transport bound: base64 source files are limited to 72 KiB total, leaving room for JSON-RPC and multipart overhead. Larger exports remain available through the web, HTTP API and CLI. I recorded this size decision in DESIGN §48; the existing MCP body limit stays unchanged.Finding for #462: the first web suite run failed 1 of 899 tests. The shared date-formatting gate reported
apps/web/src/lib/money/api.tsbecause the new multipart helpers added directIntl.DateTimeFormatcalls. The preview and confirm routes do not use timezone data, so I removed that header and left the existing assertion unchanged. I will rerun the web suite before committing the UI slice.Finding for #462: the first CLI Clippy build rejected the multipart preview method with Rust E0521 because reqwest's multipart field names must be
'static. The CLI only uses the fixed API field namesfile,planandregister, so I tightened the helper signature toVec<(&'static str, PathBuf)>. I’m rerunning the CLI gate.Finding for #462: the first
cargo clippy -p calternal-server --all-targets -- -D warningsattempt stopped before MCP diagnostics because#[derive(RustEmbed)]requiresapps/web/build/, which was absent in the fresh worktree. I’m building the production SPA, then I will rerun the server gate. This is a build prerequisite, not an importer code failure.Private Actual differential rerun for #462: all verification totals still pass. The rerun emitted only aggregate feature identifiers and counts; it did not print source rows.
closed_account_stateaccount_kind_guessed_from_labelunsupported_templatescredit_payment_category_createdunsupported_budget_goal_modesreconciliation_lock_statetransaction_notes_joined_with_payeeThe new production Money E2E exposed a test-timing issue: the expected route was correct, but
assertRenderedRoutecheckedbody.innerText()before the route's ready selector had appeared. The assertion now runs after each route-specific ready signal, so it checks rendered app content after hydration. This was an E2E synchronization defect, not an app error.A production E2E rerun found a second test-only locator defect:
getByLabel("Plan.csv")matched both the file input and the export-format selector option text. The test now selects the twoinput[type=file]controls by order and uploads the synthetic Plan and Register fixtures there.The CSV preview rendered five aggregate check rows, including Ready to Assign, while the new E2E assertion expected four. The verifier output and rendered preview agree on five; I corrected the new assertion to cover account, Category, and Ready to Assign checks.
The production UI flow exposed a preview-cache gap. Four review sheets were opened and cancelled; the fifth preview returned HTTP 429 because the browser discarded its token but the server retained all four prepared imports until the 15-minute expiry. I am adding an owner-bound cancellation action to the API, web UI, CLI and MCP so a dismissed preview releases its prepared files and pending slot immediately. This preserves the existing four-preview resource limit.
The production rerun verified that cancelling the empty-route previews frees their slots. The month-route screenshot sweep itself then hit the existing four-preview cap because the test left each review sheet open after capture. It now exercises Cancel and waits for the sheet to close after every month-route preview, keeping the test owner within the configured cap.
The production E2E reached import confirmation and captured all 60 planned screenshots, but its console check reported two generic resource 404 errors. The browser message omitted request paths, so the E2E now records 404 response paths before I determine whether these are app assets or expected missing routes.
The two 404s were
GET /api/v1/notes/journal/<date>. The app shell checks for an optional Daily note, andreadDayinapps/web/src/lib/calendar/journal.tsmaps a 404 tonullwhen that day has no note. The E2E health check now filters only this known optional lookup and continues to fail on other HTTP 404 responses or browser console errors.git merge origin/devhad one content conflict incrates/calternal-cli/src/main.rs: the Money import CLI parser test and the new Calendar feed/subscription parser test occupied the same insertion point. I kept both test functions.git diff --name-only --diff-filter=Uis empty after resolution.Post-merge E2E finding: the first browser run received HTTP 422 while saving the test account appearance because the request still sent , which origin/dev has removed. The shared E2E harness and server schema confirm the current contract is ; updated the Money visual helper to match and am rerunning the production-build flow.
Correction to the post-merge E2E note: the server returned HTTP 422 because the test request included the removed auto_scheme.location field. The shared harness and server schema confirm the accepted request is auto_scheme with mode only. I updated apps/web/e2e/money.mjs accordingly; the production-build browser run now passes and captured 60 screenshots across 390, 820, and 1440 px in light and dark.
Adversarial finding after origin/dev merge: Money HTTP hostile-input, bounded-size, ownership, cancellation, and request-storm probes passed. The MCP campaign stopped before Money tool calls because origin/dev now advertises 13 tools (including three calendar/task tools), while the shared adversarial probe expects exactly 11. I am preserving that existing expectation and checking the Money MCP operations separately; no Money MCP failure has been observed.
Correction after inspecting the MCP probe: the advertised list contains 13 tools; its expected set contains 11. The two additional names are calternal_calendar and calternal_mail_reader. My prior note incorrectly described three extra calendar/task tools; today and task tools are already in the expected set. The probe stops before any Money MCP call, so the Money HTTP adversarial probe passed while the MCP adversarial flow remains unverified by this run. Server unit tests for Money MCP bounds and route scope passed.
Benchmark finding: the first synthetic profile run completed the 2,000-row samples, 50,000-row preview, and 4-request burst, but report construction failed because the preview helper returned only the API body while callers read latency from that body. The latency sample values were therefore missing. I am updating the helper to retain the browser-measured request latency, then will run the profile once successfully.
Synthetic local debug import profile completed (no comparable Money metric exists in docs/perf/baseline.json): 2,000 rows, 204,671 input bytes, 5 samples p50 1,324.80 ms / p95 2,007.80 ms, 1.064 server CPU seconds per preview, 268,369,920 B peak RSS. 50,000 rows, 5,079,825 bytes: 23,119.40 ms, 15.390 CPU seconds, 446,189,568 B peak RSS. Four concurrent 50,000-row previews: p50 21,914.80 ms / p95 22,476.30 ms, 22,622.50 ms total, 68.020 CPU seconds, 1,030,676,480 B peak RSS. Synthetic YNAB CSV only; local debug build, not the locked performance VM.
Focused Money MCP adversarial continuation passed against a fresh local server. It verified the three Money tools are present, preview checks and byte-stable round trip, owner scope, read/API-only denial, cancel/confirm single-use tokens, 129 KiB transport rejection (HTTP 413), malformed JSON-RPC (HTTP 415, no 5xx), and 48 parallel calls with no 5xx. The original exact global inventory assertion remains unchanged; the focused probe makes the Money lifecycle independently testable.
Forgejo #462 final report
Branch:
job/money-importBase: merged
origin/devHead:
b906b17fe1999f914f96aa3d469e0f21133a0d0bNo push, deploy, or merge to
devwas done.Built
Differential checks
Actual export aggregate results:
Aggregate import size: 86 accounts, 256 categories, 13,257 assignments, 14,725 transactions. Seven format-gap classes were reported.
YNAB public demo fixture results: account month-end balances 14/0; category month budgeted 36/0, activity 36/0, balance 36/0; Ready to Assign 2/0. The fixture is from Actual's MIT-licensed public demo and has its license and attribution alongside it.
Actual format-gap aggregates (feature class: count): closed account state 54; account kind guessed from label 4; unsupported templates 417; credit payment category created 4; unsupported budget goal modes 517; reconciliation lock state 14,624; transaction notes joined with payee 4,131. These are counts only. Actual schedules and unsupported template kinds, YNAB scheduled transactions, and reconciliation-lock state remain format gaps.
Local import profile
Synthetic YNAB CSV on the local debug build; no comparable Money import-preview metric exists in
docs/perf/baseline.json:This was a local measurement, not the locked performance VM.
Production-build browser evidence
EXPECTED empty Daily note lookups: 4PASS captured 60 Money screenshots in /home/kayg/Developer/calternal-wt/money-import/artifacts/moneyThe captures cover empty and imported budget flows at 390, 820, and 1440 px in light and dark. They are attached for the visual reviewer:
Gates (verbatim output excerpts)
cargo fmt --checkexited 0 and printed no output.The four lines above are the successful Clippy summaries for
calternal-money,calternal-plugin-money,calternal-server, andcalternal-cli, in that order.These are the
calternal-moneyunit, integration, property, adjustment, vector, and doc-test summaries.calternal-plugin-moneyand its doc tests.calternal-server.calternal-cliunit and output-contract tests.Adversarial output:
Known gaps and decisions
The shared broad MCP probe still stops at its strict tool-inventory assertion because the merged server advertises two additional unrelated tools (
calternal_calendarandcalternal_mail_reader) beyond its expected 11. That assertion was left unchanged. The focused Money MCP adversarial probe ran separately and passed.Design choices are recorded in
docs/DESIGN.md§48: map off-budget Actual accounts to Tracking unless clear Loan evidence exists and report name-based guesses; import fixed monthly#templatevalues only; report schedules, unsupported goal/template kinds and reconciliation locks as format gaps; represent source Ready to Assign reconciliation with a zero-value hidden adjustment category; keep the 128 KiB MCP request cap with a 72 KiB total source-byte limit; reject amounts that cannot be represented exactly; import into a new Budget and write only after confirmation.Files
Cargo.lock,docs/DESIGN.md.crates/calternal-money/src/{budget.rs,edit.rs,import.rs,ledger.rs,lib.rs,schedule.rs}andcrates/calternal-money/tests/{account_kinds.rs,import.rs,ready_to_assign_adjustment.rs}.crates/plugins/money/Cargo.toml,src/{import.rs,lib.rs,routes.rs,tests.rs,views.rs}, andtests/fixtures/{ACTUAL-LICENSE.txt,NOTICE.md,ynab5-demo-budget.json}.crates/calternal-server/src/mcp.rs;crates/calternal-cli/{Cargo.toml,src/main.rs,src/remote_commands.rs}.apps/web/e2e/money.mjs,apps/web/src/lib/components/money/MoneyImport.svelte,apps/web/src/lib/money/api.ts,apps/web/src/routes/money/+page.svelte,apps/web/src/routes/money/[budget]/[month]/+page.svelte.bench/money-import-462.mjs,tests/adversarial/{mcp_probe.py,money_api.mjs,money_mcp_probe.py,run.sh}.Cleanup completed:
cargo cleanprintedRemoved 18896 files, 11.4GiB total;apps/web/buildandapps/web/.svelte-kit/outputwere removed. The worktree is clean.Starting UI review fixes on job/money-kinds; branch base is
2bd6890290(merge-base with origin/dev). I will keep changes in apps/web and preserve Loan maths from the independent review branch.Finding after the required origin/dev merge: MoneyAccountView exposes balance and payment_category_id; MoneyCategoryView exposes target; MoneyTransactionList exposes rows only. No API field supplies a Loan due date, an interest total or payoff basis. DESIGN §48 also prohibits Credit cycle and payment-plan properties on Loan and does not define a due-date field. I will render the real balance and monthly target and omit unavailable facts rather than infer interest from category names or invent a payoff denominator.
E2E finding: the production Money run reached theme setup, where its helper sent the removed auto_scheme.location field. The merged server returned HTTP 422; the current Appearance input accepts auto_scheme.mode only. I am updating that helper payload and rerunning the production flow.
Completed
docs/perf/baseline.json.No Money maths or API behavior changed.
Screenshots
The production Money e2e captured 78 screenshots at 390, 820, and 1440 px in light and dark. These attached sets cover each changed view. The sidebar rows were also inspected at full screenshot detail.
| UI state | 390 light | 820 light | 1440 light | 390 dark | 820 dark | 1440 dark |
| --- | --- | --- | --- | --- | --- |
| Add Account form | 390 | 820 | 1440 | 390 | 820 | 1440 |
| Balance date picker open | 390 | 820 | 1440 | 390 | 820 | 1440 |
| Loan register summary | 390 | 820 | 1440 | 390 | 820 | 1440 |
| Sidebar groups and icons | 390 | 820 | 1440 | 390 | 820 | 1440 |
Gates
Rust crate gates were not run because this job changed no Rust files.
node --checkpassed for the Money e2e and benchmark scripts.Performance evidence
Local only, on
calternal-devwith load average[25.79, 23.61, 24.83]; no earlier Money Loan register baseline exists, so this is a seed measurement and is not comparable with the isolated perf-test baseline. The one-row case measured sequential p50/p951760.6/2956.6 ms, mean/peak server RSS218542787/231579648 bytes, mean/peak CPU20.39/76%, and an eight-page burst p95 of8772.4 ms. The 501-row case measured sequential p50/p955379.4/8332.1 ms, mean/peak RSS269820259/270905344 bytes, mean/peak CPU18.43/60.23%, and an eight-page burst p95 of22276 ms.Decisions and known gaps
DESIGN §48 and the current API expose the Loan balance and monthly target through its linked payment Category. They do not expose next due date, year-to-date interest, or payoff basis/progress. The summary therefore shows only the available two facts and does not derive the missing values from account names or transaction rows. A positive Loan balance is labeled
Refund dueto distinguish a credit balance; DESIGN does not specify that label.Head SHA:
46682f255f9a9e6c0c5a9ea6e08aeb66a10d857d.Independent Money importer review started on
job/money-import-reviewat baseb906b17fe1999f914f96aa3d469e0f21133a0d0b(#462). Scope: exact source totals, Markdown identity, source adapters, and preview/confirm/cancel lifecycle. Test data is synthetic. No push or deploy.Independent Money import review — #462
Base:
b906b17fe1999f914f96aa3d469e0f21133a0d0b.All inputs below are synthetic. No User financial data is in this report.
Local fixes
High: failed source checks did not prevent publication.
Input: a well-formed CSV whose Plan activity differs from its Register.
Wrong output:
ImportSource::rendersets failed check counts, but thepreview route retains the plan and confirmation publishes it.
Evidence:
routes.rsretained every successfully rendered plan withoutchecking
summary.checks; confirmation did not check them either.Fix: reject mismatched source totals before a pending preview is retained.
Regression:
source_total_mismatch_cannot_be_confirmed.High: owner isolation did not preserve preview state.
Input: a request from a different User for an existing preview.
Wrong output: confirmation denied the request but removed the owner's
pending preview. Cancellation correctly kept it.
Evidence: the combined owner/expiry branch in
confirm_importremovedthe entry in both cases.
Fix: reject an owner mismatch without changing pending state.
Regression: owner can still confirm after a denied access check.
Findings that need a larger change
High: large imports have no working-memory or execution-time bound.
Input: a valid large export within the accepted size caps.
Wrong output: the import creates several full-size representations before
checking the output size. There is no progress signal or import timeout.
Evidence: Actual permits a 512 MiB expanded database; ZIP content is read
into a Vec and copied into SqliteOwnedBuf. Each source table uses fetch_all.
The source model, generated Markdown, Document lines and projected Ledger
coexist during rendering. YNAB JSON first builds a full serde_json::Value;
CSV retains a HashMap for every row. Preview cache permits 32 plans of up
to 128 MiB each, plus concurrent imports before cache admission.
Also, transfer pairing scans later transactions for every transfer.
A byte cap is not a bounded working-memory contract. A 200 MiB database
or 1M-row run is not safe to certify from these bounds on this shared host.
Needs a bounded import worker, admission before parsing, cancellation,
progress/timeout, and streamed or bounded representations. No stress
workload was used to exhaust the host.
Medium: expiry rejects use but does not free idle preview memory.
Input: create a preview, let its 15-minute TTL expire, then make no more
preview requests.
Wrong output: prepared Markdown remains in pending_imports indefinitely.
Evidence: cleanup runs only on a new preview or an owner request for that
token. There is no expiry worker. Needs automatic expiry/reclamation.
High: YNAB verification can manufacture the balance it then checks.
Input: API Account balance differs from the full exported transaction net.
Wrong output: add_api_opening_balances inserts the difference as a starting
balance, and account_balance_snapshots verifies the modified source model.
A missing transaction or incorrect transfer pairing can become fabricated
historical opening money with all checks passing. No gap reports this
inferred opening row. Account balances without transactions or a month
are instead omitted, with only account_balance_without_month reported.
Needs an explicit source-completeness rule and preview disclosure before
treating current-balance differences as historical opening balances.
Medium: conflicting source identities can be silently discarded.
Input: two YNAB Accounts or Categories have one ID and different fields.
Wrong output: Account parsing keeps the first; add_ynab_category returns
early for an ID already in its map. Conflicts are neither rejected nor
reported. Categories may legitimately appear in nested and flat API
lists, so a fix must distinguish identical repetitions from conflicts.
Medium: source export round trip is not implemented.
Input: import a source and request a source-format export to re-import.
Wrong output: no Actual/YNAB exporter exists in the scoped code.
Tests can verify deterministic render and Money Markdown re-import, but
cannot prove Actual/YNAB import → source export → re-import identity.
Review limits
No web code is changed. The adapters all use the same API lifecycle, but the
full browser/CLI/MCP production flow and requested destructive resource
exhaustion probes are not certified by the unit/property checks. Large
workloads and exploitation probes are left unexecuted. Static review confirms
Actual uses SQLx deserialize with read_only=true and no archive path is
written to disk; publication uses calternal-fs below the authenticated Home.
Independent Money import review — #462
Base:
b906b17fe1999f914f96aa3d469e0f21133a0d0b.All inputs below are synthetic. No User financial data is in this report.
Local fixes
High: failed source checks did not prevent publication.
Input: a well-formed CSV whose Plan activity differs from its Register.
Wrong output:
ImportSource::rendersets failed check counts, but thepreview route retains the plan and confirmation publishes it.
Evidence:
routes.rsretained every successfully rendered plan withoutchecking
summary.checks; confirmation did not check them either.Fix: reject mismatched source totals before a pending preview is retained.
Regression:
source_total_mismatch_cannot_be_confirmed.High: owner isolation did not preserve preview state.
Input: a request from a different User for an existing preview.
Wrong output: confirmation denied the request but removed the owner's
pending preview. Cancellation correctly kept it.
Evidence: the combined owner/expiry branch in
confirm_importremovedthe entry in both cases.
Fix: reject an owner mismatch without changing pending state.
Regression: owner can still confirm after a denied access check.
Findings that need a larger change
High: large imports have no working-memory or execution-time bound.
Input: a valid large export within the accepted size caps.
Wrong output: the import creates several full-size representations before
checking the output size. There is no progress signal or import timeout.
Evidence: Actual permits a 512 MiB expanded database; ZIP content is read
into a Vec and copied into SqliteOwnedBuf. Each source table uses fetch_all.
The source model, generated Markdown, Document lines and projected Ledger
coexist during rendering. YNAB JSON first builds a full serde_json::Value;
CSV retains a HashMap for every row. Preview cache permits 32 plans of up
to 128 MiB each, plus concurrent imports before cache admission.
Also, transfer pairing scans later transactions for every transfer.
A byte cap is not a bounded working-memory contract. A 200 MiB database
or 1M-row run is not safe to certify from these bounds on this shared host.
Needs a bounded import worker, admission before parsing, cancellation,
progress/timeout, and streamed or bounded representations. No stress
workload was used to exhaust the host.
Medium: expiry rejects use but does not free idle preview memory.
Input: create a preview, let its 15-minute TTL expire, then make no more
preview requests.
Wrong output: prepared Markdown remains in pending_imports indefinitely.
Evidence: cleanup runs only on a new preview or an owner request for that
token. There is no expiry worker. Needs automatic expiry/reclamation.
High: YNAB verification can manufacture the balance it then checks.
Input: API Account balance differs from the full exported transaction net.
Wrong output: add_api_opening_balances inserts the difference as a starting
balance, and account_balance_snapshots verifies the modified source model.
A missing transaction or incorrect transfer pairing can become fabricated
historical opening money with all checks passing. No gap reports this
inferred opening row. Account balances without transactions or a month
are instead omitted, with only account_balance_without_month reported.
Needs an explicit source-completeness rule and preview disclosure before
treating current-balance differences as historical opening balances.
Medium: conflicting source identities can be silently discarded.
Input: two YNAB Accounts or Categories have one ID and different fields.
Wrong output: Account parsing keeps the first; add_ynab_category returns
early for an ID already in its map. Conflicts are neither rejected nor
reported. Categories may legitimately appear in nested and flat API
lists, so a fix must distinguish identical repetitions from conflicts.
Coverage limit: source export round trip is not implemented.
Input: import a source and request a source-format export to re-import.
Wrong output: no Actual/YNAB exporter exists in the scoped code.
Tests can verify deterministic render and Money Markdown re-import, but
cannot prove Actual/YNAB import → source export → re-import identity.
High: source verification runs after lossy normalization.
Input: Actual transfer legs have dates on opposite sides of a month boundary,
or a split child has no parent in the export.
Wrong output: Actual collapses a transfer to one selected date and computes
verification from that normalized transaction. The other Account leg moves
months. Orphan children are dropped with a format gap, then are absent from
verification. Checks can pass without checking original source postings.
Evidence: actual_source passes
transactions(after pairing and droppingchildren), not raw_transactions, into actual_verification. The function's
doc comment says original rows, but its input is the normalized model.
Needs independent raw-row Account snapshots before normalization and an
explicit policy for transfer legs with different calendar dates.
Medium: Actual payee references become opaque IDs.
Input: a normal Actual SQLite transaction whose description references a
payee ID in the payees table.
Wrong output: the importer reads description directly as visible payee text
and never joins payees. The Money row contains the ID, not the source name;
no format gap reports the missing name.
Evidence: Actual's documented export schema
joins transactions.description to payees.id; import.rs has no payees query.
The importer fixture instead puts the literal visible name in description.
Needs a real source-schema fixture and an ID-to-name lookup.
Review limits
No web code is changed. The adapters all use the same API lifecycle, but the
full browser/CLI/MCP production flow and requested destructive resource
exhaustion probes are not certified by the unit/property checks. Large
workloads and exploitation probes are left unexecuted. Static review confirms
Actual uses SQLx deserialize with read_only=true and no archive path is
written to disk; publication uses calternal-fs below the authenticated Home.
Independent Money import review — #462
Base:
b906b17fe1999f914f96aa3d469e0f21133a0d0b.All inputs below are synthetic. No User financial data is in this report.
Local fixes
High: failed source checks did not prevent publication.
Input: a well-formed CSV whose Plan activity differs from its Register.
Wrong output:
ImportSource::rendersets failed check counts, but thepreview route retains the plan and confirmation publishes it.
Evidence:
routes.rsretained every successfully rendered plan withoutchecking
summary.checks; confirmation did not check them either.Fix: reject mismatched source totals before a pending preview is retained.
Regression:
source_total_mismatch_cannot_be_confirmed.High: owner isolation did not preserve preview state.
Input: a request from a different User for an existing preview.
Wrong output: confirmation denied the request but removed the owner's
pending preview. Cancellation correctly kept it.
Evidence: the combined owner/expiry branch in
confirm_importremovedthe entry in both cases.
Fix: reject an owner mismatch without changing pending state.
Regression: owner can still confirm after a denied access check.
High: prepared files could exceed the Money reader limit.
Input: a generated month or definition file larger than 8 MiB but a prepared
plan smaller than the 128 MiB total import cap.
Wrong output: preview retained it and confirmation published it, but
UserMoney::parse rejected the file. The new Budget could not be opened.
Evidence: routes.rs checked only the total generated size; store.rs enforces
MAX_FILE_BYTES = 8 MiB for every Money file.
Fix: check every generated file against the existing reader limit before
retaining the preview. Keep the existing aggregate cap as a second check.
Regression: import_file_limits_match_the_money_reader checks the boundary
without parsing or sending a large untrusted export.
Findings that need a larger change
High: large imports have no working-memory or execution-time bound.
Input: a valid large export within the accepted size caps.
Wrong output: the import creates several full-size representations before
checking the output size. There is no progress signal or import timeout.
Evidence: Actual permits a 512 MiB expanded database; ZIP content is read
into a Vec and copied into SqliteOwnedBuf. Each source table uses fetch_all.
The source model, generated Markdown, Document lines and projected Ledger
coexist during rendering. YNAB JSON first builds a full serde_json::Value;
CSV retains a HashMap for every row. Preview cache permits 32 plans of up
to 128 MiB each, plus concurrent imports before cache admission.
Also, transfer pairing scans later transactions for every transfer.
A byte cap is not a bounded working-memory contract. A 200 MiB database
or 1M-row run is not safe to certify from these bounds on this shared host.
Needs a bounded import worker, admission before parsing, cancellation,
progress/timeout, and streamed or bounded representations. No stress
workload was used to exhaust the host.
Medium: expiry rejects use but does not free idle preview memory.
Input: create a preview, let its 15-minute TTL expire, then make no more
preview requests.
Wrong output: prepared Markdown remains in pending_imports indefinitely.
Evidence: cleanup runs only on a new preview or an owner request for that
token. There is no expiry worker. Needs automatic expiry/reclamation.
High: YNAB verification can manufacture the balance it then checks.
Input: API Account balance differs from the full exported transaction net.
Wrong output: add_api_opening_balances inserts the difference as a starting
balance, and account_balance_snapshots verifies the modified source model.
A missing transaction or incorrect transfer pairing can become fabricated
historical opening money with all checks passing. No gap reports this
inferred opening row. Account balances without transactions or a month
are instead omitted, with only account_balance_without_month reported.
Needs an explicit source-completeness rule and preview disclosure before
treating current-balance differences as historical opening balances.
Medium: conflicting source identities can be silently discarded.
Input: two YNAB Accounts or Categories have one ID and different fields.
Wrong output: Account parsing keeps the first; add_ynab_category returns
early for an ID already in its map. Conflicts are neither rejected nor
reported. Categories may legitimately appear in nested and flat API
lists, so a fix must distinguish identical repetitions from conflicts.
Coverage limit: source export round trip is not implemented.
Input: import a source and request a source-format export to re-import.
Wrong output: no Actual/YNAB exporter exists in the scoped code.
Tests can verify deterministic render and Money Markdown re-import, but
cannot prove Actual/YNAB import → source export → re-import identity.
High: source verification runs after lossy normalization.
Input: Actual transfer legs have dates on opposite sides of a month boundary,
or a split child has no parent in the export.
Wrong output: Actual collapses a transfer to one selected date and computes
verification from that normalized transaction. The other Account leg moves
months. Orphan children are dropped with a format gap, then are absent from
verification. Checks can pass without checking original source postings.
Evidence: actual_source passes
transactions(after pairing and droppingchildren), not raw_transactions, into actual_verification. The function's
doc comment says original rows, but its input is the normalized model.
Needs independent raw-row Account snapshots before normalization and an
explicit policy for transfer legs with different calendar dates.
Medium: Actual payee references become opaque IDs.
Input: a normal Actual SQLite transaction whose description references a
payee ID in the payees table.
Wrong output: the importer reads description directly as visible payee text
and never joins payees. The Money row contains the ID, not the source name;
no format gap reports the missing name.
Evidence: Actual's documented export schema
joins transactions.description to payees.id; import.rs has no payees query.
The importer fixture instead puts the literal visible name in description.
Needs a real source-schema fixture and an ID-to-name lookup.
Review limits
No web code is changed. The adapters all use the same API lifecycle, but the
full browser/CLI/MCP production flow and requested destructive resource
exhaustion probes are not certified by the unit/property checks. Large
workloads and exploitation probes are left unexecuted. Static review confirms
Actual uses SQLx deserialize with read_only=true and no archive path is
written to disk; publication uses calternal-fs below the authenticated Home.
Independent Money import review — #462
Base:
b906b17fe1999f914f96aa3d469e0f21133a0d0b.All inputs below are synthetic. No User financial data is in this report.
Local fixes
High: failed source checks did not prevent publication.
Input: a well-formed CSV whose Plan activity differs from its Register.
Wrong output:
ImportSource::rendersets failed check counts, but thepreview route retains the plan and confirmation publishes it.
Evidence:
routes.rsretained every successfully rendered plan withoutchecking
summary.checks; confirmation did not check them either.Fix: reject mismatched source totals before a pending preview is retained.
Regression:
source_total_mismatch_cannot_be_confirmed.High: owner isolation did not preserve preview state.
Input: a request from a different User for an existing preview.
Wrong output: confirmation denied the request but removed the owner's
pending preview. Cancellation correctly kept it.
Evidence: the combined owner/expiry branch in
confirm_importremovedthe entry in both cases.
Fix: reject an owner mismatch without changing pending state.
Regression: owner can still confirm after a denied access check.
High: prepared files could exceed the Money reader limit.
Input: a generated month or definition file larger than 8 MiB but a prepared
plan smaller than the 128 MiB total import cap.
Wrong output: preview retained it and confirmation published it, but
UserMoney::parse rejected the file. The new Budget could not be opened.
Evidence: routes.rs checked only the total generated size; store.rs enforces
MAX_FILE_BYTES = 8 MiB for every Money file.
Fix: check every generated file against the existing reader limit before
retaining the preview. Keep the existing aggregate cap as a second check.
Regression: import_file_limits_match_the_money_reader checks the boundary
without parsing or sending a large untrusted export.
High: transaction IDs changed same-day Credit allocation order.
Input: two funded Credit purchases, then a partial payment on the same
date. The first purchase's ID sorts after the second purchase's ID. Refund
the second purchase in the next month.
Wrong output: rendering sorted equal-date rows by ID. The payment consumed
a different purchase, and the refund left a reserve that should be released.
Evidence: transaction_sort_key returned (date, ID), while both source replay
and Money replay preserve file order for equal dates.
Fix: use stable date-only sorting. Keep the source row order on each date.
Regression: import_keeps_same_day_source_order_for_credit_payments_and_refunds
checks the resulting reserve and Account balance across two months.
Findings that need a larger change
High: large imports have no working-memory or execution-time bound.
Input: a valid large export within the accepted size caps.
Wrong output: the import creates several full-size representations before
checking the output size. There is no progress signal or import timeout.
Evidence: Actual permits a 512 MiB expanded database; ZIP content is read
into a Vec and copied into SqliteOwnedBuf. Each source table uses fetch_all.
The source model, generated Markdown, Document lines and projected Ledger
coexist during rendering. YNAB JSON first builds a full serde_json::Value;
CSV retains a HashMap for every row. Preview cache permits 32 plans of up
to 128 MiB each, plus concurrent imports before cache admission.
Also, transfer pairing scans later transactions for every transfer.
A byte cap is not a bounded working-memory contract. A 200 MiB database
or 1M-row run is not safe to certify from these bounds on this shared host.
Needs a bounded import worker, admission before parsing, cancellation,
progress/timeout, and streamed or bounded representations. No stress
workload was used to exhaust the host.
Medium: expiry rejects use but does not free idle preview memory.
Input: create a preview, let its 15-minute TTL expire, then make no more
preview requests.
Wrong output: prepared Markdown remains in pending_imports indefinitely.
Evidence: cleanup runs only on a new preview or an owner request for that
token. There is no expiry worker. Needs automatic expiry/reclamation.
High: YNAB verification can manufacture the balance it then checks.
Input: API Account balance differs from the full exported transaction net.
Wrong output: add_api_opening_balances inserts the difference as a starting
balance, and account_balance_snapshots verifies the modified source model.
A missing transaction or incorrect transfer pairing can become fabricated
historical opening money with all checks passing. No gap reports this
inferred opening row. Account balances without transactions or a month
are instead omitted, with only account_balance_without_month reported.
Needs an explicit source-completeness rule and preview disclosure before
treating current-balance differences as historical opening balances.
Medium: conflicting source identities can be silently discarded.
Input: two YNAB Accounts or Categories have one ID and different fields.
Wrong output: Account parsing keeps the first; add_ynab_category returns
early for an ID already in its map. Conflicts are neither rejected nor
reported. Categories may legitimately appear in nested and flat API
lists, so a fix must distinguish identical repetitions from conflicts.
Coverage limit: source export round trip is not implemented.
Input: import a source and request a source-format export to re-import.
Wrong output: no Actual/YNAB exporter exists in the scoped code.
Tests can verify deterministic render and Money Markdown re-import, but
cannot prove Actual/YNAB import → source export → re-import identity.
High: source verification runs after lossy normalization.
Input: Actual transfer legs have dates on opposite sides of a month boundary,
or a split child has no parent in the export.
Wrong output: Actual collapses a transfer to one selected date and computes
verification from that normalized transaction. The other Account leg moves
months. Orphan children are dropped with a format gap, then are absent from
verification. Checks can pass without checking original source postings.
Evidence: actual_source passes
transactions(after pairing and droppingchildren), not raw_transactions, into actual_verification. The function's
doc comment says original rows, but its input is the normalized model.
Needs independent raw-row Account snapshots before normalization and an
explicit policy for transfer legs with different calendar dates.
Medium: Actual payee references become opaque IDs.
Input: a normal Actual SQLite transaction whose description references a
payee ID in the payees table.
Wrong output: the importer reads description directly as visible payee text
and never joins payees. The Money row contains the ID, not the source name;
no format gap reports the missing name.
Evidence: Actual's documented export schema
joins transactions.description to payees.id; import.rs has no payees query.
The importer fixture instead puts the literal visible name in description.
Needs a real source-schema fixture and an ID-to-name lookup.
Medium: web cancellation can remain stuck after a token is gone.
Input: a preview expires, or confirmation fails because its Budget title
already exists. Then close the review sheet.
Wrong output: confirm consumes the token before publication. The later
cancellation returns 404. MoneyImport.discardPreview catches that error
without clearing preview, so the review sheet remains open. Expired tokens
have the same client result. No Budget is written in the conflict case.
Needs an explicit client policy for an already-gone preview and publication
failures. This is a client-flow finding; no web code was changed.
Review limits
No web code is changed. The adapters all use the same API lifecycle, but the
full browser/CLI/MCP production flow and requested destructive resource
exhaustion probes are not certified by the unit/property checks. Large
workloads and exploitation probes are left unexecuted. Static review confirms
Actual uses SQLx deserialize with read_only=true and no archive path is
written to disk; publication uses calternal-fs below the authenticated Home.
Independent review complete; unresolved contract findings remain. Do not treat this report as merge approval.
Branch: job/money-import-review
Base:
b906b17fe1Head:
60d8eae331Fetched and merged origin/dev once before final gates. No push or deploy.
Built: three 128-case property groups and focused adapter/lifecycle tests; four local fixes (source-total publication gate, denied-User token preservation, per-file reader limit, stable same-day Credit allocation ordering); bounded real-server HTTP checks; extended import performance profile. Original test expectations were not changed.
Files: crates/calternal-money/src/import.rs; crates/calternal-money/tests/import.rs; crates/plugins/money/src/routes.rs; crates/plugins/money/src/tests.rs; crates/plugins/money/tests/import_review.rs; tests/adversarial/money_import_review.mjs; bench/money-import-462.mjs. Merge resolutions also retained both sides in crates/calternal-cli/src/remote_commands.rs, tests/adversarial/mcp_probe.py and tests/adversarial/run.sh.
Gates (verbatim output excerpts; full logs in artifacts/money-import-review):
cargo fmt --check— exit 0cargo clippy -p calternal-money --all-targets -- -D warnings— exit 0cargo test -p calternal-money— exit 0cargo clippy -p calternal-plugin-money --all-targets -- -D warnings— exit 0cargo test -p calternal-plugin-money— exit 0cargo clippy -p calternal-cli --all-targets -- -D warnings— exit 0cargo test -p calternal-cli— exit 0cargo clippy -p calternal-server --all-targets -- -D warnings— exit 0cargo test -p calternal-server— exit 0Bounded local HTTP round (verbatim):
Performance: measured once, local debug build, shared-host load averages 19.73/25.86/26.44. Shared release lacked the import route; no Money baseline exists in docs/perf/baseline.json. No regression comparison or production memory guarantee is claimed.
Decisions: refuse inconsistent totals with generic 400 before retaining a preview; reuse the existing 8 MiB reader cap rather than raise it; preserve source order for equal dates; use bounded local diagnostic measurements because the shared release lacked this route. No dependencies added. Existing proptest 1.11.0 was verified with cargo search. No web source was changed, so web check/test gates were not required; the real production web build completed to support the server harness.
Known gaps and detailed findings follow. Large-input resource exhaustion and exploitation probes were not run. Full web/CLI/MCP end-to-end certification remains outstanding. Findings are also saved in the ignored artifacts/money-import-review/findings.md; artifacts are not committed.
Independent Money import review — #462
Base:
b906b17fe1999f914f96aa3d469e0f21133a0d0b.All inputs below are synthetic. No User financial data is in this report.
Local fixes
High: failed source checks did not prevent publication.
Input: a well-formed CSV whose Plan activity differs from its Register.
Wrong output:
ImportSource::rendersets failed check counts, but thepreview route retains the plan and confirmation publishes it.
Evidence:
routes.rsretained every successfully rendered plan withoutchecking
summary.checks; confirmation did not check them either.Fix: reject mismatched source totals before a pending preview is retained.
Regression:
source_total_mismatch_cannot_be_confirmed.High: owner isolation did not preserve preview state.
Input: a request from a different User for an existing preview.
Wrong output: confirmation denied the request but removed the owner's
pending preview. Cancellation correctly kept it.
Evidence: the combined owner/expiry branch in
confirm_importremovedthe entry in both cases.
Fix: reject an owner mismatch without changing pending state.
Regression: owner can still confirm after a denied access check.
High: prepared files could exceed the Money reader limit.
Input: a generated month or definition file larger than 8 MiB but a prepared
plan smaller than the 128 MiB total import cap.
Wrong output: preview retained it and confirmation published it, but
UserMoney::parse rejected the file. The new Budget could not be opened.
Evidence: routes.rs checked only the total generated size; store.rs enforces
MAX_FILE_BYTES = 8 MiB for every Money file.
Fix: check every generated file against the existing reader limit before
retaining the preview. Keep the existing aggregate cap as a second check.
Regression: import_file_limits_match_the_money_reader checks the boundary
without parsing or sending a large untrusted export.
High: transaction IDs changed same-day Credit allocation order.
Input: two funded Credit purchases, then a partial payment on the same
date. The first purchase's ID sorts after the second purchase's ID. Refund
the second purchase in the next month.
Wrong output: rendering sorted equal-date rows by ID. The payment consumed
a different purchase, and the refund left a reserve that should be released.
Evidence: transaction_sort_key returned (date, ID), while both source replay
and Money replay preserve file order for equal dates.
Fix: use stable date-only sorting. Keep the source row order on each date.
Regression: import_keeps_same_day_source_order_for_credit_payments_and_refunds
checks the resulting reserve and Account balance across two months.
Findings that need a larger change
High: large imports have no working-memory or execution-time bound.
Input: a valid large export within the accepted size caps.
Wrong output: the import creates several full-size representations before
checking the output size. There is no progress signal or import timeout.
Evidence: Actual permits a 512 MiB expanded database; ZIP content is read
into a Vec and copied into SqliteOwnedBuf. Each source table uses fetch_all.
The source model, generated Markdown, Document lines and projected Ledger
coexist during rendering. YNAB JSON first builds a full serde_json::Value;
CSV retains a HashMap for every row. Preview cache permits 32 plans of up
to 128 MiB each, plus concurrent imports before cache admission.
Also, transfer pairing scans later transactions for every transfer.
A byte cap is not a bounded working-memory contract. A 200 MiB database
or 1M-row run is not safe to certify from these bounds on this shared host.
Needs a bounded import worker, admission before parsing, cancellation,
progress/timeout, and streamed or bounded representations. No stress
workload was used to exhaust the host.
Medium: expiry rejects use but does not free idle preview memory.
Input: create a preview, let its 15-minute TTL expire, then make no more
preview requests.
Wrong output: prepared Markdown remains in pending_imports indefinitely.
Evidence: cleanup runs only on a new preview or an owner request for that
token. There is no expiry worker. Needs automatic expiry/reclamation.
High: YNAB verification can manufacture the balance it then checks.
Input: API Account balance differs from the full exported transaction net.
Wrong output: add_api_opening_balances inserts the difference as a starting
balance, and account_balance_snapshots verifies the modified source model.
A missing transaction or incorrect transfer pairing can become fabricated
historical opening money with all checks passing. No gap reports this
inferred opening row. Account balances without transactions or a month
are instead omitted, with only account_balance_without_month reported.
Needs an explicit source-completeness rule and preview disclosure before
treating current-balance differences as historical opening balances.
Medium: conflicting source identities can be silently discarded.
Input: two YNAB Accounts or Categories have one ID and different fields.
Wrong output: Account parsing keeps the first; add_ynab_category returns
early for an ID already in its map. Conflicts are neither rejected nor
reported. Categories may legitimately appear in nested and flat API
lists, so a fix must distinguish identical repetitions from conflicts.
Coverage limit: source export round trip is not implemented.
Input: import a source and request a source-format export to re-import.
Wrong output: no Actual/YNAB exporter exists in the scoped code.
Tests can verify deterministic render and Money Markdown re-import, but
cannot prove Actual/YNAB import → source export → re-import identity.
High: source verification runs after lossy normalization.
Input: Actual transfer legs have dates on opposite sides of a month boundary,
or a split child has no parent in the export.
Wrong output: Actual collapses a transfer to one selected date and computes
verification from that normalized transaction. The other Account leg moves
months. Orphan children are dropped with a format gap, then are absent from
verification. Checks can pass without checking original source postings.
Evidence: actual_source passes
transactions(after pairing and droppingchildren), not raw_transactions, into actual_verification. The function's
doc comment says original rows, but its input is the normalized model.
Needs independent raw-row Account snapshots before normalization and an
explicit policy for transfer legs with different calendar dates.
Medium: Actual payee references become opaque IDs.
Input: a normal Actual SQLite transaction whose description references a
payee ID in the payees table.
Wrong output: the importer reads description directly as visible payee text
and never joins payees. The Money row contains the ID, not the source name;
no format gap reports the missing name.
Evidence: Actual's documented export schema
joins transactions.description to payees.id; import.rs has no payees query.
The importer fixture instead puts the literal visible name in description.
Needs a real source-schema fixture and an ID-to-name lookup.
Medium: web cancellation can remain stuck after a token is gone.
Input: a preview expires, or confirmation fails because its Budget title
already exists. Then close the review sheet.
Wrong output: confirm consumes the token before publication. The later
cancellation returns 404. MoneyImport.discardPreview catches that error
without clearing preview, so the review sheet remains open. Expired tokens
have the same client result. No Budget is written in the conflict case.
Needs an explicit client policy for an already-gone preview and publication
failures. This is a client-flow finding; no web code was changed.
Review limits
No web source is changed. A fresh local server passed a bounded HTTP round:
failed totals return 400 without a preview; cancel writes no Budget;
concurrent confirm and cancel/confirm have one consistent winner. A shared
route-state test confirms that another User cannot consume the owner's token.
Unit and property tests cover exact JPY/INR/KWD conversion, all Account kinds,
splits, transfers, signed and zero amounts, month boundaries, hidden Categories,
cash/Credit overspending, Ready to Assign, and Money Markdown re-import.
Small malformed Actual files return generic errors. A valid Actual fixture
with an unknown table and a benign write trigger still imports read-only.
The full browser review sheet, CLI/MCP lifecycle, six-width/theme screenshots,
time-zone transitions, deleted Category variants, and schedule state are not
independently certified. Deep-nesting, conflicting-ID and NUL cases are not
covered by dynamic rejection tests. The 200 MiB database, 1M transactions and
archive expansion stress tests were not run. Source-format export does not
exist; the round trip checks Money Markdown instead. Static review confirms
Actual uses SQLx deserialize with read_only=true and no archive path is
written to disk; publication uses calternal-fs below the authenticated Home.
The profile was measured once on the local debug server with 2,000-row average
samples and a bounded 10,000-row/four-request larger case. The shared release
server did not expose the import endpoint. The local host was under high load.
There is no Money import baseline in docs/perf/baseline.json. These numbers do
not certify the large-input memory contract or a production regression.
Started the #462 review fix round on
job/money-import-reviewat60d8eae3312dd9f5e21d1f29ac48e25c821cafe4. I read CLAUDE.md, CONTEXT.md, DESIGN §48, andartifacts/money-import-review/findings.md. I am reviewing the existing issue thread now, then I will fix the remaining importer findings with regression coverage and commit each feature slice. No pushes, merges, or deploys.Finding fixes committed as
138a3c87819d4ffe5e3733d3e253a9199cd9ac7c(synthetic fixtures only):transactions.descriptionholds a payee ID; the adapter now resolves ordinary and transfer payees throughpayees.id, and rejects a missing reference rather than writing an opaque ID. The fixture covers both payee kinds. Source: Actual's transfer SQL joinstransactions.descriptiontopayees.idand its API identifies transfers as payees.HobbiesCategories inQuality of Life; its existing success expectation now asserts the specified clear rejection.Regression coverage:
actual_account_checks_use_raw_monthly_source_rows,actual_transfer_legs_cannot_cross_months,actual_orphan_split_children_are_rejected,actual_cents_conversion_never_rounds,actual_export_archive_imports_exact_rows_in_memory, and YNAB identity tests.Gates so far:
cargo fmt --checkpassed;cargo clippy -p calternal-money --all-targets -- -D warningspassed;cargo test -p calternal-moneypassed; plugin clippy and tests passed. I will post complete final output after the remaining changes.Owner confirmed the current behaviour (2026-10-01).
Finding for #462: the first local Actual 1,000,000-row profile exceeded its six-minute outer measurement limit. The synthetic request used a valid generated Actual archive; the local host load average was 20.61, 19.31, 20.56, and the server RSS sample reached 3,192,168 KiB before the run stopped. This first result is not a completed preview or a performance baseline.
I found that source verification kept a raw account posting for every transaction and the renderer kept all row strings while it built the parsed Money Ledger. Commit
f9842fef1aggregates exact Account totals by month, drops the Actual row index before projection, drains rendered rows into each month file, and checks cancellation while it parses and projects generated files. Core and plugin Money clippy and test gates pass after that change. I am rerunning the local synthetic profile after rebuilding the production server.Follow-up performance finding for #462: after the projection memory and cancellation changes, the local debug server still did not return a 1,000,000-row synthetic Actual preview within the six-minute outer profile. The profile canceled through
DELETE /api/v1/money/imports/jobs/{progress_id}; that route returned 204 and the preview request returned 409 with “Import cancelled. No Budget was created.” It sent one preview request and created no Budget. The server peak RSS was 3,071,963,136 bytes, CPU was 278.38 seconds, and load average changed from 14.26/11.72/14.66 to 20.49/18.01/16.61. This is a local debug measurement under a busy host, not a successful 1M preview or a production baseline. I am building a release server to measure the same workload before setting the reported resource bound.Finding 5 update, with the bounded release profile:
docs/perf/baseline.jsonhas no 1M-row Money import profile; p50 and p95 are therefore the same single sample.calternal-fsscratch file and queries it read-only. Rows and later projection work report phase/count progress; job cancellation removes a just-finished preview too.Profile JSON:
artifacts/money-import-review/actual-1m-release.json(ignored, not committed). The measured peak covers this fixture and this local server run; it is not a process-wide hard RSS limit.Time-boxed handoff — importer review fixes (#462)
The job exceeded the owner’s approximately four-hour limit, so I stopped before final gates. This is an incomplete handoff, not a claim that the whole issue is finished. No fetch/merge, push, deploy, or merge was performed.
Built and committed
calternal-fsscratch file, source table reads avoid whole-table fetches, import work has admission limits, time budget, progress, checkpoints, cancellation, and idle expiry cleanup.artifacts/money-import-review/actual-1m-release.json.Commits on
job/money-import-review:138a3c878raw-source verification and identity checksbdd17f632bounded import jobs and progress APIf9842fef1free import rows during projection9f74e46cdcheckpoint totals and projection9f2acdf8dstream Actual database to private scratch3855b2dacadd million-row profile0508c5a53report and cancel imports across clientsCurrent HEAD:
0508c5a535b6a9898775007ac32d06185d0f70e3.Gate results captured before the stop
cargo fmt --all -- --checkexited 0 with no output.cargo clippy -p calternal-money --all-targets -- -D warnings:cargo test -p calternal-money:cargo clippy -p calternal-cli --all-targets -- -D warnings:cargo test -p calternal-cli:cargo clippy -p calternal-plugin-money --all-targets -- -D warnings:cargo test -p calternal-plugin-money:Earlier gates for
calternal-fspassed: clippy; 52 unit and 42 integration tests.bun run checkpassed withsvelte-check found 0 errors and 0 warnings.bun run testpassed withTest Files 140 passed (140)andTests 930 passed (930). A freshbun run buildcompleted with✓ built in 31.20sand adapter-static✔ done(existing module-level directive warnings remain).The server clippy run was interrupted at the time limit (exit 130); no successful server gate output is claimed.
Remaining work
Uncommitted files are
crates/calternal-server/src/mcp.rsandtests/adversarial/money_import_review.mjs. They expose MCP progress/cancel and test malformed job IDs, progress field safety, a bounded cancellation, single-slot admission, and cleanup; commit them only after server gates pass.Still required: merge
origin/devonce, run final per-crate gates forcalternal-fs,calternal-money,calternal-plugin-money,calternal-cli, andcalternal-server; run web check/test and the production Money e2e plus one real-server adversarial round; attach 390/820/1440 light/dark Money screenshots; review comments in touched files;cargo clean; remove web build output; then post the completed final report. These checks and attachments were not completed before the time-box stop. No Money 1M baseline exists indocs/perf/baseline.json; the profile is a local one-sample result.Decision documented in DESIGN §48: merge only identical repeated source identities; reject conflicting identities and duplicate names. No other open DESIGN decision was implemented.
Started the #462 continuation on
job/money-import-reviewat0508c5a535b6a9898775007ac32d06185d0f70e3. I read the repo contract, DESIGN §48, CONTEXT.md, and the prior handoff in this issue. I am addressing the remaining Actual memory finding by removing the full transaction/projection model from the import path, then I will run the requested gates and Money production e2e. No push or deploy.Implemented the Actual production preview as a two-pass prepared SQLite row stream. The first scan keeps monthly account/category aggregates; the second writes one month at a time and compares the source totals with Money through
ImportReplay. The 80,000-row isolated regression passed with its 192 MiB RSS-growth guard and the expected row count. The representative Actual export also matches the legacy adapter's transaction counts, transfer counts, Ready to Assign corrections, and exact account/category checks.Feature commit:
96450b092e6ac9aa21bff60de70975d68bb13587. Money plugin gates passed before this update:cargo fmt --all -- --check, clippy, andcargo test -p calternal-plugin-money(46 passed, 1 ignored; 4 integration tests passed).Finding on #462 after merging origin/dev: the core Money property test still generated four account identities after DESIGN §48 settled on cash, card and tracking. Its reproducible case used one transaction that referenced the missing fourth account, and import rendering returned
transaction Account is missing. I reduced the generated transfer/account space to the three allowed kinds;cargo test -p calternal-money --test importnow passes (9 passed, 0 failed).Finding (#462): after adding the requested bounded first-row sample,
import_preview_is_owner_bound_and_confirmation_publishes_oncefailed because its old assertion required the preview to omit every payee. The preview now exposes the first source payee as a sample field and still omits the private memo; the test asserts both behavior and continues to verify owner binding and confirm-once publication.Finding (#462): the first preview-sample test run showed YNAB CSV normalization joining
Payee — Memobefore sampling, which made the bounded row expose the Memo. I am preserving the source Payee separately through the import adapters so the sample can include a useful Payee field without returning Memo text. The existing test now checks this boundary explicitly.Decision for #462: DESIGN §48 required the first 20 source rows but did not define their fields. The sample shows date, Account name, Payee, exact amount and currency. It omits Memo, caps Account names and Payees at 120 characters, and replaces control characters with spaces. I recorded this in DESIGN §48. Separate source Payee fields keep the YNAB and Actual previews from exposing Memo text.
Finding (#462 benchmark): the first 1M-row local profile returned 404 before preview because this new User has Money turned off by default. The server used the current instance and User defaults correctly; the benchmark did not enable the User plugin. I added the normal
/api/v1/plugins/money/meopt-in to the harness and will take the requested successful 1M-row sample. The failed run peaked at 204,460,032 bytes, but it is not a valid import measurement.Test harness update (#462): the same default-off User state would have made
tests/adversarial/money_import_review.mjsreceive 404s before reaching its import probes. I added the normal Money User opt-in there as well as in the 1M-row benchmark, so both exercise the authenticated production routes rather than the disabled-plugin state.Finding (#462, 1M-row local Actual profile): after enabling Money for the new benchmark User, the preview was cancelled by the harness at 372,099 ms with HTTP 409. The server peaked at 521,777,152 bytes RSS, above the 400 MiB target. The source totals phase reached 1,000,000 rows, but the preview did not finish. I am tracing retained month output and SQLite allocations; the 80k in-process memory guard still passes. Load average was 12.85, 16.39, 16.54 before and 15.41, 16.20, 16.69 after.
Finding after the 1M-row profile: the Actual SQLite query and aggregate checks were streaming, but the route retained every generated month Markdown string in
ImportPlan.files. The profile reached 1,000,000 aggregate rows, then the harness canceled at six minutes with peak server RSS 521,777,152 bytes, above the 400 MiB target.Fix: Actual preview now validates and appends each completed file to a private
calternal-fsscratch spool. Pending previews keep only generated names and byte ranges; confirmation copies one file at a time. Cancel, expiry, failed preview, and failed confirmation release the scratch file through its owner Drop. The isolated 80,000-row test now uses a sink that keeps no Markdown, asserts the plan is summary-only, and checks every month plus the two shared files were emitted. A rooted-spool test checks byte reads, path rejection, duplicate rejection, and cleanup.Gates passed for
calternal-money,calternal-plugin-money, andcalternal-server(fmt and clippy where applicable; test output is being collected for the job report). The 1M profile has not yet been rerun after this fix.1M-row profile after output spooling (local): the Actual ZIP contained 26,888,938 compressed bytes. Peak server RSS was 376,258,560 bytes (358.7 MiB), under the 400 MiB target. Server CPU was 342.74 s. The preview did not return a complete result within the harness's six-minute limit: the harness canceled the job (DELETE 204), and the preview returned 409. The profile recorded 366,704.26 ms latency; load average was 16.39 / 22.60 / 22.12 before and 21.28 / 20.06 / 20.96 after. Progress reached the aggregate and month-file writing stages. No 1M-row baseline exists yet. This confirms the measured memory bound, while max-size completion time remains a slow-path gap under a heavily loaded local host.
Production Money E2E finding:
bun e2e/money.mjsstopped inpreviewCsvImportatapps/web/e2e/money.mjs:138: the existing check expects 5 verification rows, and the real preview rendered 4 (4 !== 5). The synthetic Plan.csv contains only a Food category and no Ready to Assign snapshot.verify_source_totalsemitsready_to_assignonly when the source has such a snapshot, so the fourth row count is consistent with the input; DESIGN §48 says the preview shows verification counts but does not require empty check classes. I left the existing assertion and fixture unchanged under the owner rule. Screenshots captured before the assertion stopped: default-off at 390/820/1440 in light/dark, enabled empty at light 390, and the light 390 import form. The full 3-width/2-theme capture did not complete.Finished #462 on
job/money-import-review.Head:
2f8265a6fa2a8d7ee7d7caaeecdd001f891e4937. No push, deploy, or merge was run by this job. The branch includes the one requiredorigin/devmerge performed before final gates.Built: Actual imports stream SQLite rows and aggregate source totals. The live preview validates each completed Markdown file and writes it to one private
calternal-fsscratch spool. The pending plan holds only names, byte ranges and summary data. Confirmation reads and writes one file at a time. Drop removes the spool on cancel, expiry and failed paths. The isolated 80,000-row probe retains no output text, asserts the plan has no files and checks each month and both shared files were emitted.Files: the branch changes 44 files across
apps/web/,bench/,contracts/,crates/calternal-cli/,crates/calternal-fs/,crates/calternal-money/,crates/calternal-server/,crates/plugins/money/,docs/,packages/api-client/andtests/adversarial/. The final bounded-output commit changedcrates/calternal-money/src/import.rs,crates/plugins/money/src/import.rs,crates/plugins/money/src/import_review_tests.rsandcrates/plugins/money/src/routes.rs.Profile: local 1M-row Actual ZIP (26,888,938 compressed bytes) peaked at 376,258,560 bytes (358.7 MiB), below 400 MiB. It did not return a preview before the six-minute harness limit. The harness canceled the job (204); the preview returned 409. Latency was 366,704.26 ms, server CPU was 342.74 s. Load average was 16.39 / 22.60 / 22.12 before and 21.28 / 20.06 / 20.96 after. The profile recorded aggregate and month-file writing stages. The output is in ignored
artifacts/money-import-actual-462.json. There is no 1M-row baseline indocs/perf/baseline.json.E2E:
bun e2e/money.mjsfailed at its existing CSV verification assertion: actual 4 rows, expected 5. Its synthetic Plan.csv has no Ready to Assign snapshot, andverify_source_totalsemits that check only when the source has a snapshot. DESIGN §48 requires verification counts but does not require empty check classes. I kept the assertion and fixture unchanged under the owner rule. The script captured 8 screenshots before it stopped. They remain in ignoredartifacts/money/.scripts/fj issue comment --helpexposes only text and--body-file; it has no attachment option, so these screenshots are not attached to this issue.Decisions not stated in DESIGN: store staged Markdown in one private
ScratchFile, with an in-memory map of generated names to byte ranges; keep staged ActualImportPlan.filesempty; copy one file at a time on confirmation.Known gaps: max-size preview did not complete before the harness timeout, although measured RSS met the target. The production E2E did not finish because its existing check-count expectation does not match its fixture. Screenshot coverage is partial and attachments are not supported by the available
fjcomment command. The one time-boxed adversarial round passed. No non-SLOW adversarial finding was found.Gate output (verbatim summary lines):
Failed checks:
Independent importer re-check started on
job/money-import-recheck, base2f8265a6fa2a8d7ee7d7caaeecdd001f891e4937. Read CLAUDE.md, CONTEXT.md, DESIGN §48 and the complete #462 comment thread. Scope is tests and measurements only. I will compare each source Account and Category per month, including derived Ready to Assign; measure the 10k/100k/1M streaming curve; test exact conversion, duplicate identities, cancellation and preview lifecycle. Synthetic fixtures only. No product edits, push or deploy.Money importer independent re-check — #462
Reviewed head:
2f8265a6fa2a8d7ee7d7caaeecdd001f891e4937.All fixtures are synthetic. No User financial data is used.
Scope and decisions
This job changes tests and review records only. The review keeps existing test
expectations. Source export round trips are not available. Round trips use the
Money Markdown codec and Ledger projection. Repeating a source conversion must
produce the same postings; a second confirmation must not publish twice.
CSV check count
The production e2e source has one Food Assignment and one Checking expense in
January 2026. It has no Ready to Assign source snapshot. Its expected source
minor units are: Checking -2500; Food Assigned 10000, Activity -2500, Available
7500; all system Categories zero; Ready to Assign -10000. Thus Account total
-2500 equals Ready to Assign plus Available (-10000 + 7500).
An independent test compares all these values after Markdown projection. It
also adds an explicit source Ready to Assign snapshot and checks that this adds
one check class and changes no generated bytes. Test results are pending.
Growth curve
The existing Actual fixture adds a parent lookup index. The production cursor
joins parent, peer, split and payee records and sorts the joined rows. Measure
both the supplied fixture and a copy without its benchmark-specific indexes.
Do not infer quadratic work from one loaded-host timeout. Results are pending.
Findings 1–7 and the earlier fixes
Independent dynamic checks and final verdict are pending.
Confirmed results and new blocker
The unchanged CSV fixture passes the independent Ledger comparison. Adding a
source Ready to Assign snapshot adds the fifth check and changes no generated
bytes. Verdict on the row count: missing RTA check is correct. The existing e2e
count expectation is wrong for its fixture. It is left unchanged.
Blocking: YNAB transaction identity conflicts still change money. Two
identical source transactions with ID
sameand amount 1000 milliunits producetwo Money rows. The Account total becomes 200 minor units instead of 100.
Different amounts (1000 and 2000 milliunits) under that ID are also accepted.
The adapter computes verification from this doubled source model, so all
Account checks pass. DESIGN §48 requires identical source rows to merge and
conflicting fields under one ID to fail. Account, Category and Payee identity
fixes do not cover Transactions.
Independent failure output:
The two new expected-behavior regressions are marked ignored with the explicit
blocker reason. Run them with
--ignored --exactto reproduce the failures.This keeps the test-only review branch usable without changing their assertions
or claiming the defect is fixed. No product change is authorized in this job.
The exact Actual SQL query uses the parent index in the supplied benchmark.
Without those indexes, SQLite uses an automatic partial covering index for the
split join, with a temporary B-tree for sorting. This plan does not show a
per-parent full split-table scan. A separate CSV transfer loop does scan the
full transaction array for each pair. Growth measurements are pending.
Independent #462 growth finding (local debug adapter, synthetic exports):
There is no Money import baseline in docs/perf/baseline.json. Shared host load was 64.65–73.91 in the first two runs. I am building an optimized test executable for the completion curve. The new Transaction identity failures already posted on this issue remain the merge blocker; product code is unchanged in this test-only job.
Independent Money importer re-check: NO-GO.
Branch:
job/money-import-recheck.Reviewed importer:
2f8265a6fa2a8d7ee7d7caaeecdd001f891e4937.Final head:
55dae3ae6c86e269c2f5bcd0cf3390c3c931ede1. origin/dev was merged once before final gates.Built: independent Ledger and source-adapter tests, randomized multi-currency and FIFO refund oracles, random cancellation checks, a real local TCP route round, expiry/spool cleanup tests, retained expected-failure Transaction identity probes, and repeatable 10k/100k/1M profiles. The routes.rs change is test-only. All fixtures are synthetic.
Files owned by this review:
The complete findings, decisions, known gaps and verbatim gate excerpts follow. Cleanup completed with cargo clean; generated benchmark ZIPs and web build output were removed. No push, deployment or merge into dev/main was performed.
Money importer independent re-check — #462
Reviewed head:
2f8265a6fa2a8d7ee7d7caaeecdd001f891e4937.All fixtures are synthetic. No User financial data is used.
Scope and decisions
This job changes tests and review records only. The review keeps existing test
expectations. Source export round trips are not available. Round trips use the
Money Markdown codec and Ledger projection. Repeating a source conversion must
produce the same postings; a second confirmation must not publish twice.
CSV check count
The production e2e source has one Food Assignment and one Checking expense in
January 2026. It has no Ready to Assign source snapshot. Its expected source
minor units are: Checking -2500; Food Assigned 10000, Activity -2500, Available
7500; all system Categories zero; Ready to Assign -10000. Thus Account total
-2500 equals Ready to Assign plus Available (-10000 + 7500).
An independent test compares all these values after Markdown projection. It
also adds an explicit source Ready to Assign snapshot and checks that this adds
one check class and changes no generated bytes. The test passes, including all system Category totals.
Growth curve
The existing Actual fixture adds a parent lookup index. The production cursor
joins parent, peer, split and payee records and sorts the joined rows. Measure
both the supplied fixture and a copy without its benchmark-specific indexes.
Do not infer quadratic work from one loaded-host timeout. Completed results appear below.
Findings 1–7 and the earlier fixes
Independent results appear below. The final verdict is NO-GO.
Confirmed results and new blocker
The unchanged CSV fixture passes the independent Ledger comparison. Adding a
source Ready to Assign snapshot adds the fifth check and changes no generated
bytes. Verdict on the row count: missing RTA check is correct. The existing e2e
count expectation is wrong for its fixture. It is left unchanged.
Blocking: YNAB transaction identity conflicts still change money. Two
identical source transactions with ID
sameand amount 1000 milliunits producetwo Money rows. The Account total becomes 200 minor units instead of 100.
Different amounts (1000 and 2000 milliunits) under that ID are also accepted.
The adapter computes verification from this doubled source model, so all
Account checks pass. DESIGN §48 requires identical source rows to merge and
conflicting fields under one ID to fail. Account, Category and Payee identity
fixes do not cover Transactions.
Independent failure output:
The two new expected-behavior regressions are marked ignored with the explicit
blocker reason. Run them with
--ignored --exactto reproduce the failures.This keeps the test-only review branch usable without changing their assertions
or claiming the defect is fixed. No product change is authorized in this job.
The exact Actual SQL query uses the parent index in the supplied benchmark.
Without those indexes, SQLite uses an automatic partial covering index for the
split join, with a temporary B-tree for sorting. This plan does not show a
per-parent full split-table scan. A separate CSV transfer loop does scan the
full transaction array for each pair. Growth measurements appear below.
Local debug growth measurements
These isolated tests call the production Actual streaming adapter with a
counting sink. They do not include HTTP, authentication, the web app or the
server's idle RSS. Each size has one measurement.
wait4records process CPUand peak RSS. The shared host had load averages from 64.65 to 73.91 during the
first two runs.
docs/perf/baseline.jsonhas no Money import baseline.The 1M scan aggregates all source rows. Its second pass reaches 505,856 rows
and emits 63 files before the time limit. Total child-process elapsed is
306.073 s, including archive reading and cleanup. The preview does not complete.
The 10k to 100k CPU ratio is 8.78 for a 10x size increase (growth exponent 0.94).
This does not support a quadratic Actual matching or deduplication loop for
this fixture. It does not certify all source shapes. The optimized completion curve appears below.
Removing both fixture-added indexes at 100k produces the same 122 files,
12,810,864 output bytes, and passing checks: 45.976 s adapter elapsed,
39.081 s CPU, 24.86 MiB peak RSS. The SQLite plan uses an automatic partial
covering index for split lookup. No per-row full split-table scan appears.
Finding 5 remains partial: CSV transfer pairing is quadratic. The adapter
searches the complete
rowsarray for each unmatched outgoing transfer. Inthis bounded fixture all outgoing legs precede their incoming mates. Thus N
pairs require N full-prefix searches, even when both Accounts and dates match.
The conversion time ratios are 3.14 and 4.08 when pairs double. All three
outputs have the exact expected transfer counts and passing Account checks.
This is a separate slow path from the Actual fixture. Performance alone does
not block a merge. Transaction identity corruption does.
Findings 1–7: independent results
The four earlier local fixes are findings 1–4 in the original importer review.
The same table covers the three findings that required larger changes.
The later Actual fixes also pass independent checks. Raw monthly Account and
Food Category totals match the projected Ledger. A cross-month transfer,
orphan split child and missing payee each produce the required error and leave
no scratch file. A reconciled row stays cleared. Cancellation after the first
validated staging write returns no plan and leaves no private database file.
The original Account, Category and Payee identity regressions still pass.
The Transaction identity policy is incomplete, as the new failures show.
Properties and lifecycle
Assigned, Activity and Available, and Ready to Assign in three months.
They include splits, Tracking transfers and positive expense refunds.
Explicit FX totals set budget-currency amounts; no exchange rate is guessed.
Assign properties pass. They use JPY, INR and KWD source amounts.
19-digit values. USD half cents and out-of-range magnitudes fail; representable
KWD and CLF values stay exact. Reconciled YNAB rows stay cleared.
state. Confirmation after cancellation fails, the Home stays empty, and
admission is released.
produces identical Ledger values. Repeated confirmation publishes once.
Review limits and decisions
All changes are tests, benchmark fixtures, or review records. The routes.rs
change is inside its existing cfg(test) module. No dependency was added;
cargo search proptest --limit 1confirmed the existing version 1.11.0.origin/devwas fetched and merged once, atc4a61e8cf, before the final gates.The perf VM lock was busy on one check. Measurements use the local host.
Actual and YNAB source exporters do not exist. Round-trip and idempotence
checks therefore use deterministic source rendering, Money Markdown re-import,
and confirm-once publication. They do not claim a source-export round trip or
that importing into a different new Budget is a no-op.
The TCP round runs the production Money router with an injected data-scope
User. Full-server authentication, web/CLI/MCP lifecycle and full UI evidence
are not rerun by this test-only job. The original production e2e assertion
still expects five checks and will still stop on its four-check source. No
existing expectation was changed. The issue has no real-Mac check requirement.
The ignored identity tests keep their required behavior and their explicit
blocker labels. Both were run directly after the ordinary gates; each exits
101. This is a test-only handoff for a product fix, not a passing identity check.
The two ignored growth tests run only when their explicit profiling inputs are
set. They are separate from the ordinary crate gates.
Optimized completion curve and verdict
NO-GO. Identical YNAB Transaction IDs double postings. Conflicting
Transaction IDs produce a valid plan. The source checks pass because they use
the changed source model. Reject conflicts and merge identical Transactions
before source totals or normalization. Keep the two failing regression
assertions and enable them after the product fix.
The optimized adapter completes all three Actual fixtures. Each emits 122
files. The 1M result has 1,000,000 Transactions, 20 preview rows, 128,010,864
output bytes and passing monthly checks. It retains no generated files. The
private SQLite scratch directory is empty on return.
CPU ratios are 10.73 and 10.65 for each 10x row increase. The growth exponent
is about 1.03. This supports near-linear work for this Actual source shape,
not quadratic matching or deduplication. SQLite sorting may add N log N work.
The debug build costs about 5.4x more CPU at 100k. Debug work and host contention
explain a timeout without evidence of quadratic Actual work in this fixture.
The measurements do not prove the full server's latency or RSS: the counting
sink excludes generated-file spool writes, HTTP and server idle memory.
The optimized 1M run had load averages 56.36/55.77/54.79 before and
52.28/54.41/54.38 after. The perf VM lock was busy on one nonblocking check;
no measurement ran there. Each size has one sample. No baseline or regression
threshold comparison is possible because the baseline has no import profile.
Logs are in artifacts/recheck/release/. The earlier debug logs remain in
artifacts/recheck/. No review artifact is committed.
The separate CSV bound is still incomplete. Its pairing loop has no
ImportControl check or report, and the preview handler calls it synchronously.
Cancellation and the five-minute limit are not checked inside that loop. The
bounded 8k-row measurement and static inspection are the evidence. No prolonged
or resource-exhaustion request was sent. Replace the repeated full-array scan
with indexed pairing and add bounded progress/cancel checkpoints.
The four-check CSV preview is correct for its source. Fix the e2e count or add
an explicit source Ready to Assign snapshot. The existing expectation and
fixture remain unchanged in this review.
Gates (verbatim output excerpts)
Both explicit Transaction identity probes exit 101 after the ordinary gates.
The normal integration gate lists their known-blocker ignore reasons, and the
two profiling tests are ignored until explicit inputs are supplied. No web or
server gate is required: this job changes no product route or API contract.
The production Money router passes the bounded TCP round in the plugin gate.
Started money-ident on
job/money-ident, baseorigin/job/money-importatb906b17fe; merged the authorized recheck branch at55dae3ae6. Read the full NO-GO findings and #462 report. Scope: Transaction identity before source totals, indexed CSV transfer pairing with cancellation, unchanged recheck assertions, corrected four-check CSV e2e, per-crate gates and locked perf-VM profiles. No push or deployment.Root cause confirmed by inspection:
ynab_api_json_with_controlhas no Transaction ID map before conversion and source-total calculation. The CSV adapter scans the whole register for each transfer mate. The existing API transfer matcher already uses FIFO hash queues; CSV can share it. The two ignored identity regressions are enabled without assertion changes. Added multi-currency identity properties and conflict-field coverage. Decisions: compare all source Transaction fields (including deletion state and split children), identify conflicting JSON rows by one-based array position and CSV rows by record number including its header, never echo source IDs or amounts. CSV rows without source IDs remain distinct. Re-import into the same Budget remains a no-write conflict, consistent with the create-new-Budget import contract. Perf VM is reachable but the first nonblocking lock check was busy; no measurement ran outside the lock.Identity fix committed at
f22ecb489a5d14f8e516fe510b7959853e3685c3. The recheck identical-ID test first failed withleft: 2,right: 1; both recheck assertions now pass without changes. The full plugin suite passed, including the new route test: conflict names rows 1 and 2, no Budget is written, duplicates post once, re-import confirmation returns 409 and keeps all existing Budget bytes unchanged. Multi-currency identity properties passed.Gate excerpts (verbatim):
Next slice: shared indexed CSV/API transfer pairing, bounded checkpoints and perf profiles.
Commit
ae3558469states the merged Transaction count in the production preview. It fixes the authorized CSV check-count expectation from five to four. The e2e uses macOS platform and User-agent emulation at every capture. The real-server adversarial probe now checks identical IDs and amount, memo and deletion conflicts. Property tests cover merged counts and exact transfer totals for JPY, USD, KWD and CLF.Final Money gates after the single origin/dev merge pass: fmt, core clippy/test, plugin clippy/test. Web check has zero errors and warnings. The final web test run passed 1052 tests; one unrelated FileCollection setup hook timed out at 10 seconds. A targeted retry with a 60-second hook timeout passed all five FileCollection tests. No assertion was changed. The production build completed. Server gates and optimized VM profiling are still running.
Local fallback profile complete at head
ae3558469. The perf VM remains locked by another run; the required locked VM profile is queued. The local host load was 125.74/114.79/106.93 at start and 133.95/126.76/115.88 at end. All 18 optimized adapter runs passed, with three samples per size and format. These are synthetic source-adapter measurements, not HTTP or server-idle measurements.For a 10x CSV size increase, CPU grows 11.24x then 9.08x. This supports near-linear work. CSV retains its source records in memory; the 1M-row peak is 1.43 GiB. Actual remains streamed at 60.42 MiB. docs/perf/baseline.json has no Money import baseline, so there is no established import threshold comparison. The prior optimized recheck had one loaded-local Actual measurement per size (2.371/13.865/79.453 seconds, CPU 0.752/8.066/85.938 seconds); host loads differ, so the elapsed values do not establish a regression.
Server clippy passed. Its test gate is still compiling; production browser evidence and one local-server adversarial round follow the server build.
Required perf VM profile complete. All 18 optimized runs passed under
flock -w 14400 /root/perf.lock. The lock is released. No code was compiled on the VM. Load inside the lock changed from 4.58/4.18/4.03 to 1.50/2.64/3.41. There are three samples per source and size; p95 is the nearest-rank maximum of those three samples. These are adapter process measurements, not HTTP or server-idle measurements.CSV CPU grows about 11.5x then 10.5x for each 10x increase in rows. The transfer loop now has near-linear work. The retained-source CSV memory limit remains visible at 1M rows; Actual stays streamed.
docs/perf/baseline.jsonhas no Money import profile or established threshold for this comparison. The old recheck measured Actual on a loaded local host, so it is not a VM baseline.All changed-crate Rust gates and server clippy pass. Server test/build is still waiting on shared-host disk I/O. Production screenshot and bounded local HTTP evidence remain pending.
Validation wait at head
ae35584698. Server clippy passed. The server test/build chain has reached linking but has not returned. The compiler and rust-lld linker have threads in folio_wait_bit_common, with low process CPU use. The job keeps CARGO_BUILD_JOBS=4 and the preset HDD target. There is no compiler or test error in the log. All Money gates and all 18 locked perf VM runs pass. The focused production screenshot checks, bounded HTTP probe and full Money e2e are prepared but need the new server binary. I will report any checks still pending at the four-hour job limit and clean the generated output. No push or deployment was made.Server link failure found after the I/O wait. The server test returned 101:
rust-lld: error: undefined symbol: OrtGetApiBase. ort-sys reports that its prebuilt ONNX Runtime was unavailable becauseCARGO_NET_OFFLINE=truestopped the download and no custom runtime path was set. I added that offline environment setting; this is a build setup error, not a Money assertion failure. I stopped the following build so it would not repeat the same setup error. The retry usesCARGO_NET_OFFLINE=false, builds the production server first, then runs the server tests. No source or dependency version is changed. Money gates and the locked VM profile still pass. Production UI and HTTP evidence remain pending until this build returns.Money identity fix — #462
Branch:
job/money-ident. Head:ae35584698192489d53bea2a5ad72b23d98632c1.Implementation is committed. Validation is incomplete: the production route failure is filed as #943, and the corrected server test run did not finish before the job time limit.
Built
Identical source Transaction IDs with the same parsed fields collapse before conversion and totals. Conflicting fields stop the preview with row positions; no Budget is written. This includes unknown JSON fields, deletion flags and embedded split children. CSV source IDs use the same rule. CSV without IDs keeps separate equal purchases. The existing same-Budget re-import contract keeps published files unchanged. Both recheck identity tests are enabled, with their assertions unchanged.
CSV and JSON use one indexed FIFO transfer matcher. It matches Accounts, date and exact minor-unit amount. Matching and stable compaction check cancellation every 1,024 steps. CPU parsing and Markdown replay run on blocking workers so Progress and Cancel stay usable on a single-thread executor. A dropped request cancels its worker and keeps admission until that worker exits.
The preview states the merged count in plain words. It lists that result separately from omitted features. The authorized CSV e2e expectation is four checks. The e2e now emulates macOS and checks a duplicated Transaction with an inferred opening balance. Properties check exact amounts, duplicate counts, source-field conflicts, repeated transfer keys, zero amounts and reversed legs across currency scales. The local-server probe includes identity conflicts and a no-write check.
Files
crates/plugins/money/src/import.rscrates/plugins/money/src/routes.rscrates/plugins/money/src/tests.rscrates/plugins/money/src/import_review_tests.rscrates/plugins/money/tests/import_review.rsapps/web/src/lib/components/money/MoneyImport.svelteapps/web/e2e/money.mjstests/adversarial/money_import_review.mjsbench/money-import-recheck.shHistory
Atomic implementation commits:
f22ecb489,689961044,b11ae7d07,ae3558469. The branch starts fromorigin/job/money-importand includesorigin/job/money-import-recheck. The single required final fetch and merge broughtorigin/devat440e19dce23040ac8ebaae88f0469b6535b1afcbinto this branch (6a7732fef). Its two media-runtime changes were kept. No push, deployment or merge into dev/main was made. No dependencies or migrations changed. Changed doc comments were read again.Gate output (verbatim excerpts)
cargo fmt --check: no output, exit 0.cargo clippy -p calternal-money --all-targets -- -D warningscargo test -p calternal-moneycargo clippy -p calternal-plugin-money --all-targets -- -D warningscargo test -p calternal-plugin-moneycargo clippy -p calternal-server --all-targets -- -D warningsThe ignored unit memory child runs through its parent test and passed. The three ignored integration tests are profiles. The requested Actual and CSV profiles passed separately.
bun run checkbun run test --maxWorkers=1 --no-file-parallelism --testTimeout=60000targeted FileCollection retry with --hookTimeout=60000The web suite failure was
FileCollection select-all header > keeps one animated check and dash drawing for its three states:Error: Hook timed out in 10000ms.Its targeted retry passed all five tests. No expectation was changed. The production web build passed (Wrote site to "build",✔ done).Performance
All 18 optimized adapter runs passed on root@10.69.69.63 under
flock -w 14400 /root/perf.lock. The lock was released. No code was compiled there. Load inside the lock: 4.58/4.18/4.03 before; 1.50/2.64/3.41 after. Three samples per size; p95 is their nearest-rank maximum. This profile excludes HTTP, Markdown publication and server idle RSS.CSV CPU grows about 11.5x and 10.5x for successive 10x size increases. CSV retains source records; the 1M-row peak is 1.43 GiB. Actual stays streamed.
docs/perf/baseline.jsonhas no Money import profile or threshold. The old optimized recheck was a loaded-local measurement, not a VM baseline; it cannot establish a VM regression. The local fallback also passed all 18 runs; its separate report records host load 125–134.Decisions
UX gaps closed
Duplicate count uses normal preview copy. Identity conflicts give row positions and publish no Budget. Duplicates cannot double totals. Progress and Cancel remain available during CPU work. A disconnected request cancels its worker without releasing admission early. The stale CSV check-count expectation is fixed.
Production checks and known gaps
The corrected production server build passed:
The first
cargo test -p calternal-serverreturned 101:I had added
CARGO_NET_OFFLINE=true. That made ort-sys skip its native runtime, even though the matching runtime package was cached. The retry usedCARGO_NET_OFFLINE=false; the server build passed. Its test retry was stopped at the time limit while dependencies were compiling. Server test success is not claimed.The bounded real full-server probe passed, including the new identical-ID and amount/memo/deletion conflict checks, no-write checks, cancellation and one-winner token races:
The local authenticated Actual HTTP profile passed at 10k rows. One preview took 11.137 seconds. The two-request burst produced [200,429], admitted one job, and completed cleanup with 204. Burst elapsed was 14.478 seconds, CPU 7.2 seconds and peak server RSS 216.77 MiB. Progress was sampled 26 times. This is local data at load 49.95/51.38/51.85 before and 52.64/51.98/52.04 after, not a perf VM HTTP baseline.
The first focused UI run timed out at theme setup. Its retry used the shared theme harness, then timed out waiting for No budget yet. The diagnostic found a blank /money route and this pageerror:
This non-SLOW rendering failure is filed as #943. Its cause is not diagnosed or attributed to the identity change. The broad Money e2e timed out waiting for #route-content on /today before reaching the import assertions. Browser-plugin tools were unavailable; the runs used regular Playwright with the real production build.
macOS-emulated 390px Paper-light failure capture. It shows the blank route; it is not import visual approval. No review image was committed.
UX gaps left
The production import screen cannot be walked until #943 is fixed. Required 390/820/1440 light/dark import screenshot coverage and pointer/touch/keyboard confirmation are incomplete. The full Money e2e must run again. The server test gate must finish with native runtime linking enabled. The required independent second number review still follows before merge.
Cleanup
Web build output and the named synthetic profile archives were removed. The perf VM lock was released and its synthetic archive directory was removed. Git status is clean. Logs, profiles and the failure capture remain under artifacts/ in this worktree.
Finished
devprofile [unoptimized + debuginfo] target(s) in 29m 24srev2-money-ident: NO-GO
Read-only LIGHT review complete. Reviewed
job/money-identheadae35584698192489d53bea2a5ad72b23d98632c1, diff basec4faf184df726a9375ae0c13bdfb6018ac2cf57e, and the earlier NO-GO recheck.Review branch:
job/rev2-money-ident.Review head:
effec0a1845401ed8719af04554de79f2b105c0f.Built: static review records only. Files:
audit-findings.mdandreview-money-ident.md. No product code or test expectation changed.Three atomic documentation commits are on the review branch. No push,
deployment, merge, build, test, server or browser run took place.
Blocking findings
identity validation.
crates/plugins/money/src/import.rs:4219appends separatechildren without an ID check; lines 4294–4307 convert embedded or separate
children without one. A -1000 milliunit parent with two identical -500
children under ID s passes the parent sum check and posts Food Activity and
Available -100 instead of rejecting the incomplete unique split. Ready to
Assign is 0. Conflicting s children (-600 Food/-400 Other) also pass. Account
checks use the accepted parent, so no mismatch blocks publication.
legs across months post twice. Pairing at
import.rs:4672includes the date;unmatched rows each generate both legs. Raw source January a=-100,b=0 and
February a=-100,b=100 becomes January a=-100,b=100 and February a=-200,b=200.
Ready to Assign, Activity and Available stay zero. Account checks at line
729 use the normalized transfers, so they agree with the wrong balances.
Searches before filing found no focused issues for these fixes. Each issue
contains evidence, the DESIGN §48 rule, expected behavior and a regression idea.
The earlier top-level Transaction-ID defect is repaired by the new raw-record
identity check on static inspection. The earlier assertions are unchanged and
active. The new tests do not cover repeated IDs within one child list.
The review table covers identical/conflicting IDs, Memo and cleared changes,
splits, paired transfers, currencies and explicit core FX, refunds, 0.005,
large values, negative zero, repeat import, manual edits and cancellation.
It gives exact expected Ready to Assign, Activity and Available, and the code
trace for each. All amounts are synthetic.
Verification output
git diff --check: exit 0; stdout and stderr are empty (verbatim output isempty). Working tree: clean after the report commits.
Rust and web gates: not run; no output. The LIGHT instructions prohibit them.
No runtime pass or performance claim is made.
Known gaps
Both blockers need product fixes and focused regression tests. Runtime
scheduling, source export round trips, crash recovery during confirmation,
full authentication and UI evidence are not certified by this static review.
No UX gap was closed. Browser, device-width and macOS checks remain with the
implementation and merge round.
Decisions
Use the fixed source head and base recorded above. Apply DESIGN §48 identity
rules to split children. Recommend rejection of cross-month YNAB transfers,
as Actual already does, because one Money transfer cannot keep both dates.
Same-title re-import must preserve the existing Budget; a new title creates
a separate Budget under the existing design. No new product design was added.
The LIGHT override means no dev merge or build-output cleanup in this job.
For the merge round
After fixing #948 and #949 and adding their regressions, run once:
cargo test -p calternal-plugin-money --test import_review: prove top-leveland child identity, exact totals, and transfer-date rejection.
cargo test -p calternal-plugin-money csv_import_keeps_progress_and_cancel_responsive:prove progress and Cancel remain usable during CPU work.
bun e2e/money.mjsfromapps/web, using the prepared production build:prove the full import/register flow.
bun tests/adversarial/money_import_review.mjsfrom the repository root:extend for both findings and prove no invalid or cancelled source can retain
a preview or publish a Budget.
Do not merge the reviewed importer while #948 and #949 remain unresolved.
Starting the #948/#949 blocker fixes on job/money-ident. Original head:
ae35584698. Merged origin/dev once; current base:88aaafa39e. Read DESIGN §48 and the independent review. Add child identity and cross-month transfer regression/property tests before fixing normalization. No dependency or UI changes planned.Root-cause trace (#948): only top-level rows use accept_transaction_identity; child conversion discards ordinary child IDs. Separate children are appended before identity checks and embedded children take precedence. Transfer-only splits also discard any remaining parent amount, so merging a duplicate transfer child must be followed by an explicit parent sum check. Tests now cover equal/conflicting child records, raw deletion/unknown fields, parent ownership and transfer children. Root-cause trace (#949): the shared FIFO index includes the full calendar date, so reciprocal legs in another month stay as two complete transfers. Preserve exact-date matching first, then check remaining reciprocal rows against the existing Actual month guard. No dependency changes; no version lookup needed. Compilation is in progress; no gate result yet.
Build infrastructure finding: the first focused test build exited 101 before running tests. sccache reported "Failed to create temp dir" with a missing TMPDIR in another job worktree (authfix). No source test result was produced. Retrying with RUSTC_WRAPPER empty for this job only, keeping CARGO_TARGET_DIR unchanged and TMPDIR at this worktree/target/tmp. No shared cache or other worktree was changed. The explicit dev merge commit is
88aaafa39; regression assertions are unchanged.Committed the #948 fix as
a8272ff254. Top-level and split child rows share one borrowed identity map; child identity includes its parent and all raw fields. Equal children merge before exact parent-sum validation, including transfer-only splits. Conflicting fields, parent references, top-level collisions and missing parents reject. The six new split tests first failed on the old executable, then passed. Existing assertions are unchanged. The bounded HTTP probe includes embedded/separate duplicate and conflicting children; its real-server run is for the merge round.Focused output, verbatim:
Import review file output, verbatim:
Package formatting and JavaScript syntax checks produced no output and exited 0. Clippy is still checking dependencies; no Clippy result is claimed. Next slice: #949 raw monthly postings and remaining reciprocal transfer pairing.
The #949 source fix is ready for verification. The pre-fix executable failed all three new transfer tests: cross-month JSON/CSV rejection, generated cross-month cases, and same-month differing-date pairing with exact-date priority. Source normalization now captures compact raw Account/month postings before pairing. Exact-date FIFO pairs take priority; remaining reciprocal pairs use the existing Actual month guard. Same-month pairs keep the first source row date and post once. Only one-sided transfers receive a synthetic destination posting. YNAB verification now reuses the shared raw-posting snapshot calculation and adds inferred openings separately; the duplicate normalized snapshot implementation was removed.
Decisions: use the first source row date for a same-month fallback pair, consistent with the existing FIFO identity/order rule and Actual normalization. The current one-date Money format cannot keep both source leg dates. Cross-month fallback pairs reject.
The two new CSV fixture Vec allocations flagged by Clippy were replaced by arrays. No assertion changed. Full Money crate tests are building; formatting/Clippy are in progress. The bounded real-server probe was extended for both blocker fixtures but is reserved for the merge round under the current verification policy.
Fixed the two NO-GO blockers for this round of #462. Head:
e552c4c79f2b9f77c10635c60cb19422826f6519onjob/money-ident. The working tree is clean. No push or deployment was made. The requestedorigin/devmerge was done first (dev SHAc4faf184df726a9375ae0c13bdfb6018ac2cf57e, merge commit88aaafa39e9ef55e737b9712021b743eab3a2df3).Built and commits
a8272ff254343f11b8b5d8237d8a71cdd2d1d22a(#948): use one borrowed source Transaction identity map for top-level rows and embedded/separate children. Child identity includes its parent and all raw fields. Merge equal rows; reject conflicts, deletion-state conflicts, parent-reference conflicts, top-level collisions and missing parents. Check the exact child sum after merging, including transfer-only splits.e552c4c79f2b9f77c10635c60cb19422826f6519(#949): capture raw Account/month totals before normalization. Resolve exact-date FIFO pairs first, then remaining reciprocal pairs. Reject cross-month pairs with the existing Actual month guard. Same-month pairs post once. Use the shared raw-posting snapshot calculation for verification; add inferred openings separately. Only one-sided source transfers need a synthetic destination.Files
crates/plugins/money/src/import.rscrates/plugins/money/tests/import_review.rstests/adversarial/money_import_review.mjsDecisions
UX gaps closed
Duplicate/conflicting split children and cross-month reciprocal legs can no longer produce the reviewed wrong totals. Same-month differing-date pairs post once. Preview math stays tied to raw monthly postings.
UX gaps left and known gaps
No UI component changed in this round. The independent break-the-numbers review is still required before merge. The extended real-server HTTP probe and production UI checks were not run in this job, per the current verification policy. Existing ignored profiling tests remain ignored; the isolated Actual memory guard was run by its parent test and passed. No dependency changed, so no package version lookup was needed. No performance measurement was run because this is a correctness issue.
For the merge round
bun tests/adversarial/money_import_review.mjsfrom the repository root, against the combined build: prove the new invalid fixtures return 400, retain no preview token and publish no Budget; keep owner, cancellation and confirmation checks.bun e2e/money.mjsfromapps/web, with the merge round's production build: prove the import/register flow still works and exact totals reach the UI.Verification
Used
CARGO_PROFILE_DEV_DEBUG=line-tables-only,CARGO_INCREMENTAL=0,CARGO_BUILD_JOBS=4, the preset job target and this worktree'starget/tmp. The first build failed before running tests because shared sccache used a missing TMPDIR in another job. The retry and all final Rust commands used an emptyRUSTC_WRAPPERfor this job only. Two Clippy fixture allocation warnings were fixed by replacing Vecs with arrays; assertions did not change. Re-read the doc comments in all touched files.cargo fmt --check: exit 0, no stdout or stderr.node --check tests/adversarial/money_import_review.mjs: exit 0, no stdout or stderr.git diff --check: exit 0, no stdout or stderr.cargo clippy -p calternal-plugin-money --all-targets -- -D warnings: exit 0. Output verbatim:cargo test -p calternal-plugin-money -- --test-threads=4: exit 0. Output verbatim:cargo clean: exit 0. Output verbatim:Web build output was removed if present. Gate logs remain in
artifacts/money-ident/and are not committed. Head is ready for the independent review; neither blocker issue was closed.Round-2 break-the-numbers review of
job/money-identate552c4c79(static trace only; no build/test run). Full record:/home/kayg/Developer/calternal-wt/rev2-money-ident/review-money-ident-r2.md.Verdict: NO-GO.
import.rs:4736-4753). Example:a→b -1000andb→a +900on the same date (or one leg edited in a CSV). Money gives a=-190, b=+190, the source register says a=-100, b=+90, and every check passes; R/A/V stay (0,0,0). This is the same failure as #949 from a different input. A third leg for one key, or one leg with a known target, also produces it. Fix: reject an unpaired leg with a known target, or follow Actual (a one-sided plain row plus a gap), and do not synthesize into raw deltas.-0rejects; error positions mix lists; leftover.import-*stage folder on a dropped confirm.git log -p.Money import identity: review round 2 fixes (branch
job/money-ident, heada93304738, not pushed)c3264ca9b): a YNAB transfer leg with no reciprocal mate (one leg only, legs with different amounts, or a third leg for one key) now stays a one-sided plain row on its own Account, with anunmatched_transfer_rowsgap. The Actual adapter does the same. No counter-posting is synthesized. For a→b −10.00 / b→a +9.00 the Budget now shows From=−10.00, To=+9.00, which is the source register. Before the fix it showed −19.00/+19.00. The rows have no Category, so they post to Uncategorized. Rejecting the file was the other option. This option was chosen because it keeps every Account equal to the source register and matches Actual.account_month_end_balancevalues.a93304738): a property test (256 cases) makes random raw YNAB registers with splits (0-4 children), split transfer children, exact/same-month/next-month/mismatched/missing mates, equal and changed duplicates, month and year boundaries, and shuffled order. It runs them through the JSON and CSV adapters and compares the result with an oracle that only sums raw rows. The test fails when the synthesized posting is put back, and when the cross-month guard is skipped.-0imports as zero.Regression tests run in both the JSON and CSV adapters: the mismatched pair (with and without API balances), the three-leg key, one leg with a known target, and the same-month preference. Each checks the rendered R/A/V and the Account balances. The new tests fail on
e552c4c79. No existing assertion was changed.Gates (Money crates only):
cargo fmt --check -p calternal-plugin-money -p calternal-money: exit 0cargo clippy -p calternal-plugin-money -p calternal-money --all-targets -- -D warnings:Finished, no warningscargo test: calternal-money 16+4+12+11+1+2 passed. Plugin lib 55 passed, 1 ignored.import_review27 passed, 3 ignored, 0 failed.The fixes need a new independent review before merge.
Money import identity review, round 3 (job/money-ident @
a93304738): GORead-only break-the-numbers review of
c3264ca9banda93304738. Full report:calternal-wt/rev2-money-ident/review-money-ident-r3.md.Fixed and traced on CSV and JSON, with and without API balances, both leg orders, Dec 31 / Jan 1:
-0row amount) and P3-3 (same-month mate first) are fixed.Follow-ups (do not block):
-0inbalance/to_be_budgetedsilently skips that check.Question for the owner (product, not a defect): a transfer leg with no matching leg can (1) import as a one-sided Uncategorized row with a preview gap (current branch; it can lower next month's Ready to Assign), (2) block the import with a clear message (real YNAB exports always pair), or (3) import, but only after the User explicitly acknowledges it at confirm, as for inferred opening balances. Which one?
Owner decision (2026-10-03): a transfer leg with no matching leg stays a one-sided plain row on its own Account with its payee kept and the gap shown in the preview (current behaviour). YNAB exports JSON; real YNAB budgets (including the public demo) must import successfully.
Job
money-ident, branchjob/money-identatd168cf307. Only aggregate numbers are given here. No owner data is in this comment or in the repository.1. Duplicate names (owner decision, 2026-10-03). The source ID is the identity. When two Accounts or two Categories have the same name (also in one group), the importer now keeps both, with their own block IDs and their source names. It does not merge or rename them, because Money links use block IDs. Duplicate IDs are still an error. In Actual, a payment Category that is found by name now links to one Card only. The public YNAB5 demo (two Hobbies Categories in one group) is a success test again. It checks Ready to Assign, Assigned, Activity, Available and the Account balances against the fixture JSON, through a separate projection of the rendered files (36 Category-month rows, 2 months, 2 Accounts: all equal).
2. Owner's Actual export, compared with Actual's own numbers. I used the live staged preview path (
actual_zip_plan_staged_with_root_and_control), then projected and calculated the rendered files. The oracle is@actual-app/api26.9.0: it imports the same ZIP, then givesgetBudgetMonthandgetAccountBalanceat each month end.3. Transfers. All transfers in the export are linked transfers. There are 2946 live legs with
transferred_id. All are reciprocal, all have opposite amounts, none crosses a month and none is dangling. Each leg uses a transfer payee, so Actual shows a transfer payee on both sides. No live transaction uses a transfer payee without a link, and no live transaction uses a plain payee whose name looks like a transfer. calternal writes 1473 linked transfer rows (one for each pair) and 515transfer categorymarkers (the on-budget to off-budget legs that have a Category). It pairs only the legs that Actual links, and all balances above are equal.Verdict: GO on the real data.
Break-the-numbers review r4 of
job/money-ident(d168cf307, read-only): GO. No P1 found.I traced every number path after the change. Each one resolves Accounts and Categories by source ID, then by Money block ID. This covers the core render, references, assignment anchors, verification, ledger/codec, routes, views, MCP and CLI. I found no name lookup that can cross-post Assigned, Activity, Available or Ready to Assign between two Categories with the same name. The YNAB5 demo test checks the fixture JSON against projected Markdown, so its oracle is independent of the importer. The Actual Card link is deterministic (
ORDER BY sort_order, id).P2 (these do not block the merge):
routes.rs~786 andMoneyImport.svelte:228). Two Accounts with the same name give duplicate Svelte keys, and the User confirms two rows they cannot tell apart. Key by block ID.P3: the transaction form shows the same names with no hint; the Actual payment link moves if the User reorders the Cards; two "Visa" payment Categories with one Card cause a hard failure; YNAB matches prefix names before exact names; the CSV register name fallback walks a HashMap.
Full review:
calternal-wt/rev2-money-ident/review-money-ident-r4.md