COLLAB: live document history — benchmark the cheapest footprint, then build (§61) #975
Open
opened 2026-10-03 06:38:49 +00:00 by kayg
·
66 comments
No Branch/Tag specified
dev
wip/previewcard-1098
wip/palette2-1123
wip/palette-1093
wip/onboard2-1141
wip/onboard-1141.aborted-early
wip/onboard-1141
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
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-r
wip/directory-1199
wip/delete-1119
job/merge30
wip/collabrev-1197
wip/collabloss2-1197
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/rev2-webperf
wip/rev2-money-ident
wip/restyle-mailmoney
wip/restyle-files
job/restyle-notes
job/tagperf-1186
job/adv-1202
job/notifloop-1194
job/restyle-mailmoney
job/onboard-1141
wip/restyle-notes
job/segmented-1200
job/hide-1153
wip/notifloop-1194
job/txentry-1198
job/collabloss-1197
job/perf-1124
job/perf2-1124
job/tocrail-1191
job/restyle-settings
wip/restyle-settings
wip/segmented-1200
job/restyle-files
job/tagdnd-1187
job/cards-1179
wip/cards2-1179
wip/cards-1179
wip/tocrail-1191
wip/tagdnd-1187
wip/perf-1124
wip/merge30j
job/wizchoices-1140
wip/wizchoices-1140
wip/restyle-1190
job/moneyfmt-1180
wip/moneyfmt2-1180
wip/moneyfmt-1180-r
wip/moneyfmt-1180
job/pillglass-1189
job/flags-1181
wip/flags-1181
job/restyle-1190
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/proflog-1204
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#975
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?
Owner decision (2026-10-03, #509 grill C1)
"Agreed but this needs to be benchmarked for the utmost cheapest footprint." Contract:
docs/DESIGN.md§61. Context: §60 (Canvas), §10 (agents).Goal
One history primitive for live (collaborative) documents. Canvas uses it first, then Notes. It must have the smallest disk, CPU and memory cost we can measure.
Phase 1: measurement (do this first; it decides the design)
Follow the measurement protocol: perf VM
root@10.69.69.63vianetbird ssh, holdflock /root/perf.lock, HDD-backed path, interleaved runs, at least 5 repetitions, median and p95.Write the decision rule in the issue before any run. Suggested rule: pick the candidate with the lowest disk bytes per hour of editing, unless its p95 restore or undo exceeds 50 ms on the 5k-element canvas or its peak RSS exceeds 2x the baseline.
Candidates (all need a licence compatible with AGPL-3.0-only):
yrs(crates/calternal-collab): an update log plus periodicencode_state_as_update_v2snapshots. Test v1 versus v2 encoding, zstd on and off, and snapshot interval N ∈ {100, 500, 2000}.yrswith deleted content dropped from snapshots (gc on) versus kept (gc off, needed for Restore). Measure what Restore costs under each.Workloads (generated, deterministic seeds, in
bench/):For each candidate and workload, measure:
Post the table and the chosen design on this issue. Then stop, and wait for review before Phase 2.
Phase 2: implementation (after review)
calternal-collab(reuse gate: extend, do not duplicate) with the chosen encoding. The author tag comes from the collaboration event path. There is one path for the web, API, CLI, MCP and WebMCP (§60).calternal-fsand the Index. Never build paths from strings. Writes are crash-safe: a torn tail is dropped on open, never fatal.bench/with the Phase 1 workload. Regression threshold indocs/perf/.Gates
cargo fmt --check,cargo clippy --all-targets -- -D warnings,cargo testfor the touched crates. Quote the output verbatim. Comments are docs: the module doc explains the encoding choice and cites this issue's numbers.Phase 1 prep: research (2026-10-03)
Decision rule and shortlist below are the starting point; the measurement job confirms figures marked unverified before quoting them.
#975 Phase 1 research: history primitive for live documents (DESIGN §61)
Read-only research, 2026-10-03. No builds or runs. Numbers marked "published" come from the cited sources; every other figure is an estimate that Phase 1 must measure.
0. What the code does today (crates/calternal-collab)
yrs = "0.28.0"(workspace Cargo.toml:67), featuresync. Client:yjs ^13.6.33(apps/web, packages/editor).stored.rskeeps one row per Note in the SQLite tablenote_collab_state(user_id, note_id, etag, epoch, seed, state).stateis the fullencode_state_as_update_v1(&StateVector::default())(v1, defaultDocoptions, so gc is ON).The row is a cache keyed by the Markdown etag; a changed file drops it (new epoch). Size cap 16 MiB.
session.rswrites v1 everywhere (sync frames, flush, conflict shadow). It already reads YjsUndoManager markers from clients (paste/undo capture tests near session.rs:2013-2160), so
origin-aware handling exists in the code.
stored.rs(reuse gate), do not add a parallel store. The table must move behindcalternal-fsrows" and "append-only segment file" for the winning encoding, because SQLite page overhead
(4 KiB pages, overflow pages for >~4 KB blobs) can dominate small-update storage.
1. yrs / Yjs facts
1.1 v1 vs v2 encoding
larger for small updates (single keystrokes) and better for large updates (whole document);
slightly slower to encode. Published figure: v2 has ~50% overhead over the UTF-8 text on the
text-trace datasets. Extreme published case: one transaction with 100k repetitive ops,
v2 = 62 B vs v1 = 1,983,505 B (synthetic, not representative).
Sources: https://discuss.yjs.dev/t/how-efficient-is-updatev2-encoding/1148 ,
https://discuss.yjs.dev/t/is-ykeyvalue-still-necessary-with-yjs-update-version-2-optimizations/3516
probably smaller as v2. Measure both per role; do not assume one encoding for both.
zstd only pays when updates are batched (one compressed frame per N updates or per idle
flush) or with a trained dictionary. Add both to the matrix.
1.2 gc on/off and Restore
Snapshot= state vector + delete set only; it carries no content.createDocFromSnapshot/ yrs
encode_state_from_snapshotrebuild a past state from the current doc, so the doc mustkeep deleted content: gc must be off on every replica for the whole life of the doc
(published: https://discuss.yjs.dev/t/how-to-restore-the-document-by-snapshot/3815 ,
https://discuss.yjs.dev/t/how-to-recover-to-the-specified-version/2301). In yrs this is
Options { skip_gc: true, .. }.GC range. For typing traces (automerge-paper: 182k inserts, 77k deletes, 105k final chars) that is
roughly +30-75% of content bytes kept forever, plus live memory in the server doc and in every
browser tab. For Canvas (Excalidraw elements as Y.Map values, LWW per key) each move/resize
overwrites a map value; with gc off every overwritten value (whole element props, freehand point
arrays of 200 points) stays in the doc. This is the expensive case: 1 h of moves on 5k elements
may grow the live doc by orders of magnitude. Measure live-doc bytes and RSS with gc off.
An update log with periodic full-state checkpoints gives Restore with gc ON:
Restore(P) = load the newest checkpoint <= P, replay logged updates up to P into a temporary doc.
The checkpoint is an exact state at its point (gc loses only deleted content, which is not part of
that state). The live doc and clients stay gc on. Restore is then applied to the live room as a
forward edit (diff restored content vs current, apply as new ops), so clients never reload and
there is no epoch change. Cost: Restore time = checkpoint decode + replay of <= N updates, so N
bounds restore latency. Retention folding = delete log rows between kept checkpoints (coarser
points, never wrong state).
292,742 B (largest), Loro shallow snapshot 63,352 B (54,517 B compressed). Source:
https://loro.dev/docs/performance/native , https://cn.loro.dev/docs/performance/docsize
(site returned 403 to fetch; figures via search excerpts, verify before quoting in the issue).
2. Alternatives with AGPL-compatible licences
checkout(frontiers), shallow snapshots (git shallow clone), built-in UndoManagerSources: https://www.npmjs.com/package/loro-crdt , https://loro.dev/blog/v1.0 ,
https://automerge.org/blog/automerge-3/ , https://docs.rs/diamond-types .
update to the yrs room, observe events (map key changes, text deltas), replay them into a Loro or
Automerge doc with the author as peer. Costs: two CRDT docs in memory per room (>= 2x RSS),
per-update translation CPU, IDs that do not match Yjs IDs (so per-author undo results must be
translated back to Yjs ops), and a second source of truth that can drift. The live state is
already in yrs, so the mirror adds cost on every edit to save bytes only in history.
Recommendation: measure Loro standalone (history bytes, checkout time) as a reference point
only; reject the mirror unless Loro is >3x smaller AND the yrs log fails the latency rule.
automerge-prosemirror. No maintained Excalidraw binding for Loro or Automerge found; Canvas would
need a new binding. Also sync server, awareness, epoch/seed logic in stored.rs, offline tabs,
the conflict shadow, adversarial tests. Estimate: a multi-week rewrite of calternal-collab plus
editor bindings. Count it as a fixed cost in the decision; it should need a decisive win.
3. Per-author undo
UndoManagerwithtracked_originsis a selective undo: it undoes onlytransactions with tracked origins, and works after other origins edited (removes the tracked
inserts by ID, re-inserts the tracked deletes, keeps other edits). yrs has the same
(
yrs::undo::UndoManager,Options { tracked_origins, capture_timeout_millis, .. }).Docs: https://docs.yjs.dev/api/undo-manager
agent turn ID) by the server, from the collaboration event path.
https://discuss.yjs.dev/t/is-there-a-way-to-revert-to-a-specific-version/379):
UndoManagertracking origin = turn ID,capture_timeout = 0;undo(); encode the temp doc diff vs the live state vector; apply to the live room as a normalupdate (no client reload, no epoch change).
Restoring deleted content works because the temp doc replays from a checkpoint where that content
still exists; the live doc can stay gc on.
itself (it would still revert a moved element's other keys). For Canvas, do element-level
logic: from the log, the set E_turn of element IDs the turn touched; the set E_later of element
IDs that later rows from other authors touched; revert E_turn \ E_later to their pre-turn value
(from the checkpoint replay), report E_turn ∩ E_later. Element = Excalidraw element ID (§60). For
Notes, use UndoManager, then report blocks (block IDs) where later foreign edits overlap.
Alternative without UndoManager: keep a clientID -> author map per room; walk the turn's update
(items + delete set by ID) and build the inverse directly. Cheaper in RAM, more code; benchmark
only if UndoManager misses the 50 ms rule.
separable by ID.
4. Benchmark plan (proposal for the issue; post before any run)
4.1 Decision rule (write on the issue first)
p95 Restore <= 50 ms, p95 per-author undo <= 50 ms, p95 cold open <= 50 ms,
peak RSS <= 2x baseline (baseline = today's yrs, gc on, no history, same workload),
undo correctness = 100% on the checker, live client doc not grown (gc stays on unless the
candidate needs gc off, in which case add browser heap growth to the RSS limit).
median of 5).
yrs candidate, because the client rewrite is a fixed multi-week cost.
4.2 Workloads (bench/, deterministic seed)
~1 op/s bursts): moves, resizes, freehand strokes of 200 points, text edits; author A3 = agent adds
300 elements in one turn at t=20 min; then 10 min of edits by A1/A2, some touching agent elements.
them), 2 authors, one agent turn rewriting a section.
writes, y-prosemirror XML ops), recorded once to a fixture, then replayed identically into every
candidate.
4.3 Metrics table (one row per candidate x workload; median and p95 of >= 5 interleaved runs)
4.4 Candidate configurations
Y = yrs 0.28 log + checkpoints, author tag per row, gc ON live doc:
merge_updates_v1) per author per idle window before write (fewer, larger rows; loses intra-window points)G = yrs gc OFF + Yjs
Snapshot(state vector + delete set) per history point, Restore viaencode_state_from_snapshot:L = Loro 1.x standalone reference (MIT): full history snapshot, shallow snapshot + update blocks,
checkoutfor Restore, Loro UndoManager per peerA = Automerge 3 standalone reference (MIT):
save()+ incrementalsave_incremental, view at headsJ = lower bound: JSON element diffs (Canvas: changed elements per update; Note: text patches) + zstd, CRDT only for live sync
Optional: Y-inv = Y2 with clientID -> author inverse undo (only if Y2 UndoManager misses 50 ms)
4.5 Expected outcome (hypothesis, to be tested)
Y2 (v1 batched + zstd rows, v2+zstd checkpoints, gc on, N ~500) should land near J on disk, pass
the latency rule because replay is bounded by N, and keep the browser untouched. G1 likely fails the
RSS rule on Canvas (overwritten freehand points kept forever). L may be smallest on disk but loses
on rule 4 (client switch, no Excalidraw binding).
Phase 1 decision rule (posted before any benchmark run)
I am starting Phase 1 on branch
job/hist-975, based onorigin/devat48c94c9776660cee105be86c5a6ace90dd425367.The decision rule is:
The measured shortlist is Y1–Y4, G1, L and J from the research comment. Measure Y1 and Y2 at N={100,500,2000}; set Y3 and Y4 to the best Y2 interval. Use one deterministic edit trace per workload for all candidates. Report bytes on disk, bytes/hour, append CPU/update, checkpoint/snapshot time, restore latency and peak RSS, agent-turn undo latency and peak RSS plus correctness, cold open, live room RSS, and encoded bytes sent to a new client. The 50 ms and 2x limits apply to the Canvas and Note workloads; all values are summarized with median and p95.
Decision-rule amendment before benchmark runs: for every Y1–Y4 configuration, measure both SQLite BLOB rows and append-only segment storage on the HDD path. Count SQLite main, WAL and shared-memory files while the database is open, then also record the compacted size after retention folding. Count segment headers and metadata as well as payload bytes. Keep storage setup, filesystem, sync policy and retention points the same across repetitions. G1 uses its snapshot representation; L and J use their native representation and the same retention points. Report backend separately in the result table. This resolves the SQLite-vs-segment requirement in the research comment; all eligibility and winner rules in the prior comment still apply.
Phase 1 research update: current registry metadata confirms
yrs0.28.0 (workspace pin),loro1.16.2 (MIT), andautomerge0.12.0 (MIT). Official Loro docs describe shallow snapshots that discard older history andcheckoutfor prior versions; current Automerge Rust docs exposesave_incrementalandfork_at. I will benchmark Loro as the one required alternative engine; Automerge remains a documented comparison, not an additional candidate, to keep the shortlist bounded. Sources: https://docs.rs/loro/1.16.2/loro/struct.LoroDoc.html , https://docs.rs/automerge/0.12.0/automerge/struct.AutoCommit.html , https://loro.dev/docs/performance/docsizeProgress finding: the deterministic traces and smoke checks pass, but a run-path audit found that the runner forced middle/end checkpoints for every candidate. That removed replay work from Restore and made N comparisons invalid. I am changing Restore to load the nearest prior persisted checkpoint and replay only the required stored updates before starting the matrix.
Progress finding: the fixture's foreign edits to agent-touched items were originally placed after the undo probe, so it could not exercise the required skip/report behavior. I moved C1 overlaps into minutes 20–30 and N1 overlaps into minutes 60–70, and aligned each undo probe to the end of that ten-minute window. N1 now uses Yrs XML paragraph nodes with per-character XmlText appends in bursts; the JSON lower bound stores Note suffix patches. The small C1 and Note smoke traces pass Restore and undo checks across the Yrs stores, Loro, and JSON.
Matrix audit finding:
choose_y2usedcanvas_bytes + note_bytes / 2, which differs from the posted average(canvas_bytes + note_bytes) / 2and could select the wrong interval/backend. It also ranked disk first inside the 10% tie band instead of using the required CPU then RSS tie-breakers. Corrected the formula and tie handling in commit9639b1d58;python3 -m py_compile bench/live-history/run-matrix.pyandgit diff --checkpassed. No perf runs have started.First completed sample (n=1; not an eligibility decision): C1 Canvas, Y1-N500, SQLite, repetition 1, perf VM load average 0.68 inside
/root/perf.lock. Open history bytes: 189,054,976 B; after current-point fold: 17,895,424 B. Restore start/middle/end: 9.48/30.12/46.51 ms; cold open: 29.15 ms; undo: 15.66 ms, correct, with 20 later-edited elements skipped. Snapshot mean/max: 3,648.49/15,620.89 ms. Peak process RSS: 171.6 MiB; live room RSS: 136.4 MiB. One run is not enough to decide the candidate; the matrix continues.Second completed sample (n=1; provisional): C1 Canvas, Y1-N2000, segment, repetition 1, load average 2.00 inside
/root/perf.lock. Open bytes: 61,255,680 B; after current-point fold: 8,888,320 B. Restore start/middle/end: 14.75/109.45/133.92 ms; cold open: 138.88 ms; undo: 18.07 ms, correct, with 20 later-edited elements skipped. Snapshot mean/max: 1,434.77/3,953.13 ms. Peak process RSS: 165.2 MiB. The one-sample restore and cold-open times exceed 50 ms. Same-run baseline: cold open 23.09 ms, peak RSS 123.9 MiB. This does not establish p95 eligibility.Third and fourth completed samples (each n=1; provisional): Y1-N500/segment repetition 1. C1 load 2.00: open bytes 175,648,768 B; after current-point fold 8,888,320 B; restore start/middle/end 13.20/25.94/294.73 ms; cold open 38.15 ms; undo 14.02 ms, correct, 20 skipped; snapshot mean/max 428.47/2,136.53 ms; peak RSS 146.4 MiB. N1 load 1.92: open bytes 2,146,304 B; after fold 73,728 B; restore 2.09/4.33/7.46 ms; cold open 8.17 ms; undo 3.27 ms, correct, 20 skipped; snapshot mean/max 109.60/531.44 ms; peak RSS 14.9 MiB. For C1, the single segment-backend restore-to-end result exceeds 50 ms; this remains provisional at n=1.
Further provisional sample (n=1): Y1-N100/SQLite. C1 load 1.99: open bytes 781,778,944 B; after current-point fold 17,899,520 B; restore start/middle/end 9.09/27.69/48.95 ms; cold open 30.63 ms; undo 16.80 ms, correct, 20 skipped; snapshot mean/max 461.91/2,173.89 ms; peak RSS 171.2 MiB. N1 load 2.00: open bytes 7,163,904 B; after fold 5,795,840 B; restore 1.51/3.24/5.83 ms; cold open 7.68 ms; undo 5.75 ms, correct, 20 skipped; snapshot mean/max 51.58/539.42 ms; peak RSS 19.2 MiB. The C1 storage total is high despite the one-sample restore staying below 50 ms; these are not p95 estimates.
Matrix finding: the C1 Y2-N100/segment run (repetition 1) failed with
Error: entry already exists. The trace has 11,100 updates, divisible by N=100. The loop persisted a checkpoint at sequence 11,100, then the post-loop final-checkpoint path tried to create the same immutable segment again. The matrix stopped after 15 completed runs; those raw rows remain on the perf VM. I am fixing the duplicate final write and adding a regression test before rerunning.Regression result (n=1): after commit
d4d592585, the full C1 Y2-N100/segment boundary probe completed; the previous duplicatesnapshot-00011100.segerror did not recur. Load average was 0.28 inside/root/perf.lock. Open bytes: 117,972,992 B; after current-point fold: 1,355,776 B. Restore start/middle/end: 12.37/40.00/41.46 ms; cold open: 43.96 ms; undo: 25.47 ms, correct, 20 later-edited elements skipped. Snapshot mean/max: 702.19/5,710.99 ms. Peak RSS: 139.3 MiB. This verifies the exact boundary case; it is not a p95 result.Y2-N500/SQLite C1 repetition 2 (load average 0.97 inside the perf lock): restore start/middle/end was 9.04/168.22/31.77 ms; cold open 30.15 ms; undo 15.48 ms and correct; peak RSS 162.7 MiB; open bytes 32,858,112 B. Repetition 1's maximum restore point was 48.33 ms. The posted rule defines p95 over five runs; because p95 of five is the maximum, this 168.22 ms sample means the configuration cannot qualify if retained in the final run set. I will finish its scheduled repetitions to report variability, but it is not an eligible winner on the current data.
Provisional sample (n=1): C1 Y2-N100/SQLite, repetition 1, perf-lock load average 1.11. Open bytes: 123,195,392 B; after current-point fold: 119,156,736 B. Restore start/middle/end: 13.37/222.80/49.42 ms; cold open: 41.87 ms; undo: 29.45 ms, correct, 20 later-edited elements skipped. Snapshot mean/max: 282.01/3,897.23 ms. Peak process RSS: 145.8 MiB. The middle restore exceeds 50 ms; this candidate cannot meet the posted p95 rule if the sample remains in the set.
#975 Phase 1 report — no winner; stop before Phase 2
Branch:
job/hist-975. Base merge fromorigin/devwas completed before the final gates. Head:d4d592585adbda36c05951c71badbf6469d01eb7.Built and committed
bench/live-history/Cargo.toml,bench/live-history/Cargo.lock,bench/live-history/README.md,bench/live-history/run-matrix.py,bench/live-history/src/lib.rs,bench/live-history/src/main.rs.bea46e769,897bf005e,9639b1d58,d4d592585.Result
The benchmark package and deterministic C1 Canvas / N1 Note traces are built. No history design is selected. The full matrix did not finish, and every Y2 configuration with five runs on both workloads failed at least one hard limit on Canvas. Do not start Phase 2 from these partial results.
Three repeated configurations were summarized with p50 and nearest-rank p95 (p95 of five is the maximum). Disk is shown as median MiB open / after current-point fold. Snapshot is median mean / p95 maximum checkpoint time. Restore lists start / middle / end p95. Undo shows p95, correctness, and skipped later-edited items. RSS is process peak p95.
The baseline peak RSS p50 was 123.71 MiB for Canvas and 13.22 MiB for Note. Each repeated candidate stayed below 2x those baselines. The encoded live state was 8,874,769 B for Canvas and 67,493 B for Note. All six repeated candidate/workload groups had correct undo results and reported 20 later-edited overlaps.
The issue's combined disk score is
(median C1 bytes + median N1 bytes) / 2. The three complete Y2 scores were: Y2-N500/SQLite 16.08 MiB; Y2-N100/segment 56.69 MiB; Y2-N100/SQLite 59.40 MiB. Y2-N500/SQLite had the smallest score in this subset, but its Canvas restore p95 was 168.22 ms and cold-open p95 was 55.24 ms. Y2-N100/segment had Canvas restore p95 107.08 ms and cold-open p95 53.18 ms. Y2-N100/SQLite had Canvas restore p95 222.80 ms. None is eligible.One C1 Y2-N2000/segment sample had 10,342,400 B open and 126.13 ms maximum restore. It has no five-run comparison and is not a winner. Its smaller disk sample does not pass the latency rule.
Load average was recorded inside
/root/perf.lockfor every measured process. The 51 logged matrix/follow-up samples ranged from 0.24 to 2.23; the repaired boundary probe logged 0.28. Measurements used/mnt/hddonroot@10.69.69.63.Matrix failure and correction
The first interleaved matrix stopped after 15 successful rows. C1 Y2-N100/segment has 11,100 updates, exactly divisible by 100. The loop wrote its final periodic checkpoint, then the final-checkpoint path attempted to create the same immutable segment and returned
Error: entry already exists. Commitd4d592585skips that redundant write and adds a boundary regression. The full C1 Y2-N100/segment probe then completed and passed restore and undo correctness.The full 200-run matrix was not restarted. The remaining Y1/Y2/backend repetitions and the Y3, Y4, G1, Loro, and JSON performance candidates are not measured. Only the listed three Y2 candidate configurations have five runs on both workloads. The independent smoke test exercises Yrs SQLite/segment, G1, Loro, and JSON correctness, but it is not performance evidence. Restore/undo RSS values are cumulative process high-water readings, not isolated phase peaks. The after-fold number is a storage floor, not a retention policy. The Note trace uses Yrs XML paragraph/text operations shaped like y-prosemirror; it is not the app's exact editor event stream.
I stopped the full matrix before the approximately four-hour job limit. The remaining candidates and repetitions could not finish within that window. The measurements do not decide Phase 2. Raw JSONL, summary, and lock-protected load logs remain on the perf VM under
/mnt/hdd/bench/hist-975/.Decisions not specified by DESIGN §61
synchronous=FULL; segment files use immutablecalternal-fsroot-relative writes. The fixed 64-edit flush applies to Y2 batches. No product retention rule was inferred from the current-point fold.Gates
cargo fmt --manifest-path bench/live-history/Cargo.toml --checkexited 0 with no output.cargo clippy --manifest-path bench/live-history/Cargo.toml --all-targets -- -D warnings:cargo test --manifest-path bench/live-history/Cargo.toml -- --test-threads=1:An initial default-parallel
cargo testattempt hit a SQLx pool timeout; the isolated boundary regression and serialized full package suite passed.cargo clean --manifest-path bench/live-history/Cargo.tomlcompleted with:Web build output: none. Worktree status is clean. No push, deployment, or Phase 2 implementation was done. The only merge was the required update from
origin/devbefore the final gates.Continuing #975 Phase 1 on branch
job/hist-975. Worktree head at start:d4d592585adbda36c05951c71badbf6469d01eb7; branch base:4a871b383864dad0d6c87bcd22b7f0d589e3506e(currentorigin/dev:f06679b11cde29cc0b7120fdab5f721389caf695). I will use the decision rule already posted above, retain its 50 ms and 2x limits, complete the shortlisted candidate matrix, and measure the named Y2 Canvas remedies on the locked HDD-backed perf VM. No Phase 2 implementation is in scope.Harness finding: the earlier
snapshot_msvalue combined Yrs state encoding, zstd compression and durable storage write. Restore values combined checkpoint lookup/read/decode with log fetch and update replay. The old results therefore identify the failing Restore point and N, but not the expensive stage. Commitec9ae6f7cdb73470469b3ff3c4423972a35377beadds opt-in per-stage timings, a 5-run Y2 diagnostic runner and a decoded-latest-checkpoint probe. The cache probe consumes a temporary decoded Doc and reports its RSS; it does not change the live benchmark room. The focused benchmark crate tests pass; measurements have not started with this profiler build.Raw-result audit finding:
/mnt/hdd/bench/hist-975/y2-100-segment/results.jsonlcontains Canvas repetitions 2, 3, 4 and 5. The repaired repetition-1 boundary probe was printed in the prior issue comment, but it was not appended to a raw JSONL file. The prior Y2-N100/segment Canvas row therefore has four auditable repetitions, not five. I will collect a fresh five-run group with stage profiles; this keeps the p95 rule based on five saved raw rows.Provisional diagnostic samples (r1 only; not p95): Y2 Canvas/SQLite had maximum Restore over start/middle/end of 44.884 ms at N=25, 47.948 ms at N=500 and 138.648 ms at N=2,000. Undo was 27.430, 26.370 and 24.637 ms respectively. This points to checkpoint interval as a Restore factor, but the stage breakdown and four remaining repetitions are still running.
Provisional remedy result (r2): Y2-N25/segment Canvas maximum Restore was 54.887 ms, above the 50 ms limit. The N=25 SQLite Canvas r1 was 44.884 ms. A single exceedance makes the five-run nearest-rank p95 fail if retained; I will report the complete group and its replay-stage breakdown after all repetitions finish.
Provisional diagnostic samples (r3): maximum Restore over start/middle/end was 52.225 ms for Y2-N100/segment Canvas, 99.333 ms for Y2-N500/SQLite Canvas and 352.223 ms for Y2-N2000/SQLite Canvas. Each exceeds 50 ms. These are not final p95 values; the five-run stage profiles are still in progress.
Y2 diagnosis and remedy measurements: 45 fresh interleaved runs, five repetitions per row, on the perf VM HDD path. Every process held
/root/perf.lock; load was recorded inside the lock. The p95 of five is the maximum.All measured undo p95 values are below 50 ms and peak process RSS is below 2x the baseline (Canvas baseline p50 123.7 MiB; limit 247.4 MiB). Stage timings identify checkpoint decode/apply, not undo, as the main N=100/500 Restore cost: middle-point checkpoint decode/apply p95 was 65.1 ms at N=100 SQLite and 91.6 ms at N=500 SQLite, while update replay was 4.9 and 4.4 ms. At N=2,000 both stages contribute: middle-point checkpoint decode/apply 252.8 ms and replay 86.7 ms, across 1,550 updates. At N=25 SQLite the middle point is already a checkpoint (zero replay); decode/apply p95 was 49.2 ms, just above the overall 50 ms limit once read/load overhead is included.
Checkpoint work is also expensive, but mostly in durable writes rather than state encoding. For N=500 SQLite the p95 per-run maximum state-encode time was 13.8 ms, compression 62.0 ms and durable write 2,407.9 ms. The latest-checkpoint cache lowered Restore end to 10.52 ms p95, with 10.5 ms p95 cache access and 105.5 ms p95 warm-up. It did not fix uncached cold open (53.72 ms); process peak RSS remained 165.1 MiB p95.
No measured Y2 configuration passes the posted rule. The closest are N=25 SQLite (Restore misses by 1.46 ms, but uses 436.74 MiB) and N=500 SQLite with a decoded latest-checkpoint cache (Restore and undo pass, cold open misses by 3.72 ms, with 31.34 MiB). The rule remains unchanged. I will compare these with Y1, Y3, Y4, G1, Loro and JSON before recommending any owner decision.
Runner finding: resumed rows used labels such as
Y1-500, but the completion specs usedy1-500. The first completion attempt therefore failed to recognize saved Y1 rows. I stopped it after five new measurements, before any candidate comparison, and fixed candidate-key normalization in commite58d5ca9addc1aa7c7382f3638f1e88f110d45d8. The deterministic helper check now covers Y1/G1 case normalization and the Loro/JSON report-label aliases. I am restarting with a clean output directory and the same saved source rows.The first completed shortlist run exposed a harness defect: Loro historical checkout is read-only by default, so Canvas selective undo failed when applying its detached branch. I enabled detached editing only after checkout, with a code comment on the isolated peer identity invariant, and added a guard that the benchmark exercises a non-empty undo. I am rerunning the focused benchmark tests before resuming the matrix.
A resumed run showed that the Note Loro trace can have an empty selective-undo action set, so the new non-empty-action assertion was too broad and stopped at repetition 2. Canvas Loro has non-empty actions and is now measured successfully. I am narrowing the invariant to Canvas only, then I will rerun the full five Note Loro repetitions with detached editing enabled for consistent measurements.
Phase 1 continuation complete
Branch:
job/hist-975; head:3400b72ce8615a21913e68a046c01f075cc8b448. I mergedorigin/devonce before the final gates. The matrix has five fresh-process repetitions for all 13 shortlisted configurations on both C1 Canvas and N1 Note. Y2 diagnostics cover nine configuration/workload groups with five repetitions each. Each new perf-VM run held/root/perf.lockand logged load inside the lock. The 80 newly completed shortlist runs had load average 0.03–2.02 (median 1.95); reused rows retain their earlier locked-run logs.Recommendation
Recommend Y4 at N=500 with segment storage for Phase 2 review. It is the lowest-disk eligible Yrs configuration: combined median open footprint
(C1 + N1) / 2is 16.19 MiB (C1 31.86 MiB; N1 0.52 MiB), versus 91.73 MiB for Y1-500/SQLite and 369.63 MiB for Y1-100/segment. Y4-500/segment passes every correctness, latency and memory gate. Its closest metric is Canvas Restore p95 at 48.55 ms, 1.45 ms below the limit; Canvas cold-open/undo p95 are 39.32/19.91 ms. Note Restore/cold-open/undo p95 are 6.86/7.56/3.62 ms. Peak process RSS p95 is 157.02 MiB on Canvas and 18.98 MiB on Note, below their respective 247.43/26.44 MiB limits.Y1-100/segment and Y1-500/SQLite also pass. J (JSON element diffs) has a 13.28 MiB combined score and meets the measured gates, but remains a lower bound because it does not preserve live CRDT history. L (Loro) is 3.54× smaller than the best eligible Yrs option, but fails Canvas Restore (136.56 ms p95), Note Restore (91.14 ms), and Canvas cold-open (62.00 ms), so it does not meet the engine-switch rule. No rule change is proposed: eligible Yrs candidates exist. Phase 2 remains for owner review.
Full shortlist matrix
Rows are per candidate, backend and workload. Metric pairs are median/p95 over five runs; p95 uses nearest rank, so with five runs it is the maximum.
S/M/Emeans Restore to start/middle/end. Disk open includes SQLite main/WAL/shared-memory files or segment metadata. Folded disk is the one-current-point storage floor, not a retention policy. Snapshot mean/max are in milliseconds. RSS is process high-water RSS; live-room RSS and encoded bytes describe the current live document.All metric pairs show median/p95 over five runs. P95 uses nearest rank, so it is the maximum of five.
S/M/Eare Restore to start/middle/end.Snapshot mean/maxreports average and maximum checkpoint times. Process RSS values are process high-water marks.Y2 Restore profile and remedies
Times below are p95.
mid/end stagesuse checkpoint read / decode+apply / log load / update replay, then replayed update count. Checkpoint values are maximum per-run state encode / compression / durable write.Y2 diagnosis and remedies
All three original Y2 Canvas configurations miss the 50 ms latency rule. At N=100 on either backend, middle Restore is dominated by checkpoint decode/apply (65.07–65.91 ms p95); replay is only 4.38–4.88 ms over 50 updates. At N=500/SQLite, middle Restore is 99.33 ms, with 91.57 ms in checkpoint decode/apply and 4.43 ms replay over 50 updates. At N=2,000/SQLite, middle Restore is 352.22 ms: 252.78 ms decode/apply plus 86.68 ms replay over 1,550 updates; end Restore is 142.77 ms, including 91.08 ms replay over 1,100 updates. Thus decode dominates at N=100/500; both decode and long-tail replay dominate at N=2,000. Undo p95 stays below 50 ms in every tested Y2 configuration (25.81–38.61 ms).
The profile separates checkpoint state encode, compression and durable write. For N=100 SQLite their max-per-run p95 is 18.59/75.34/2,618.57 ms; N=500 SQLite 13.78/62.02/2,407.87 ms; N=2,000 SQLite 28.48/57.94/1,063.05 ms. These checkpoint-write costs are substantial, but they are not the interactive Restore operation causing the 50 ms misses.
The tested remedies do not make Y2 eligible. N=25/SQLite reduces Canvas middle Restore to 51.46 ms, still 1.46 ms over the limit; N=25/segment is 88.40 ms. Their Canvas open footprints grow to 436.74/434.75 MiB. The decoded-latest-checkpoint cache at N=500 brings Canvas Restore start/middle/end to 9.72/42.49/10.52 ms, but cold-open remains 53.72 ms (3.72 ms over); cache warm-up is 105.46 ms and cache RSS is 129.8 MiB. The cache improves Restore, not a new cold open.
J is the element-level Canvas checkpoint probe: it stores a current map keyed by stable element ID plus changed-element diffs. Its Canvas Restore/cold-open/undo p95 are 37.44/32.22/26.64 ms and its combined disk score is 13.28 MiB. It demonstrates size and latency headroom, while remaining only the issue’s JSON lower bound, not a Yrs element-level history implementation.
Decisions and remaining gaps
Final gates
cargo fmt --check— exit 0, no output.cargo clippy --manifest-path bench/live-history/Cargo.toml --all-targets -- -D warnings:cargo test --manifest-path bench/live-history/Cargo.toml -- --test-threads=1:Phase 2 plan (owner approved 2026-10-03): six parallel slices, one integration + review at the end
Winner from Phase 1: Y4, N=500, segment storage (Yjs v1 update rows batched per flush, zstd with a trained dictionary; v2+zstd checkpoints every 500 updates; gc on; decoded-checkpoint cache). Contract: DESIGN §61. Owner rule: cheapest footprint; review once at the end, not per slice.
Shared contract (every slice codes against this; do not change it without noting it in your report)
Module
crates/calternal-collab/src/history/(new):Restore and undo produce a normal Yjs update applied through the one collaboration event path (§60): no second writer, clients never reload.
Slices (each its own branch from origin/dev, own worktree, per-branch gates only)
history/store.rssegment files viacalternal-fs(append-only, torn tail dropped on open, fsync policy), zstd dictionary training + versioned dictionary IDs, checkpoints, decoded-checkpoint cache, fold, usage;MemoryHistoryStore+ conformance test suite used by all impls; crash tests.Authorfrom the authenticated connection; per-author batching 1–2 s; quota and per-collaborator daily write budget; Canvas and Notes rooms; refuse updates whose Yjs client ID belongs to another connection (spoofing).history/restore.rs: state at point → diff → one update; per-author undo with yrsUndoManager+ tracked origins; Canvas element-level skip-and-report, Notes block-level; preflight report ("2 items you edited were kept"); property tests (restore(point)==state at point; undo(author) keeps others' changes).?v=<point>); build against D's OpenAPI shapes with a typed mock until D merges; production screenshots.bench/as a profile with thresholds indocs/perf/; tests/adversarial: oversized updates, history growth DoS, clock/length abuse, cross-user point access; a CI guard that history bytes per 1k edits stay under budget.Integration + review (Sol, after all six report)
Merge A→B→C→D→E→F on one branch, replace stubs, full gates, e2e (two browsers co-editing then restoring and undoing one author), adversarial, bench vs Phase 1 numbers, independent review. One report, one owner review.
Starting Phase 2 slice A/store on
job/hist2-store, base/headd4e7188810a89fb0e8e6b162279917f7e23989f9. Scope: shared history contract, segment storage through calternal-fs, crash recovery, dictionaries/checkpoints/cache, retention and usage, memory reference and conformance tests. Other Phase 2 slices remain outside this branch. I will preserve the approved trait signatures and report any additional type definitions required by the contract.Starting Phase 2 slice B (write) on job/hist2-write, base
d4e7188810. Scope: authenticated author attribution, applied-update history capture and batching, quota/write-budget checks, and connection-bound Yjs client identity. I will keep HistoryStore signatures from the approved Phase 2 plan and report any required integration hooks.Starting Phase 2 slice API on branch job/hist2-api, based on origin/dev at
d4e7188810. I am reading the approved Phase 2 contract and will keep this branch to the API-owned routes, parity surfaces, contract stubs, and focused tests.Starting Phase 2 slice E (UI) on
job/hist2-ui, based onorigin/devatd4e7188810a89fb0e8e6b162279917f7e23989f9. Scope: Canvas Version history UI with shared Notes component, preview, Restore, author undo preflight, and?v=<point>deep links, using the approved D API shapes and a typed mock until that slice merges. I will keep changes within UI-owned files and report any contract adjustment.Starting Phase 2 slice C/restore on
job/hist2-restore, based4e7188810a89fb0e8e6b162279917f7e23989f9. Scope:history/restore.rs, shared contract stub if absent, restore as one forward Yjs update, tracked-origin selective undo, whole-item skip/report, and focused seeded property tests. Persistence, event capture, routes, UI and benchmarks remain with their assigned slices. No pushes or deployment. Existing Note merge/serialization helpers will be reused. Registry verification: yrs 0.28.0 and async-trait 0.1.92.Starting Phase 2 slice F (bench + guards) on branch
job/hist2-bench, based onorigin/devatd4e7188810a89fb0e8e6b162279917f7e23989f9. I will port the approved Phase 1 workloads into abench/profile, add perf thresholds and the history-bytes CI guard, and add focused adversarial coverage. I will keep implementation within bench-owned files and add the approved shared contract signatures only if this branch does not contain them.Finding:
origin/devdoes not contain the Phase 1 history benchmark (git ls-tree -r origin/dev bench/live-historyis empty). The priorjob/hist-975result contains the deterministic C1/N1 workload generator and Y4 segment runner. I will port that measured harness into the slice and add a Y4-only profile plus a CI budget check, so the approved Phase 1 comparison remains reproducible.Slice B finding: PluginEvent carries the Home owner and path but no author. I added a server-only notify_external_change_as hook for parity callers with a verified actor; legacy filesystem notices use the Home owner. The history writer has its own migration namespace (collab-history-write) for durable daily counters, so no other crate migration is changed. Integration must install slice A through Hub::with_history and use Home-relative segment files whose bytes are included in calternal-fs quota accounting; reservations cover queued updates until append succeeds. Canvas rooms do not exist on this base; HistoryWriter is document-neutral for their event path.
Store progress and contract details:
Err(NotFound)against the stub. The implementation now uses the real Yjs replay engine; persistent conformance, checkpoint, dictionary, invalid-flush, clock-reversal and crash-tail tests are running.HistoryStoresignatures are unchanged. The plan did not defineUserId,ItemId,TurnId,ResultorFoldReport. This branch defines the IDs as the existing server string IDs, typed errors, and a fold report withremoved_points,reclaimed_bytes, andoldest_point.pointsuses an exclusive ascending cursor, capped at 1,000.updates_byincludes both range endpoints. Point zero denotes the initial empty state but is not a listed/restorable point. The first append must contain the initial full v1 state so history knows the seed.append_batchas an inherent store API (not a trait change), so slice B can persist up to 64 attributed rows in one flush. Checkpoints are captured at exact point multiples of 500 even when a flush crosses the boundary. Each row keeps its own point and authenticated author. An idlecheckpointmethod and explicit idle cacheevictare available to the hub.Slice C finding and integration contract: a Yjs state vector does not change for a delete-only edit. The prepared restore/undo therefore checks the complete v1 state, plus DocKey, before bytes are exposed to the event path. Preparation captures a detached baseline before store awaits, so a concurrent edit cannot silently become part of its precondition. Focused regressions cover deletion-only staleness and a wrong-owner DocKey.
Shared contract signatures remain unchanged. This branch supplies String aliases for UserId/ItemId/TurnId, boxed store errors, and a FoldReport stub until slice A integrates. Additional required semantics: ranges are inclusive and increasing; PointId(0) is the initial empty state; a retained predecessor is required for undo. C helpers are prepare_restore and prepare_undo; PreparedChange.checked_update requires the DocKey and the live Doc under the hub room lock. Neither helper applies or appends an update. Canvas uses one atomic JSON value per stable element key, with the room map name supplied by its binding.
The first dependency build is in progress on the shared host. Restore and selective undo are being split into separate commits with focused tests.
Focused replay finding: using yrs 0.28 Update::merge for consecutive typing retained only the first character (expected abcd, replayed a). Its implementation merges block containers and does not merge delete sets. I replaced it with Update::merge_updates and added a separate delete-set replay regression. Initial compilation succeeded; the focused write tests caught this before commit. Client-to-author bindings now persist in the same collaboration-owned Security-state migration so unload/restart cannot reset ownership.
Slice E finding: the Phase 2 plan lists the history API operations but does not define their OpenAPI response fields, and
origin/devhas no history routes yet. I added a narrow typed UI adapter so the panel can use a fixture client before D merges. Its integration shape expects: list{ points, before }; each point{ id, at_ms, author: { kind, id, label }, bytes }; preview{ kind, content }; Restore returns the new point; undo preflight returns{ token, changed, kept: [{ id, label }] }; undo apply accepts the token. TheHistoryStoretrait and its approved signatures are unchanged. D/integration can map this adapter to the generated OpenAPI client. Timeline session groups use same-author points separated by at least 30 minutes because the approved point shape has no session ID.UI review found that a point deep link can mount before ModeHeader renders the Version history action. A null PopoverSurface anchor falls back to x=0,y=0. I now anchor it to the stable header row until the action exists, so the panel remains in the expected area.
Store validation update:
5ad7ee3d8adds the confined history-file helper. At that step, calternal-fs clippy exited 0 and its tests passed (53 unit tests, 42 integration tests).10 passed; 0 failed, with--test-threads=1. It checks every point in a 1,050-edit trace after restart, exact N=500 checkpoint boundaries, a fold through a compressed batch, persisted trained dictionaries, interrupted final frames, interior corruption, invalid flushes, clock reversals, cross-User isolation, concurrent point allocation, and LRU eviction.Db::connectcalls (Sqlx(PoolTimedOut)), before store construction. The store tests now request one reader and use serialized execution. No existing assertion or expectation changed.Doc::cloneshares mutable state; it must not mutate the cached checkpoint. The latest state has its own decoded cache.origin/devis complete: merge commit6af52f6bc, upstreamcfee85c6b11537968aaa0685d1ed1c3ac68a8c3e. Automatic Cargo.toml merging added two blake3 declarations; I kept upstream's workspace dependency and removed the duplicate. Other upstream changes remain intact.Phase 2 API finding: base
d4e7188has no calternal-collab history module or HistoryStore contract; the existing Hub only exposes live Note sessions. This slice will add the approved HistoryStore signatures and a small HistoryMutations interface for Restore, undo preflight and apply. The production router will stay owner-only and return service-unavailable until the storage and mutation slices are connected during integration. I will use one stable item ID route so the API supports Canvas and Notes.Quota integration finding:
Root::recompute_quotaalready walks.calternal/history, so these segment bytes are included in Home usage. Slice B must not addHistoryStore::usageto that total a second time.usagereports the history share of the same total.The new low-level append helper currently depends on slice B's admission check. I added a focused regression for the existing Home quota invariant: appending history beyond a configured Home quota must fail before changing the segment. I will enforce the existing calternal-fs growth and free-space checks in this new helper. This does not change any existing filesystem path or move per-collaborator policy out of slice B.
Deep-link review found that a malformed value was ignored on page load but could still be passed to the panel when opening history from the header. That showed a false 'point unavailable' message beside a valid timeline. The Note action now validates the point ID before passing it to the panel.
Deep-link review found that a malformed v query value was ignored on page load but could still be passed to the panel when opening history from the header. That showed a false point-unavailable message beside a valid timeline. The Note action now validates the point ID before passing it to the panel.
UI scope check: the merged origin/dev tree has no Canvas route or Canvas editor ( returned no files). I kept this slice to the reusable VersionHistory surface and a real Notes host, with a custom preview snippet hook for Canvas. I did not add a standalone Canvas screen without its editor/backend.
UI decisions where §61 is silent: group a same-author run into one session until there is a 30-minute idle gap (the point contract has no session ID); render Note previews as escaped plain text and let a Canvas host inject its renderer; fetch 100 points per page and cap each API JSON reply at 2 MiB so opening history stays bounded.
Slice B progress: core write layer committed at
2a4974013; Hub capture committed atcb79d157b. Focused tests now prove real authenticated WebSocket capture, agent-turn replay, external actor capture and refusal before agent mutation when quota is full. Additional hardening keeps presence-only claims ephemeral (no Security-state rows for Viewer cursors), verifies existing presence IDs against the Index, caches only active ownership, releases quota if admission is cancelled while waiting for the Index, and retries failed appends with one bounded background task. Pending targeted tests and the required origin/dev merge precede final gates.Slice C progress: Restore is committed in
206bbb148; selective undo in1e08e6145. The focused suite now passes 12 tests, including 768 seeded Canvas point restores with replica convergence, 64 Canvas undo streams, 64 Note undo streams, foreign equal-value writes, root deletion/resurrection, foreign deletion, protected insertion, anchored block reorder, stale/delete-only preflight checks, concurrent store-read edits, retention gaps and author-range boundaries.Finding: an order-only Note edit changed the document but was absent from the preflight reverted list. The guard fix now includes selected blocks whose positions changed, while excluding the foreign blocks shifted by that move. The dedicated regression passes. Preparation also retains encoded baseline bytes across awaits and drops decoded replay documents before it builds the forward edit, to reduce peak memory.
Contract additions to confirm at integration: ordered inclusive ranges, point 0 as the empty initial state, and point order matching applied event order across author batching. No approved trait signature changed. Undo requires its retained predecessor and caps one preflight at 10,000 points and 64 MiB of update bytes; states use the existing 16 MiB room-state cap. Over-limit or incomplete ranges fail without a partial apply. Exact DocKey + complete-state apply checks run under the hub lock. Canvas values are atomic JSON map entries; the binding supplies the map name. Notes use blockAnchor where present and Yjs branch identity otherwise; replay-only marker attributes never enter emitted updates.
Per-slice gates so far: cargo fmt --check, cargo clippy -p calternal-collab --all-targets -- -D warnings, focused history_restore tests and the malformed/incomplete-state unit test pass. I will fetch/merge origin/dev once, run the final per-crate gates, and post full verbatim output with the final head. No UI or route changes; the combined browser/adversarial/performance checks remain for the integration round under the verification policy.
Finding: after I moved the reusable abuse probe into collab test support to keep the benchmark's isolated lockfile small,
cargo clippy -p calternal-collab --all-targets -- -D warningsreportedAdversarialReportandprobeas dead code. I added a test-only in-memory fixture that runs the probe, so oversized-update, growth, extreme-clock, page-length and same-item cross-User assertions execute without adding the server graph to the benchmark binary.#975 Phase 2 UI complete
Branch:
job/hist2-uiHead:
9560c48e5221226f0dce271cfb7ea253c73ea6dcBuilt
/n/<calternal-id>?v=<point>.Files:
apps/web/src/lib/history/{VersionHistory.svelte,api.ts,sessions.ts}and their three focused test files;apps/web/src/lib/notes/NoteView.svelte.Contract and gaps
HistoryStoresignature changed. The plan does not define OpenAPI response fields. This UI adapter expects list{ points, before }; points{ id, at_ms, author: { kind, id, label }, bytes }; preview{ kind, content }; Restore returns the new point; undo preflight returns{ token, changed, kept: [{ id, label }] }; undo apply accepts the token. Slice D or integration must map this adapter to the merged OpenAPI client.origin/devhas no Canvas route or editor. This slice provides the shared panel and a Notes host, plus a preview snippet hook for Canvas. It does not add a Canvas screen without its editor/API.UX gaps closed
vvalues no longer show a false missing-point message.UX gaps left
origin/dev. Integration must mount the shared component in Canvas and replace the provisional endpoint adapter. A live two-browser restore/undo E2E remains for the merge round.Decisions not specified by §61
Screenshots
Gates (verbatim)
bun run checkbunx vitest run src/lib/history/api.test.ts src/lib/history/sessions.test.ts src/lib/history/VersionHistory.svelte.test.ts --maxWorkers=2Production build:
bun run buildsucceeded. Screenshot server build:cargo build -p calternal-serversucceeded. No Rust source changed, so Rust crate gates did not apply.cargo cleanremoved 4.6 GiB; web build output was deleted.For the merge round
Run full web tests and add/run
bun e2e/history-975.mjsagainst the merged API and Canvas. It must prove two browser contexts co-edit, restore a point, and selectively undo one author's changes with the preflight report. Slice F owns the benchmark profile.Orchestrator visual notes on slice E (hist2-ui, 1440-light.png) for the integration round — fix there, not as a separate send-back:
Finding: replacing the broad
/api/v1/collab/authorization allowance with reviewed operation IDs exposed the existingcollab_public_edit_sessionwebsocket. Its route is/api/v1/collab/public/{slug}and it uses a public Edit-link capability, so it needs the{standard, public_link, admin}policy from DESIGN §54. I added that explicit policy, kept history and signed-in sessions at{standard, admin}, and extended the owner/control request builder with Note item IDs plus point 1 and stable Undo author ranges. Evidence:python3 -m unittest test_xuser_classificationpasses (9 tests).Phase 2 slice A/store complete
Branch:
job/hist2-store. Head:cbb8dbeaf5c43a2cb094261716cdd05332134b03. Start/base:d4e7188810a89fb0e8e6b162279917f7e23989f9. The requiredorigin/devupdate was merged once at6af52f6bc, using upstreamcfee85c6b11537968aaa0685d1ed1c3ac68a8c3e.Built
HistoryStorecontract, a memory reference implementation, and a test-only conformance checker for other backends (test-hooksfeature).Files
crates/calternal-collab/src/history/mod.rscrates/calternal-collab/src/history/store.rscrates/calternal-collab/src/history/conformance.rscrates/calternal-collab/src/lib.rscrates/calternal-collab/Cargo.tomlcrates/calternal-fs/src/history.rscrates/calternal-fs/src/lib.rsCargo.lockCommits:
5ad7ee3d8(confined segment handles),e73dfed7e(quota and reserve checks),cbb8dbeaf5c43a2cb094261716cdd05332134b03(history store). The merge commit only updates this job branch fromorigin/dev. No push or deployment was performed.Decisions and contract additions
The approved trait signatures did not change. The plan left these details undefined:
UserId,ItemIdandTurnIduse the existing string IDs.Resultuses typed errors.FoldReporthasremoved_points,reclaimed_bytesandoldest_point.pointshas an exclusive ascending cursor and caps a page at 1,000 rows.updates_byincludes both endpoints.SegmentHistoryStore::open(root, db, max_cached_documents)creates the derived Index table. No numbered migration was added or reused.append_batch,checkpointandevictsupplement the trait. A batch has at most 64 attributed rows; each row retains its own point. Slice B can use this for one durable flush. An idle checkpoint creates no point.Root::recompute_quotaalready includes.calternal/history. Slice B must not addusageto that Home total again. It still owns collaborator budgets and admission through the collaboration event path.Module and function comments were read again before this report. Full v1 states reuse the existing
stored::encodehelper. Dependencies were verified withcargo searchandcargo info: zstd 0.14.0 (BSD-3-Clause), async-trait 0.1.92 (MIT or Apache-2.0).Known gaps and integration work
For the merge round
cargo clippy --all-targets -- -D warningsandcargo test -- --test-threads=4: check the combined Rust branch once.(cd apps/web && bun run check && bun run test --maxWorkers=2): check the combined web branch once.(cd apps/web && bun run test:e2e && bun run test:e2e:notes), plus the history cases from E/integration: two clients keep editing through Restore and per-author undo, with no reload; point links and owner-only history work.bash tests/adversarial/run.sh: run D/F history cases and the XUser, authorization and robustness matrices on the real local server.flock /root/perf.lock, with load recorded inside the lock. Compare it with the Phase 1 Y4/N=500 segment result anddocs/perf/baseline.json. The exact new history-profile command belongs to F and is not on this branch yet.Verification notes
The final filesystem suite passed 62 unit tests and 43 integration tests. The collaboration runtime suite passed 89 tests. The final focused history run passed all 10 tests against the quota fix. Both crate clippy gates and fmt passed. The collaboration doc-test phase passed on a focused rerun.
Two test-run problems are recorded instead of omitted:
Db::connectincache_evicts_idle_rooms_and_reopens_durable_points,concurrent_appends_have_unique_points_and_index_rowsandsegment_conforms(Sqlx(PoolTimedOut)). The new tests now use one reader and serialized execution. Their assertions did not change.cargo test -p calternal-collab --doc -- --test-threads=1passed. The original failed gate output and its recovery are quoted below.Gate output, verbatim
All cargo commands used
CARGO_PROFILE_DEV_DEBUG=line-tables-only,CARGO_INCREMENTAL=0,CARGO_BUILD_JOBS=4and worktreetarget/tmp. The preset target directory was not overridden.cargo fmt --check: exit 0, no output.cargo clippy -p calternal-fs --all-targets -- -D warnings:cargo test -p calternal-fs -- --test-threads=1:cargo clippy -p calternal-collab --all-targets -- -D warnings:cargo test -p calternal-collab -- --test-threads=1 (runtime tests pass; original doc-test artifact failure follows):cargo test -p calternal-collab --lib history:: -- --test-threads=1 (final focused regression run):cargo test -p calternal-collab --doc -- --test-threads=1 (doc-test recovery):cargo clean:No web build output remains. Worktree status is clean. No issue was closed.
Phase 2 slice C/restore complete on
job/hist2-restore.Head:
0886494d33454e3ad1f40132272aa6a5961eba52. Base:d4e7188810a89fb0e8e6b162279917f7e23989f9.The required one-time merge used
origin/devatcfee85c6b11537968aaa0685d1ed1c3ac68a8c3e(merge9ad53bcad). The only conflict was module documentation; both descriptions were kept.Built
prepare_restore: read a point and return one normal forward Yjs update. The Note path reuses the existing block bridge; Canvas changes only affected atomic JSON map entries.prepare_undo: replay with a temporary GC-off document and yrs UndoManager tracked origins. Live documents keep GC on. Later foreign or out-of-range edits protect a whole Canvas element or top-level Note block, including equal-value writes, inserts and deletes.PreparedChange: bind the plan to DocKey and complete live state, including deletes.checked_updatemust run under the hub lock immediately before the one event path applies the bytes. Preparation holds encoded baseline bytes across store awaits, so a concurrent edit invalidates the plan. No-op plans need no event.reverted/keptIDs. Order-only Note moves count as reverted. Temporary Note identity markers survive UndoManager resurrection and are removed before forward updates.Files
crates/calternal-collab/src/history/restore.rs,history/mod.rs,tests/history_restore.rs,src/lib.rs,Cargo.toml, and rootCargo.lock.The public additions outside restore.rs are the shared contract stub, the
pub mod historyexport, and async-trait. No other crate behavior was changed by this slice. Existing test expectations were not changed. All touched doc comments were read again before this report.Commits
206bbb148: guarded forward Restore and shared contract.1e08e6145: tracked-author undo and whole-item preservation.5c98dae39: bounded preflight, order-only reporting and race/retention regressions.9ad53bcad: required origin/dev merge.0886494d3: randomized properties and independent-client checks.Decisions and shared contract details
No approved HistoryStore method signature changed. This branch uses String aliases for UserId/ItemId/TurnId, boxed store errors, and a FoldReport stub with kept_points/removed_points/reclaimed_bytes. Slice A must reconcile these supporting definitions and replace MemoryHistoryStore's placeholder.
Point 0 denotes the initial empty state. Ranges are inclusive and increasing. Point order must match applied event order across authors, even when physical writes are batched. Undo needs the retained predecessor of its first point and all later rows through its captured head.
One undo preflight is capped at 10,000 points and 64 MiB of update bytes. Complete states are capped at the existing 16 MiB room-state limit. Incomplete or over-limit windows fail without a partial apply. Canvas bindings supply their element map name and use atomic JSON values. Note units use blockAnchor where present, otherwise the original Yjs branch ID; replay-only identity attributes never reach clients. Head equivalence uses clocks/deletions plus semantic content, because JSON object wire order can differ after decode. Apply preconditions compare the actual room's complete encoded state.
Known gaps / integration hooks
The store is slice A's responsibility; the contract module here is a stub. Connect the preparation helpers to B/D's authenticated collaboration event path. That path must check owner access, validate the prepared bytes under its room lock, attribute the new event to the caller, append it and broadcast it. Forward bytes use a server-generated Yjs client ID; they must enter the trusted server edit path. Routes, CLI/MCP parity, preview UI, store crash/retention conformance and the full authorization matrix belong to their assigned slices and the integration round.
Nested shared Canvas values are refused; the approved atomic JSON element binding is required. Folded predecessors and windows above the limits cannot be selectively undone by this implementation. No known failing case remains within the implemented slice.
UX gaps closed
Backend cases closed: delete-only and concurrent preflight staleness; foreign equal-value writes; protected inserted/deleted blocks; accurate order-only undo reporting; normal-update convergence without changing room lineage. UI verification belongs to slice E.
UX gaps left
Owner-only API application, calm retry/error presentation, preview, Copy link and Undo controls need the combined B/D/E implementation.
For the merge round
cargo clippy -p calternal-server --all-targets -- -D warningsandcargo test -p calternal-server -- --test-threads=4: prove the combined route and event contracts.bun --cwd apps/web run checkandbun --cwd apps/web run test --maxWorkers=2: check the combined client and UI.bun --cwd apps/web run test:e2e:notesandbun --cwd apps/web run test:e2e: include two-browser history Restore/undo cases; prove live convergence, room lineage, owner access and the complete history UI after D/E integrate. Use E's Canvas coverage too.bash tests/adversarial/run.sh: run F's new history cases with the complete authorization, cross-User and robustness matrices.flock /root/perf.lock bash -c 'uptime; bash bench/run.sh --measure-only --runs 5'. Prove history bytes grow with edits, compare Restore/undo/cold-open latency and peak RSS with Phase 1, and check the byte-budget guard. This slice reports functional gates; it did not measure a standalone C performance result.Gates (verbatim)
The full per-crate suite ran once after the origin/dev merge and passed all 94 tests. The final additional test coverage then ran as the focused file (15 tests); production Rust code did not change after the full suite. This follows the per-slice verification policy. Every build used CARGO_PROFILE_DEV_DEBUG=line-tables-only, CARGO_INCREMENTAL=0 and CARGO_BUILD_JOBS=4. Tests used this worktree's target/tmp; CARGO_TARGET_DIR stayed at its preset value.
cargo fmt --check: exit 0; no output.cargo clippy -p calternal-collab --all-targets -- -D warnings:cargo test -p calternal-collab -- --test-threads=4:cargo test -p calternal-collab --test history_restore -- --test-threads=4:Cleanup (
cargo clean):Web build output and test scratch files were removed. The worktree is clean. The issue remains open for integration.
Finding: the focused oversized-Undo case sent a 20,000-byte author ID through a route capped at 16 KiB. Axum 0.8.9 returned a
JsonRejectionwith status 413, but the handler collapsed it to 400. I now preserve 413 with a generic error envelope, document 413 in both Undo OpenAPI operations, and keep malformed JSON on the 400 path. Evidence:cargo test -p calternal-server history_contract_tests -- --nocapturereports 7 passed, 0 failed.Phase 2 bench finding — perf VM, 2026-10-03
The Y4-500 segment profile with decoded latest-checkpoint cache completed five locked runs per workload. The byte guard passed: Canvas was 33,411,072 B / 11,100 edits = 3,010,006 B per 1,000 edits against 3,100,000; Note was 548,864 B / 7,220 edits = 76,020 B per 1,000 edits against 80,000. Phase 1 measured 33,407,632 B and 545,260 B respectively, so the open-size deltas are +3,440 B (+0.010%) and +3,604 B (+0.661%).
C1 middle Restore p95 was 40.954 ms; undo p95 was 28.680 ms; peak RSS p95 was 156.621 MiB. N1 middle Restore p95 was 3.767 ms; undo p95 was 3.575 ms; peak RSS p95 was 18.988 MiB. All runs reported correct and supported undo.
The largest C1 trace has 5,000 starting elements, 11,100 edits, a 300-edit agent turn and 200-point freehand strokes. Its maximum checkpoint time p95 was 4,973 ms. DESIGN §61 sets no checkpoint-time threshold, and docs/perf/baseline.json had no prior history timing or RSS profile. I recorded this as the first profile for owner review; byte thresholds and full metrics are in docs/perf/live-history-budget.md and docs/perf/baseline.json. Each measured run held /root/perf.lock and recorded load while holding it.
Phase 2 slice B (write) complete
Branch:
job/hist2-write.Head:
6d327cbca21b88b7d8ebd9933b95af62157b6092.Start/base:
d4e7188810a89fb0e8e6b162279917f7e23989f9.Required merge:
origin/devatcfee85c6b11537968aaa0685d1ed1c3ac68a8c3e, merge commit22dc32fbe75a689cac3994a62d3da8c1a79c6e10. The one conflict kept both the Daily note selector check (#646) and first-load serialization. No push or deployment. No merge into dev or main.Built
HistoryStoresignatures from the approved plan. Slice A's store andMemoryHistoryStoreimplementation remain for integration.HistoryWriterfor Notes and future Canvas rooms. It merges only consecutive updates by the same author. Foreign updates end a batch; replay order stays correct. A one-second task drains the queue. File saves, last-client saves and shutdown also drain it. Failed appends stay queued and retry with bounded backoff.notify_external_change_as.path:selectors because history requires a stable Note identity.Files
crates/calternal-collab/src/history/mod.rscrates/calternal-collab/src/history/write.rscrates/calternal-collab/src/history/write_tests.rscrates/calternal-collab/src/history/0001_write_budget.sqlcrates/calternal-collab/src/session.rscrates/calternal-collab/src/lib.rscrates/calternal-collab/Cargo.tomlCargo.lockAtomic commits
2a4974013: history admission, budgets, batching and ownership boundary.cb79d157b: Hub capture and real collaboration-path regressions.4067e2bb4: cancellation, retry and presence resource bounds.22dc32fbe: required origin/dev merge.6d327cbca: stable Note identity guard and its regression.Decisions
The plan did not set numeric limits. Defaults are 64 MiB of charged input per owner/collaborator per UTC day and 64 MiB of pending quota allowance.
WriteLimitscan set other instance limits. All agent turns in one Home share the agent allowance, so a new turn ID cannot reset it. First client-ID writes include a 512-byte accounting allowance. Queued quota reserves twice the charged bytes plus 1 KiB for framing/expansion.Batching has a one-second maximum healthy wait; an existing file-save flush can drain sooner. The first update supplies the batch time. All times come from the server. Failed appends retry at up to 30-second intervals with one task.
The migration uses its own
collab-history-writenamespace, version 1. It has no collision with Notes migration numbers; origin/dev had no history module at the required merge. IDs use the existing String representation. The shared contract leftFoldReportfields and error type unspecified: this branch supplies provisionalremoved_points/reclaimed_bytesfields and an opaque error Result. A may reconcile those details. None of the six HistoryStore signatures changed.I verified
yrs0.28.0 andasync-trait0.1.92 with cargo registry commands.async-traitis MIT OR Apache-2.0. No licence change.Findings fixed
The first replay regression produced
ainstead ofabcd:yrs::Update::mergedoes not preserve the complete batch and its delete set. The writer now usesUpdate::merge_updates; typing and deletion replay tests pass.PluginEventhas no actor field. Server API callers can use the new verified-actor hook; legacy filesystem notices use the Home owner. No request payload supplies an Author.Known gaps / integration requirements
Hub::with_history(store, limits)before rooms open. The default Hub has no history store until integration.MemoryHistoryStoreis a declaration placeholder here; the test recorder is test-only. It is not shipped as a substitute store.Performance / for the merge round
No perf measurement was run in B. F owns the history profile and guards, and the integration job compares the complete event path with Phase 1. The Phase 1 Y4/segment reference is C1 31.86 MiB, N1 0.52 MiB; Canvas Restore/cold-open/undo p95 48.55/39.32/19.91 ms. Those are the earlier measurements, not measurements of this branch. Include admission's Index writes and configured Home quota checks in the integrated hot path; measuring append alone will omit them.
Deferred under the verification policy:
cargo fmt --check,cargo clippy --all-targets -- -D warnings,cargo test -- --test-threads=4on the combined integration branch: verify all consumers and the server contract.bun --cwd apps/web run check,bun --cwd apps/web run test --maxWorkers=2: verify D/E's web contracts and UI.bun --cwd apps/web run test:e2e:notes: extend the runner with the integrated two-browser restore and per-author undo cases; verify convergence, kept foreign edits, and no client reload. Run E's Canvas runner too.tests/adversarial/run.sh: run the combined real-server history/identity/isolation cases added by D/F.flock /root/perf.lock, with a prebuilt release binary and load recorded inside the lock: compare the complete path with Phase 1. F has not merged here, so its command cannot be named from this branch.Gates (verbatim)
All cargo commands used
CARGO_PROFILE_DEV_DEBUG=line-tables-only,CARGO_INCREMENTAL=0,CARGO_BUILD_JOBS=4and worktreetarget/tmp. The full crate suite ran once after the required merge: 93 tests passed. The final stable-ID guard then ran with the focused tests, per the verify-once rule. Doc comments were read again before this report.cargo fmt --check: exit 0, no output.cargo clippy -p calternal-collab --all-targets -- -D warnings (merged branch):cargo test -p calternal-collab -- --test-threads=4 (merged branch):cargo clippy -p calternal-collab --all-targets -- -D warnings (final guard):cargo test -p calternal-collab --lib history -- --test-threads=4 (final guard):Cleanup
cargo clean:Web build output was removed. Worktree status is clean. The issue remains open.
Phase 2 slice F complete
Branch:
job/hist2-benchBase merged:
origin/devatbd11bacb5189d39176e7cd48d5e977f1694321c1Head:
9e3319bbf641c93145ff08ecfbbc7a8099dd9236Built
HistoryStorecontract module and exposed it fromcalternal-collab. The contract uses stable User and item identities, monotonicPointIds, and the append/list/restore/undo/fold/quota signatures.bench/live-history. Added the owner-selected Y4-500 segment profile with decoded latest-checkpoint cache.HistoryStoreadversarial probe for valid oversized updates, bounded growth, extreme timestamps, page lengths and same-item cross-User access. Its fixture tests pass; the storage slice must call it with the concrete segment store's limits.Files:
.forgejo/workflows/ci.yml, rootCargo.lock,crates/calternal-collab/Cargo.toml,crates/calternal-collab/src/lib.rs,crates/calternal-collab/src/history/{mod.rs,adversarial.rs}, all files inbench/live-history/,docs/perf/live-history-budget.{json,md}, anddocs/perf/baseline.json.Perf profile
Used an optimized release binary built on the development host and five interleaved samples per workload on
perf-test. Each sample acquired/root/perf.lockand recorded the 1-minute load under the lock (range 0.01–1.87).The guard passed. Against the Phase 1 open-size medians, Canvas is +3,440 B (+0.010%) and Note is +3,604 B (+0.661%). All five runs reported supported and correct undo. The largest C1 trace includes 5,000 starting elements, a 300-edit agent turn and 200-point strokes. Its maximum checkpoint-time p95 was 4,973 ms. DESIGN §61 sets no checkpoint-time threshold; this first timing/RSS profile is recorded in
docs/perf/baseline.jsonfor owner review.Gate output (verbatim result lines)
cargo fmt --checkexited 0 with no output.The server clippy initially could not compile because
apps/web/buildwas absent.bun install --frozen-lockfileplusbun run buildsupplied the real embedded frontend; the rerun passed. Build assets andnode_moduleswere removed afterward. The build printed existing analytics-vendoruse clientdirective warnings; no web source or lockfile changed.Known gaps and merge round
cargo test -p calternal-collabwith that store wired to the probe; this must prove its real size, growth, clock, paging and owner-scope rules.tests/adversarial/run.sh; extend it with the history endpoint cases so it proves cross-User isolation and that oversized/hostile history requests do not produce 5xx responses.Decisions not specified by DESIGN
uuid::Uuidfor User IDs, opaqueStrings for stable item and agent-turn IDs,u64monotonic point IDs, and a boxed internal error result. The boxed error keeps storage backends out of the cross-slice contract; item identity is not a path.Finding after merge:
cargo clippy -p calternal-server --all-targets -- -D warningsrejected three history route helpers because they returned AxumResponsein a largeResulterror variant (history.rs:caller,doc_key,parse_point). I replaced the response-valued error with a small typed route error and retained the same API statuses and envelopes. I am rerunning the server gates now.Phase 2 API slice report
Branch:
job/hist2-apiHead:
1543e56074003f2fe184e9f189883763afb42952Built
calternal-collab: stableDocKey/point identities, authors, store operations, errors, and the Restore/Undo mutation seam.discoverabletier; legacy actions remain without a tier.Files
crates/calternal-collab/Cargo.toml,crates/calternal-collab/src/history/mod.rscrates/calternal-server/Cargo.toml,crates/calternal-server/src/history.rs,crates/calternal-server/src/history_contract_tests.rs,crates/calternal-server/src/main.rs,crates/calternal-server/src/wire.rscrates/calternal-api/src/actions.rs,Cargo.lockcontracts/action-overrides.json,contracts/actions.json,contracts/openapi.json,packages/api-client/src/generated.tsscripts/action_registry.py,scripts/test_action_registry.py,docs/parity-matrix.mdtests/adversarial/authz_matrix.py,tests/adversarial/test_xuser_classification.py,tests/adversarial/xuser_matrix.pyGates
cargo fmt --checkexited 0 with no output.Commands and verbatim final success output:
Additional focused checks passed: action registry unit tests (14), XUser/admin classification tests (24), and
python3 scripts/parity_matrix.py --check(345 API actions; 0 adapter gaps). The generated registry reportedAction registry: 345 operations, 327 generated tools.cd apps/web && bun run checkwas attempted but could not start because workspace dependencies are not installed. It exited withERR_MODULE_NOT_FOUND: Cannot find package 'typescript'fromapps/web/scripts/check-user-storage.mjs.Known gaps and merge-round work
Nonefor the history store and mutation service, so authorized history calls return 503 until the storage/mutation slices are wired. This is the planned integration seam; no fake history is returned.bash tests/adversarial/run.shto exercise the XUser and authz matrices plus the live robustness probes against the integrated server.Decisions where the docs were silent
HistoryStoresignatures and added a separateHistoryMutationsinterface for Restore and Undo. Undo apply revalidates the same author/range instead of trusting a stale preflight token.{points, next_after}; preview returns the Yjs v1 update asapplication/vnd.calternal.yjs-update-v1bytes.Cleanup:
cargo cleanoutput wasRemoved 16856 files, 14.0GiB total;apps/web/buildwas removed. No push, deploy, or issue close was performed.Starting Phase 2 integration on job/hist2-integrate, base
bd11bacb51. I have read the approved plan, all slice reports and the six visual notes. I will merge A→B→C→D→E→F, reconcile the store and mutation seams, and report real verification evidence. Under the current verification policy, full web/e2e/adversarial suites and benchmark runs belong to the merge round; focused regressions and production screenshots run here.Integration finding: the independent review found crash lineage mismatch, self-counted quota reservations, unchecked fold staging, first-session Undo at point zero, and accepted replay-only attributes. Fixes and focused regressions are being added. A further integration check found that reading missing history created directories; reads now check for existing segments before opening them. Production API wiring and the real UI adapter are implemented. Dependency compilation is still running on the shared host; logs remain active in artifacts/.
Integration finding (#975): the follow-up review found that saving snapshot B while later edit C becomes durable can leave the saved hash at A after a crash. Recovery would mistake B for an external edit and reverse C. The fix writes an old:new materialization intent before the checked file replacement. Recovery accepts either known internal hash. The regression now saves an intermediate snapshot with a durable later edit and simulates loss of the process cache. The earlier focused history suite passed 36 tests and Restore suite passed 15; the new interleaving test is running. Independent review also confirmed that reservation accounting, fold space checks, first-session undo, reserved attributes, lineage recovery and idle room release are addressed.
#975 Phase 2 integration report
Head:
c18c6ddfb5718cd236cccc9f1a8964de42daebc5. Branch:job/hist2-integrate. Base:bd11bacb5189d39176e7cd48d5e977f1694321c1.Merged store → write → restore → API → UI → bench in the requested order. Fetched and merged
origin/devonce before final gates; it was already up to date. No push, deployment, or merge into dev/main was done.What was built
Independent review
The independent read-only reviewer checked author binding, quota, confined paths, crash recovery and Restore correctness. All seven reported blockers were fixed and confirmed in source:
Quota refusals also return a safe conflict rather than a 5xx. Module and function comments were reread and updated. Existing test expectations were preserved.
UX gaps closed
Evidence run in this job
The real-server screenshot regression created one Note through the API, typed two edits, waited for three durable points, opened a point URL, decoded its preview, and closed the panel with Escape. It verified the signed-in author name and desktop action-button anchor. Six macOS-emulated production captures passed. Product layout uses CSS; geometry assertions exist only in the test.
The Rust tests covered authenticated socket capture, Restore broadcast, first-session Undo, persistent lineage, torn-tail recovery, quota, cross-User ownership, Share denial, idle-room release, and the concurrent save-intent crash case. The API tests confirmed that mutation actors come from authentication.
This job followed the 2026-10-02 verification policy. Full browser matrices, real SIGKILL-mid-append, full web tests, release/staging checks and a new perf run were not run here. The new script's default peer scenario currently uses two contexts of the same User; it does not prove the required two-User Collaborate flow.
Screenshots
Gates — verbatim output
All final required per-job gates passed. Full crate tests ran once; focused checks reran after the fixes above. No full workspace gate ran.
cargo fmt --checkexited 0 with no output.cargo clippy -p calternal-fs --all-targets -- -D warningscargo clippy -p calternal-collab --all-targets -- -D warningscargo clippy -p calternal-plugin-notes --all-targets -- -D warningscargo clippy -p calternal-server --all-targets -- -D warningscargo clippy -p calternal-api --all-targets -- -D warningscargo test -p calternal-fscargo test -p calternal-collabcargo test -p calternal-plugin-notescargo test -p calternal-servercargo test -p calternal-apicargo test -p calternal-fs history::testscargo test -p calternal-collab durable_history_aheadcargo test -p calternal-plugin-notes collabbun run checkbunx vitest run src/lib/history/api.test.ts src/lib/history/VersionHistory.svelte.test.ts src/lib/history/sessions.test.ts --maxWorkers=2bun run buildcargo build -p calternal-server(OPENSSL_NO_VENDOR=1, final production assets)node apps/web/e2e/history-975.mjs --screenshots-onlypython3 -B tests/adversarial/test_xuser_classification.pyBench comparison
These are the prior locked slice F measurements recorded on #975. An integrated rerun remains pending under the verification policy. They measure the F candidate harness, not the complete admission/Index/file-save event path.
F's disk guards passed: C1 3,010,006 B/1,000 edits against 3,100,000; N1 76,020 against 80,000. These are prior results, not new measurements of this head.
Decisions
Known gaps / UX gaps left
For the merge round
cd apps/web && bun run test --maxWorkers=2: run the full web suite.CALTERNAL_SERVER_BIN=<combined-build> node apps/web/e2e/history-975.mjs: run its two-context Restore/Undo smoke. Extend it to two authenticated Users through Collaborate, add the third User and Share viewer, verify both editors update without reload, assert preflight counts, and exercise Copy link through the clipboard.bash tests/adversarial/run.sh: run the generated XUser/authz/robustness matrices on the combined server. Add the focused history append kill/restart phase. Verify prior points survive and only a torn tail is removed.cargo test -p calternal-collab torn_tail_recovery -- --test-threads=4: retain the focused segment crash-tail proof.bench/live-history/README.md. Runpython3 -B bench/live-history/run-matrix.py --phase2-y4 --binary /mnt/hdd/bench/live-history/calternal-live-history-bench --traces /mnt/hdd/bench/live-history/traces --results /mnt/hdd/bench/live-history/results.jsonl --loads /mnt/hdd/bench/live-history/load.tsv --runs /mnt/hdd/bench/live-history/runs, thenpython3 -B bench/live-history/check_budget.py --results /mnt/hdd/bench/live-history/results.jsonl. The runner holds/root/perf.lockand records load. Include the real admission/Index/save path in an integrated profile before claiming end-to-end numbers.READY FOR MERGE ROUND: yes. This is readiness for the combined verification round; full Phase 2 acceptance still needs the checks listed above.
Files
Includes the six merged slices and integration fixes:
Cleanup
Web build output was removed. Screenshots and gate logs remain under ignored artifacts/.