kenaz 0.3.3 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (451) hide show
  1. package/core/.agents/skills/typesafe-ai/LICENSE +21 -21
  2. package/core/.agents/skills/typesafe-ai/SKILL.md +149 -149
  3. package/core/.claude/commands/Ansuz.md +15 -2
  4. package/core/.claude/hooks/__tests__/task-2395-project-registration.test.js +84 -0
  5. package/core/.claude/hooks/handoff-staleness.js +2 -2
  6. package/core/.claude/hooks/session-init.ps1 +49 -49
  7. package/core/.claude/hooks/turn-context.js +50 -2
  8. package/core/.claude/skills/ui-ux-pro-max/data/charts.csv +26 -26
  9. package/core/.claude/skills/ui-ux-pro-max/data/colors.csv +97 -97
  10. package/core/.claude/skills/ui-ux-pro-max/data/icons.csv +101 -101
  11. package/core/.claude/skills/ui-ux-pro-max/data/landing.csv +31 -31
  12. package/core/.claude/skills/ui-ux-pro-max/data/products.csv +96 -96
  13. package/core/.claude/skills/ui-ux-pro-max/data/react-performance.csv +45 -45
  14. package/core/.claude/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -54
  15. package/core/.claude/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -53
  16. package/core/.claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -56
  17. package/core/.claude/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -53
  18. package/core/.claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -53
  19. package/core/.claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -51
  20. package/core/.claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -59
  21. package/core/.claude/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -52
  22. package/core/.claude/skills/ui-ux-pro-max/data/stacks/react.csv +54 -54
  23. package/core/.claude/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -61
  24. package/core/.claude/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -54
  25. package/core/.claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -51
  26. package/core/.claude/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -50
  27. package/core/.claude/skills/ui-ux-pro-max/data/styles.csv +68 -68
  28. package/core/.claude/skills/ui-ux-pro-max/data/typography.csv +57 -57
  29. package/core/.claude/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -101
  30. package/core/.claude/skills/ui-ux-pro-max/data/ux-guidelines.csv +99 -99
  31. package/core/.claude/skills/ui-ux-pro-max/data/web-interface.csv +31 -31
  32. package/core/.claude/skills/ui-ux-pro-max/scripts/core.py +253 -253
  33. package/core/.claude/skills/ui-ux-pro-max/scripts/design_system.py +1067 -1067
  34. package/core/.claude/skills/ui-ux-pro-max/scripts/search.py +106 -106
  35. package/core/.collaboration/core/task-api.js +1 -4
  36. package/core/.formation/README.md +21 -146
  37. package/core/.formation/bifrost/codex-doctor.js +37 -2
  38. package/core/.formation/bifrost/codex-doctor.test.js +18 -0
  39. package/core/.formation/bifrost/launch.js +1 -1
  40. package/core/.formation/bifrost/openai/codex-hook-shim.js +6 -0
  41. package/core/.formation/bifrost/openai/codex-hook-shim.test.js +11 -1
  42. package/core/.formation/bifrost/setup-codex.js +3 -0
  43. package/core/.formation/native/claude/README.md +1 -1
  44. package/core/.formation/role-map.yaml +3 -3
  45. package/core/.formation/spec/task_operations.parity.test.js +0 -1
  46. package/core/.formation/spec/task_operations.yaml +3 -3
  47. package/core/.formation/tests/hello.txt +2 -2
  48. package/core/.formation/tools/__tests__/subagent-bifrost-guard.test.js +4 -4
  49. package/core/.formation/tools/dashboard-models-chain.test.js +0 -6
  50. package/core/.formation/tools/dispatch-detail/cross-project-worktree.md +1 -9
  51. package/core/.formation/tools/dispatch-detail/worktree-subagent-mode.md +2 -2
  52. package/core/.formation/tools/dispatch.js +33 -554
  53. package/core/.formation/tools/mcp-allowlist.test.js +0 -1
  54. package/core/.formation/tools/post-complete/task2345.test.js +0 -5
  55. package/core/.formation/tools/pre-dispatch-snapshot.test.js +0 -100
  56. package/core/.formation/tools/prompt.js +33 -1623
  57. package/core/.formation/tools/prompt.test.js +26 -594
  58. package/core/.formation/unit/do.yaml +2 -4
  59. package/core/.kenaz/knowledge/ladybug_2c51da1e_shadow_file_frame_group.patch +163 -163
  60. package/core/.kenaz/knowledge/ladybug_30cf85690e_shadow_page_read_during_checkpoint.patch +393 -393
  61. package/core/.kenaz/knowledge/ladybug_7a17858d3a_node_group_delete_lock.patch +102 -102
  62. package/core/.kenaz/knowledge/ladybug_926a0ba1_column_checkpoint.patch +412 -412
  63. package/core/.kenaz/knowledge/ladybug_a346c4c4b6_pk_index_header_pages.patch +144 -144
  64. package/core/.kenaz/knowledge/ladybug_ba5f38815b_csr_node_group.patch +86 -86
  65. package/core/.kenaz/muninn_episode_write.ps1 +17 -17
  66. package/core/.kenaz/specialist/field_report_watermark.json +1 -1
  67. package/core/.kenaz/specialist/memory_guard_fired.json +1 -1
  68. package/core/.kenaz/specialist/memory_guard_trigger_cache.json +1 -1
  69. package/core/.kenaz/specialist/t1777_probe.txt +1 -1
  70. package/core/.matrix/index.matrix +5 -5
  71. package/core/.rule/general/agent-workflow.yaml +4 -4
  72. package/core/.rule/general/file-size-threshold.yaml +16 -4
  73. package/core/.rule/general/rust-verify-target.yaml +0 -1
  74. package/core/.rule/general/system-invariants.yaml +8 -25
  75. package/core/.specialist/Ansuz/SYSTEM_DOC.md +17 -42
  76. package/core/.specialist/Ansuz/capability.yaml +7 -14
  77. package/core/.specialist/Ansuz/concepts/formation_quick_ref.yaml +4 -11
  78. package/core/.specialist/Ansuz/elixir.yaml +3 -4
  79. package/core/.specialist/Ansuz/identity_contract.yaml +1 -1
  80. package/core/.specialist/Ansuz/knowledge.yaml +7 -10
  81. package/core/.specialist/Ansuz/rule.yaml +2 -2
  82. package/core/.specialist/Ansuz/tools/verify-and-land.js +22 -0
  83. package/core/.specialist/Ansuz/tools/verify-and-land.test.js +59 -5
  84. package/core/.specialist/Ansuz/workflow.yaml +7 -11
  85. package/core/.specialist/Forseti/core.yaml +1 -1
  86. package/core/.specialist/Heimdall/SKILL.md +1 -1
  87. package/core/.specialist/Heimdall/core.yaml +2 -2
  88. package/core/.specialist/Huginn/rule.yaml +1 -1
  89. package/core/.specialist/tools/README.md +2 -2
  90. package/core/.specialist/tools/__tests__/freya-cli-contract.test.js +0 -1
  91. package/core/.system/collaboration/README.md +1 -1
  92. package/core/.system/formation/LEGACY_SKILLS.md +2 -0
  93. package/core/.system/formation/POST_COMPLETE.md +2 -0
  94. package/core/.system/formation/README.md +2 -0
  95. package/core/KENAZ_CORE_VERSION +1 -1
  96. package/core/dev/ANSUZ_STARTUP_INJECTION_REVIEW.md +355 -355
  97. package/core/dev/JEV_INTEGRATION_PLAN.md +189 -189
  98. package/core/dev/design/matrix_format_EXAMPLE_memory.matrix +13 -13
  99. package/core/dev/design/matrix_format_EXAMPLE_project_status.matrix +9 -9
  100. package/core/dev/design/matrix_format_EXAMPLE_tasks.matrix +14 -14
  101. package/core/dev/design/matrix_v2_EXAMPLE.matrix +67 -67
  102. package/core/dev/jev-eval/ACTIVE_CORRECTION.md +52 -52
  103. package/core/dev/jev-eval/CONTEXT_REVIEW.md +31 -31
  104. package/core/dev/jev-eval/EVALUATION_STATUS.md +39 -39
  105. package/core/dev/jev-eval/HISTORICAL_CORPUS.md +33 -33
  106. package/core/dev/jev-eval/README.md +99 -99
  107. package/core/dev/jev-eval/THALAMUS_RANKING.md +58 -58
  108. package/core/dev/jev-eval/reports/annotation-pilot-2026-09-20.json +32 -32
  109. package/core/dev/jev-eval/reports/context-review-2026-09-20.json +48 -48
  110. package/core/dev/jev-eval/reports/history-corpus-2026-09-20.json +52 -52
  111. package/core/dev/pipeline/SELF_ENHANCEMENT_STATUS.json.bak +47 -47
  112. package/core/dev/pipeline/tasks/.gitkeep +1 -1
  113. package/core/dev/pipeline/test_results/TASK_TEST_PARALLEL_002_result.txt +27 -27
  114. package/core/dev/scripts/check-file-size.js +44 -17
  115. package/core/dev/scripts/init_git.sh +0 -0
  116. package/core/dev/scripts/init_project.sh +0 -0
  117. package/core/dev/scripts/legacy/dev_agent_scheduler.ps1.bak +536 -536
  118. package/core/dev/scripts/legacy/multi_agent_scheduler.ps1.bak +1978 -1978
  119. package/core/dev/scripts/matrix/src/cli/commands/list-tags.ts.wip +140 -140
  120. package/core/dev/scripts/matrix/src/cli/commands/manage-tags.ts.wip +173 -173
  121. package/core/dev/scripts/matrix/src/cli/commands/stats.ts.wip +168 -168
  122. package/core/dev/scripts/matrix/src/cli/commands/tokens.ts.wip +126 -126
  123. package/core/dev/scripts/multi_agent_scheduler.sh +0 -0
  124. package/core/dev/scripts/run_once.sh +0 -0
  125. package/core/dev/scripts/setup_mcp.sh +0 -0
  126. package/core/dev/scripts/setup_rag.sh +0 -0
  127. package/core/dev/scripts/start_dev_agent.sh +0 -0
  128. package/core/dev/scripts/start_multi_agent.sh +0 -0
  129. package/core/dev/scripts/sync_task_queue.sh +0 -0
  130. package/core/k-cli/README.md +1 -94
  131. package/core/k-cli/test/TEST_SPEC.md +5 -22
  132. package/core/k-cli/test/run.sh +1 -67
  133. package/core/plugins/kenaz/commands/Amelia.md +1 -1
  134. package/core/plugins/kenaz/commands/Ansuz.md +16 -3
  135. package/core/plugins/kenaz/scripts/kenaz-mode.js +11 -1
  136. package/core/plugins/kenaz/scripts/sync.js +6 -2
  137. package/core/plugins/kenaz/scripts/ygg.js +13 -1
  138. package/core/plugins/kenaz/skills/product_manager/SKILL.md +12 -12
  139. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/charts.csv +26 -26
  140. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/colors.csv +97 -97
  141. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/icons.csv +101 -101
  142. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/landing.csv +31 -31
  143. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/products.csv +96 -96
  144. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/react-performance.csv +45 -45
  145. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -54
  146. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -53
  147. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -56
  148. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -53
  149. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -53
  150. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -51
  151. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -59
  152. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -52
  153. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/react.csv +54 -54
  154. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -61
  155. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -54
  156. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -51
  157. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -50
  158. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/styles.csv +68 -68
  159. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/typography.csv +57 -57
  160. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -101
  161. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/ux-guidelines.csv +99 -99
  162. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/web-interface.csv +31 -31
  163. package/core/plugins/kenaz/skills/ui-ux-pro-max/scripts/core.py +253 -253
  164. package/core/plugins/kenaz/skills/ui-ux-pro-max/scripts/design_system.py +1067 -1067
  165. package/core/plugins/kenaz/skills/ui-ux-pro-max/scripts/search.py +106 -106
  166. package/lib/cli.js +11 -4
  167. package/package.json +11 -7
  168. package/vendor/install-kenaz-cli.js +18 -7
  169. package/core/.formation/legion/forge.yaml +0 -299
  170. package/core/.formation/legion/hive.yaml +0 -238
  171. package/core/.formation/squad/blitz.yaml +0 -192
  172. package/core/.formation/squad/strategy-tribunal.yaml +0 -354
  173. package/core/.formation/squad/strategy.yaml +0 -349
  174. package/core/.formation/squad/sweep.yaml +0 -208
  175. package/core/.formation/tools/broadcast.js +0 -103
  176. package/core/.formation/tools/dispatch-dryrun.test.js +0 -54
  177. package/core/.formation/tools/dispatch-notifier.js +0 -84
  178. package/core/.formation/tools/formations.js +0 -1203
  179. package/core/.formation/tools/headless-branch-recovery.test.js +0 -64
  180. package/core/.formation/tools/headless-dryrun.test.js +0 -79
  181. package/core/.formation/tools/headless-v2.js +0 -659
  182. package/core/.formation/tools/headless.js +0 -1071
  183. package/core/.formation/tools/legacy-scheduler.js +0 -1994
  184. package/core/.formation/tools/logger.js +0 -273
  185. package/core/.formation/tools/memory-loader.js +0 -422
  186. package/core/.formation/tools/process-state.js +0 -33
  187. package/core/.formation/tools/remote-conflict.js +0 -98
  188. package/core/.formation/tools/runner.js +0 -1332
  189. package/core/.formation/tools/task-utils.js +0 -344
  190. package/core/.formation/tools/task2028-headless-worktree.test.js +0 -112
  191. package/core/.formation/tools/tests/headless-mcp-args-plumbing.test.js +0 -123
  192. package/core/.formation/tools/worktree-leak.test.js +0 -162
  193. package/core/k-cli/dist/alaya/run.js +0 -91
  194. package/core/k-cli/dist/core/output.js +0 -24
  195. package/core/k-cli/dist/core/process.js +0 -38
  196. package/core/k-cli/dist/core/project.js +0 -80
  197. package/core/k-cli/dist/core/spawn.js +0 -268
  198. package/core/k-cli/dist/core/types.js +0 -2
  199. package/core/k-cli/dist/dashboard/update.js +0 -53
  200. package/core/k-cli/dist/dashboard/version.js +0 -20
  201. package/core/k-cli/dist/dispatch/run.js +0 -241
  202. package/core/k-cli/dist/dispatch/status.js +0 -103
  203. package/core/k-cli/dist/index.js +0 -283
  204. package/core/k-cli/dist/project/initialize.js +0 -301
  205. package/core/k-cli/dist/project/install.js +0 -89
  206. package/core/k-cli/dist/project/status.js +0 -138
  207. package/core/k-cli/dist/specialist/list.js +0 -100
  208. package/core/k-cli/dist/specialist/run.js +0 -93
  209. package/core/k-cli/dist/task/create.js +0 -194
  210. package/core/k-cli/dist/task/delete.js +0 -98
  211. package/core/k-cli/dist/task/get.js +0 -55
  212. package/core/k-cli/dist/task/list.js +0 -58
  213. package/core/k-cli/dist/task/submit.js +0 -156
  214. package/core/k-cli/dist/ygg/run.js +0 -51
  215. package/core/k-cli/node_modules/.bin/yaml +0 -16
  216. package/core/k-cli/node_modules/.bin/yaml.cmd +0 -17
  217. package/core/k-cli/node_modules/.bin/yaml.ps1 +0 -28
  218. package/core/k-cli/node_modules/.package-lock.json +0 -23
  219. package/core/k-cli/node_modules/yaml/LICENSE +0 -13
  220. package/core/k-cli/node_modules/yaml/README.md +0 -172
  221. package/core/k-cli/node_modules/yaml/bin.mjs +0 -11
  222. package/core/k-cli/node_modules/yaml/browser/dist/compose/compose-collection.js +0 -88
  223. package/core/k-cli/node_modules/yaml/browser/dist/compose/compose-doc.js +0 -43
  224. package/core/k-cli/node_modules/yaml/browser/dist/compose/compose-node.js +0 -109
  225. package/core/k-cli/node_modules/yaml/browser/dist/compose/compose-scalar.js +0 -86
  226. package/core/k-cli/node_modules/yaml/browser/dist/compose/composer.js +0 -217
  227. package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-block-map.js +0 -115
  228. package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-block-scalar.js +0 -198
  229. package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-block-seq.js +0 -49
  230. package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-end.js +0 -37
  231. package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-flow-collection.js +0 -207
  232. package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-flow-scalar.js +0 -223
  233. package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-props.js +0 -146
  234. package/core/k-cli/node_modules/yaml/browser/dist/compose/util-contains-newline.js +0 -34
  235. package/core/k-cli/node_modules/yaml/browser/dist/compose/util-empty-scalar-position.js +0 -26
  236. package/core/k-cli/node_modules/yaml/browser/dist/compose/util-flow-indent-check.js +0 -15
  237. package/core/k-cli/node_modules/yaml/browser/dist/compose/util-map-includes.js +0 -13
  238. package/core/k-cli/node_modules/yaml/browser/dist/doc/Document.js +0 -335
  239. package/core/k-cli/node_modules/yaml/browser/dist/doc/anchors.js +0 -71
  240. package/core/k-cli/node_modules/yaml/browser/dist/doc/applyReviver.js +0 -55
  241. package/core/k-cli/node_modules/yaml/browser/dist/doc/createNode.js +0 -88
  242. package/core/k-cli/node_modules/yaml/browser/dist/doc/directives.js +0 -176
  243. package/core/k-cli/node_modules/yaml/browser/dist/errors.js +0 -57
  244. package/core/k-cli/node_modules/yaml/browser/dist/index.js +0 -17
  245. package/core/k-cli/node_modules/yaml/browser/dist/log.js +0 -11
  246. package/core/k-cli/node_modules/yaml/browser/dist/nodes/Alias.js +0 -114
  247. package/core/k-cli/node_modules/yaml/browser/dist/nodes/Collection.js +0 -147
  248. package/core/k-cli/node_modules/yaml/browser/dist/nodes/Node.js +0 -38
  249. package/core/k-cli/node_modules/yaml/browser/dist/nodes/Pair.js +0 -36
  250. package/core/k-cli/node_modules/yaml/browser/dist/nodes/Scalar.js +0 -24
  251. package/core/k-cli/node_modules/yaml/browser/dist/nodes/YAMLMap.js +0 -144
  252. package/core/k-cli/node_modules/yaml/browser/dist/nodes/YAMLSeq.js +0 -113
  253. package/core/k-cli/node_modules/yaml/browser/dist/nodes/addPairToJSMap.js +0 -63
  254. package/core/k-cli/node_modules/yaml/browser/dist/nodes/identity.js +0 -36
  255. package/core/k-cli/node_modules/yaml/browser/dist/nodes/toJS.js +0 -37
  256. package/core/k-cli/node_modules/yaml/browser/dist/parse/cst-scalar.js +0 -214
  257. package/core/k-cli/node_modules/yaml/browser/dist/parse/cst-stringify.js +0 -61
  258. package/core/k-cli/node_modules/yaml/browser/dist/parse/cst-visit.js +0 -97
  259. package/core/k-cli/node_modules/yaml/browser/dist/parse/cst.js +0 -98
  260. package/core/k-cli/node_modules/yaml/browser/dist/parse/lexer.js +0 -717
  261. package/core/k-cli/node_modules/yaml/browser/dist/parse/line-counter.js +0 -39
  262. package/core/k-cli/node_modules/yaml/browser/dist/parse/parser.js +0 -967
  263. package/core/k-cli/node_modules/yaml/browser/dist/public-api.js +0 -102
  264. package/core/k-cli/node_modules/yaml/browser/dist/schema/Schema.js +0 -37
  265. package/core/k-cli/node_modules/yaml/browser/dist/schema/common/map.js +0 -17
  266. package/core/k-cli/node_modules/yaml/browser/dist/schema/common/null.js +0 -15
  267. package/core/k-cli/node_modules/yaml/browser/dist/schema/common/seq.js +0 -17
  268. package/core/k-cli/node_modules/yaml/browser/dist/schema/common/string.js +0 -14
  269. package/core/k-cli/node_modules/yaml/browser/dist/schema/core/bool.js +0 -19
  270. package/core/k-cli/node_modules/yaml/browser/dist/schema/core/float.js +0 -43
  271. package/core/k-cli/node_modules/yaml/browser/dist/schema/core/int.js +0 -38
  272. package/core/k-cli/node_modules/yaml/browser/dist/schema/core/schema.js +0 -23
  273. package/core/k-cli/node_modules/yaml/browser/dist/schema/json/schema.js +0 -62
  274. package/core/k-cli/node_modules/yaml/browser/dist/schema/tags.js +0 -96
  275. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/binary.js +0 -58
  276. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/bool.js +0 -26
  277. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/float.js +0 -46
  278. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/int.js +0 -71
  279. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/merge.js +0 -64
  280. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/omap.js +0 -74
  281. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/pairs.js +0 -78
  282. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/schema.js +0 -39
  283. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/set.js +0 -93
  284. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/timestamp.js +0 -101
  285. package/core/k-cli/node_modules/yaml/browser/dist/stringify/foldFlowLines.js +0 -146
  286. package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringify.js +0 -129
  287. package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyCollection.js +0 -153
  288. package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyComment.js +0 -20
  289. package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyDocument.js +0 -85
  290. package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyNumber.js +0 -24
  291. package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyPair.js +0 -150
  292. package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyString.js +0 -336
  293. package/core/k-cli/node_modules/yaml/browser/dist/util.js +0 -11
  294. package/core/k-cli/node_modules/yaml/browser/dist/visit.js +0 -233
  295. package/core/k-cli/node_modules/yaml/browser/index.js +0 -5
  296. package/core/k-cli/node_modules/yaml/browser/package.json +0 -3
  297. package/core/k-cli/node_modules/yaml/dist/cli.d.ts +0 -8
  298. package/core/k-cli/node_modules/yaml/dist/cli.mjs +0 -201
  299. package/core/k-cli/node_modules/yaml/dist/compose/compose-collection.d.ts +0 -11
  300. package/core/k-cli/node_modules/yaml/dist/compose/compose-collection.js +0 -90
  301. package/core/k-cli/node_modules/yaml/dist/compose/compose-doc.d.ts +0 -7
  302. package/core/k-cli/node_modules/yaml/dist/compose/compose-doc.js +0 -45
  303. package/core/k-cli/node_modules/yaml/dist/compose/compose-node.d.ts +0 -29
  304. package/core/k-cli/node_modules/yaml/dist/compose/compose-node.js +0 -112
  305. package/core/k-cli/node_modules/yaml/dist/compose/compose-scalar.d.ts +0 -5
  306. package/core/k-cli/node_modules/yaml/dist/compose/compose-scalar.js +0 -88
  307. package/core/k-cli/node_modules/yaml/dist/compose/composer.d.ts +0 -63
  308. package/core/k-cli/node_modules/yaml/dist/compose/composer.js +0 -222
  309. package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-map.d.ts +0 -6
  310. package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-map.js +0 -117
  311. package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-scalar.d.ts +0 -11
  312. package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-scalar.js +0 -200
  313. package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-seq.d.ts +0 -6
  314. package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-seq.js +0 -51
  315. package/core/k-cli/node_modules/yaml/dist/compose/resolve-end.d.ts +0 -6
  316. package/core/k-cli/node_modules/yaml/dist/compose/resolve-end.js +0 -39
  317. package/core/k-cli/node_modules/yaml/dist/compose/resolve-flow-collection.d.ts +0 -7
  318. package/core/k-cli/node_modules/yaml/dist/compose/resolve-flow-collection.js +0 -209
  319. package/core/k-cli/node_modules/yaml/dist/compose/resolve-flow-scalar.d.ts +0 -10
  320. package/core/k-cli/node_modules/yaml/dist/compose/resolve-flow-scalar.js +0 -225
  321. package/core/k-cli/node_modules/yaml/dist/compose/resolve-props.d.ts +0 -23
  322. package/core/k-cli/node_modules/yaml/dist/compose/resolve-props.js +0 -148
  323. package/core/k-cli/node_modules/yaml/dist/compose/util-contains-newline.d.ts +0 -2
  324. package/core/k-cli/node_modules/yaml/dist/compose/util-contains-newline.js +0 -36
  325. package/core/k-cli/node_modules/yaml/dist/compose/util-empty-scalar-position.d.ts +0 -2
  326. package/core/k-cli/node_modules/yaml/dist/compose/util-empty-scalar-position.js +0 -28
  327. package/core/k-cli/node_modules/yaml/dist/compose/util-flow-indent-check.d.ts +0 -3
  328. package/core/k-cli/node_modules/yaml/dist/compose/util-flow-indent-check.js +0 -17
  329. package/core/k-cli/node_modules/yaml/dist/compose/util-map-includes.d.ts +0 -4
  330. package/core/k-cli/node_modules/yaml/dist/compose/util-map-includes.js +0 -15
  331. package/core/k-cli/node_modules/yaml/dist/doc/Document.d.ts +0 -141
  332. package/core/k-cli/node_modules/yaml/dist/doc/Document.js +0 -337
  333. package/core/k-cli/node_modules/yaml/dist/doc/anchors.d.ts +0 -24
  334. package/core/k-cli/node_modules/yaml/dist/doc/anchors.js +0 -76
  335. package/core/k-cli/node_modules/yaml/dist/doc/applyReviver.d.ts +0 -9
  336. package/core/k-cli/node_modules/yaml/dist/doc/applyReviver.js +0 -57
  337. package/core/k-cli/node_modules/yaml/dist/doc/createNode.d.ts +0 -17
  338. package/core/k-cli/node_modules/yaml/dist/doc/createNode.js +0 -90
  339. package/core/k-cli/node_modules/yaml/dist/doc/directives.d.ts +0 -49
  340. package/core/k-cli/node_modules/yaml/dist/doc/directives.js +0 -178
  341. package/core/k-cli/node_modules/yaml/dist/errors.d.ts +0 -21
  342. package/core/k-cli/node_modules/yaml/dist/errors.js +0 -62
  343. package/core/k-cli/node_modules/yaml/dist/index.d.ts +0 -25
  344. package/core/k-cli/node_modules/yaml/dist/index.js +0 -50
  345. package/core/k-cli/node_modules/yaml/dist/log.d.ts +0 -3
  346. package/core/k-cli/node_modules/yaml/dist/log.js +0 -19
  347. package/core/k-cli/node_modules/yaml/dist/nodes/Alias.d.ts +0 -29
  348. package/core/k-cli/node_modules/yaml/dist/nodes/Alias.js +0 -116
  349. package/core/k-cli/node_modules/yaml/dist/nodes/Collection.d.ts +0 -73
  350. package/core/k-cli/node_modules/yaml/dist/nodes/Collection.js +0 -151
  351. package/core/k-cli/node_modules/yaml/dist/nodes/Node.d.ts +0 -53
  352. package/core/k-cli/node_modules/yaml/dist/nodes/Node.js +0 -40
  353. package/core/k-cli/node_modules/yaml/dist/nodes/Pair.d.ts +0 -22
  354. package/core/k-cli/node_modules/yaml/dist/nodes/Pair.js +0 -39
  355. package/core/k-cli/node_modules/yaml/dist/nodes/Scalar.d.ts +0 -43
  356. package/core/k-cli/node_modules/yaml/dist/nodes/Scalar.js +0 -27
  357. package/core/k-cli/node_modules/yaml/dist/nodes/YAMLMap.d.ts +0 -53
  358. package/core/k-cli/node_modules/yaml/dist/nodes/YAMLMap.js +0 -147
  359. package/core/k-cli/node_modules/yaml/dist/nodes/YAMLSeq.d.ts +0 -60
  360. package/core/k-cli/node_modules/yaml/dist/nodes/YAMLSeq.js +0 -115
  361. package/core/k-cli/node_modules/yaml/dist/nodes/addPairToJSMap.d.ts +0 -4
  362. package/core/k-cli/node_modules/yaml/dist/nodes/addPairToJSMap.js +0 -65
  363. package/core/k-cli/node_modules/yaml/dist/nodes/identity.d.ts +0 -23
  364. package/core/k-cli/node_modules/yaml/dist/nodes/identity.js +0 -53
  365. package/core/k-cli/node_modules/yaml/dist/nodes/toJS.d.ts +0 -29
  366. package/core/k-cli/node_modules/yaml/dist/nodes/toJS.js +0 -39
  367. package/core/k-cli/node_modules/yaml/dist/options.d.ts +0 -350
  368. package/core/k-cli/node_modules/yaml/dist/parse/cst-scalar.d.ts +0 -64
  369. package/core/k-cli/node_modules/yaml/dist/parse/cst-scalar.js +0 -218
  370. package/core/k-cli/node_modules/yaml/dist/parse/cst-stringify.d.ts +0 -8
  371. package/core/k-cli/node_modules/yaml/dist/parse/cst-stringify.js +0 -63
  372. package/core/k-cli/node_modules/yaml/dist/parse/cst-visit.d.ts +0 -39
  373. package/core/k-cli/node_modules/yaml/dist/parse/cst-visit.js +0 -99
  374. package/core/k-cli/node_modules/yaml/dist/parse/cst.d.ts +0 -109
  375. package/core/k-cli/node_modules/yaml/dist/parse/cst.js +0 -112
  376. package/core/k-cli/node_modules/yaml/dist/parse/lexer.d.ts +0 -87
  377. package/core/k-cli/node_modules/yaml/dist/parse/lexer.js +0 -719
  378. package/core/k-cli/node_modules/yaml/dist/parse/line-counter.d.ts +0 -22
  379. package/core/k-cli/node_modules/yaml/dist/parse/line-counter.js +0 -41
  380. package/core/k-cli/node_modules/yaml/dist/parse/parser.d.ts +0 -84
  381. package/core/k-cli/node_modules/yaml/dist/parse/parser.js +0 -972
  382. package/core/k-cli/node_modules/yaml/dist/public-api.d.ts +0 -44
  383. package/core/k-cli/node_modules/yaml/dist/public-api.js +0 -107
  384. package/core/k-cli/node_modules/yaml/dist/schema/Schema.d.ts +0 -17
  385. package/core/k-cli/node_modules/yaml/dist/schema/Schema.js +0 -39
  386. package/core/k-cli/node_modules/yaml/dist/schema/common/map.d.ts +0 -2
  387. package/core/k-cli/node_modules/yaml/dist/schema/common/map.js +0 -19
  388. package/core/k-cli/node_modules/yaml/dist/schema/common/null.d.ts +0 -4
  389. package/core/k-cli/node_modules/yaml/dist/schema/common/null.js +0 -17
  390. package/core/k-cli/node_modules/yaml/dist/schema/common/seq.d.ts +0 -2
  391. package/core/k-cli/node_modules/yaml/dist/schema/common/seq.js +0 -19
  392. package/core/k-cli/node_modules/yaml/dist/schema/common/string.d.ts +0 -2
  393. package/core/k-cli/node_modules/yaml/dist/schema/common/string.js +0 -16
  394. package/core/k-cli/node_modules/yaml/dist/schema/core/bool.d.ts +0 -4
  395. package/core/k-cli/node_modules/yaml/dist/schema/core/bool.js +0 -21
  396. package/core/k-cli/node_modules/yaml/dist/schema/core/float.d.ts +0 -4
  397. package/core/k-cli/node_modules/yaml/dist/schema/core/float.js +0 -47
  398. package/core/k-cli/node_modules/yaml/dist/schema/core/int.d.ts +0 -4
  399. package/core/k-cli/node_modules/yaml/dist/schema/core/int.js +0 -42
  400. package/core/k-cli/node_modules/yaml/dist/schema/core/schema.d.ts +0 -1
  401. package/core/k-cli/node_modules/yaml/dist/schema/core/schema.js +0 -25
  402. package/core/k-cli/node_modules/yaml/dist/schema/json/schema.d.ts +0 -2
  403. package/core/k-cli/node_modules/yaml/dist/schema/json/schema.js +0 -64
  404. package/core/k-cli/node_modules/yaml/dist/schema/json-schema.d.ts +0 -69
  405. package/core/k-cli/node_modules/yaml/dist/schema/tags.d.ts +0 -48
  406. package/core/k-cli/node_modules/yaml/dist/schema/tags.js +0 -99
  407. package/core/k-cli/node_modules/yaml/dist/schema/types.d.ts +0 -92
  408. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/binary.d.ts +0 -2
  409. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/binary.js +0 -70
  410. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/bool.d.ts +0 -7
  411. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/bool.js +0 -29
  412. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/float.d.ts +0 -4
  413. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/float.js +0 -50
  414. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/int.d.ts +0 -5
  415. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/int.js +0 -76
  416. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/merge.d.ts +0 -9
  417. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/merge.js +0 -68
  418. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/omap.d.ts +0 -22
  419. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/omap.js +0 -77
  420. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/pairs.d.ts +0 -10
  421. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/pairs.js +0 -82
  422. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/schema.d.ts +0 -1
  423. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/schema.js +0 -41
  424. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/set.d.ts +0 -28
  425. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/set.js +0 -96
  426. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/timestamp.d.ts +0 -6
  427. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/timestamp.js +0 -105
  428. package/core/k-cli/node_modules/yaml/dist/stringify/foldFlowLines.d.ts +0 -34
  429. package/core/k-cli/node_modules/yaml/dist/stringify/foldFlowLines.js +0 -151
  430. package/core/k-cli/node_modules/yaml/dist/stringify/stringify.d.ts +0 -21
  431. package/core/k-cli/node_modules/yaml/dist/stringify/stringify.js +0 -132
  432. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyCollection.d.ts +0 -17
  433. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyCollection.js +0 -155
  434. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyComment.d.ts +0 -10
  435. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyComment.js +0 -24
  436. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyDocument.d.ts +0 -4
  437. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyDocument.js +0 -87
  438. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyNumber.d.ts +0 -2
  439. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyNumber.js +0 -26
  440. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyPair.d.ts +0 -3
  441. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyPair.js +0 -152
  442. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyString.d.ts +0 -9
  443. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyString.js +0 -338
  444. package/core/k-cli/node_modules/yaml/dist/test-events.d.ts +0 -4
  445. package/core/k-cli/node_modules/yaml/dist/test-events.js +0 -134
  446. package/core/k-cli/node_modules/yaml/dist/util.d.ts +0 -16
  447. package/core/k-cli/node_modules/yaml/dist/util.js +0 -28
  448. package/core/k-cli/node_modules/yaml/dist/visit.d.ts +0 -102
  449. package/core/k-cli/node_modules/yaml/dist/visit.js +0 -236
  450. package/core/k-cli/node_modules/yaml/package.json +0 -97
  451. package/core/k-cli/node_modules/yaml/util.js +0 -2
@@ -1,412 +1,412 @@
1
- From 926a0ba10d221eb437add0885ff492e7576bb7c9 Mon Sep 17 00:00:00 2001
2
- From: kory-io <kory-io@users.noreply.github.com>
3
- Date: Thu, 24 Sep 2026 18:56:55 -0600
4
- Subject: [PATCH] fix(storage): keep segment on disk when an out-of-place
5
- checkpoint fails
6
-
7
- Column::checkpointColumnChunkOutOfPlace rewrote a segment by switching
8
- the persistent ColumnChunkData itself to IN_MEMORY (setToInMemory) and
9
- then resizing, scanning, writing, finalizing and flushing it. Any of
10
- those steps can throw, for example "Unable to allocate memory! The
11
- buffer pool is full" from the resize or the scan. The checkpoint is
12
- then rolled back, but Checkpointer::rollback only discards pending page
13
- frees and rolls back node-table indexes; it does not touch node group
14
- state. The node group keeps the half-rewritten segment, which stays
15
- IN_MEMORY, and with runtime checks enabled (Debug, or RUNTIME_CHECKS=1
16
- as in CI) every later checkpoint of that table fails the
17
- `segment->getResidencyState() == ResidencyState::ON_DISK` assertion in
18
- ColumnChunk::checkpoint, so the database can no longer checkpoint.
19
-
20
- Rewrite the segment into a new in-memory chunk of the same kind
21
- instead, and return it once it has been flushed, the same way split
22
- segments are already returned. The persistent segment object is not
23
- touched until the rewrite has succeeded, and its pages are only freed
24
- after that, so a failure leaves it on disk and unchanged and the next
25
- checkpoint starts from it again. Segments rewritten earlier in the
26
- same failed checkpoint stay replaced; their old pages are only
27
- reclaimed after the database is reopened.
28
-
29
- ColumnChunk::checkpoint already installs returned segments. The nested
30
- null, list offset and list size chunks, which are checkpointed without
31
- splitting, now install the returned chunk into their parent.
32
- StringColumn and StructColumn also call syncNumValues on the returned
33
- segments, as they already did on the persistent segment.
34
-
35
- Add FlakyCheckpointerTest.RecoverFromFailureDuringOutOfPlaceCheckpoint.
36
- It fails a checkpoint at the flush of an out-of-place rewrite, then
37
- checks that the data is intact and that the next checkpoint succeeds.
38
- Add an e2e case that checkpoints a list column in place while its
39
- offsets and nulls are rewritten out of place.
40
- ---
41
- src/include/storage/table/column.h | 4 +-
42
- src/storage/table/column.cpp | 58 +++++++---
43
- src/storage/table/column_chunk.cpp | 4 +-
44
- src/storage/table/list_column.cpp | 15 ++-
45
- src/storage/table/string_column.cpp | 3 +
46
- src/storage/table/struct_column.cpp | 3 +
47
- .../transaction/set_node/set_empty.test | 41 +++++++
48
- test/transaction/checkpoint_test.cpp | 106 ++++++++++++++++++
49
- 8 files changed, 209 insertions(+), 25 deletions(-)
50
-
51
- diff --git a/src/include/storage/table/column.h b/src/include/storage/table/column.h
52
- index 16584cb027..d56fefbf5f 100644
53
- --- a/src/include/storage/table/column.h
54
- +++ b/src/include/storage/table/column.h
55
- @@ -95,8 +95,8 @@ class Column {
56
- return common::dynamic_cast_checked<TARGET&>(*this);
57
- }
58
-
59
- - // Return value is the new segments if segment splitting occurs during an out of place
60
- - // checkpoint
61
- + // Return value is the new segment(s) replacing the persistent segment if it was checkpointed
62
- + // out of place (more than one if it was split), or empty if it was checkpointed in place
63
- virtual std::vector<std::unique_ptr<ColumnChunkData>> checkpointSegment(
64
- ColumnCheckpointState&& checkpointState, PageAllocator& pageAllocator,
65
- bool canSplitSegment = true) const;
66
- diff --git a/src/storage/table/column.cpp b/src/storage/table/column.cpp
67
- index 72b9a2f206..9d23776188 100644
68
- --- a/src/storage/table/column.cpp
69
- +++ b/src/storage/table/column.cpp
70
- @@ -3,6 +3,7 @@
71
- #include <algorithm>
72
- #include <cstdint>
73
- #include <memory>
74
- +#include <tuple>
75
-
76
- #include "common/assert.h"
77
- #include "common/data_chunk/sel_vector.h"
78
- @@ -439,42 +440,63 @@ void Column::checkpointNullData(const ColumnCheckpointState& checkpointState,
79
- segmentCheckpointState.numRows);
80
- }
81
- DASSERT(checkpointState.persistentData.hasNullData());
82
- - nullColumn->checkpointSegment(
83
- + auto newNullData = nullColumn->checkpointSegment(
84
- ColumnCheckpointState(*checkpointState.persistentData.getNullData(),
85
- std::move(nullSegmentCheckpointStates)),
86
- pageAllocator, false);
87
- + if (!newNullData.empty()) {
88
- + DASSERT(newNullData.size() == 1);
89
- + auto* nullChunk = &newNullData[0]->cast<NullChunkData>();
90
- + std::ignore = newNullData[0].release();
91
- + checkpointState.persistentData.setNullData(std::unique_ptr<NullChunkData>(nullChunk));
92
- + }
93
- +}
94
- +
95
- +static std::unique_ptr<ColumnChunkData> createInMemoryChunkLike(const ColumnChunkData& chunk,
96
- + uint64_t capacity) {
97
- + if (dynamic_cast<const NullChunkData*>(&chunk) != nullptr) {
98
- + return ColumnChunkFactory::createNullChunkData(chunk.getMemoryManager(),
99
- + chunk.isCompressionEnabled(), capacity, ResidencyState::IN_MEMORY);
100
- + }
101
- + return ColumnChunkFactory::createColumnChunkData(chunk.getMemoryManager(),
102
- + chunk.getDataType().copy(), chunk.isCompressionEnabled(), capacity,
103
- + ResidencyState::IN_MEMORY, chunk.hasNullData());
104
- }
105
-
106
- std::vector<std::unique_ptr<ColumnChunkData>> Column::checkpointColumnChunkOutOfPlace(
107
- const SegmentState& state, const ColumnCheckpointState& checkpointState,
108
- PageAllocator& pageAllocator, bool canSplitSegment) const {
109
- const auto numRows = std::max(checkpointState.endRowIdxToWrite, state.metadata.numValues);
110
- - checkpointState.persistentData.setToInMemory();
111
- - checkpointState.persistentData.resize(numRows);
112
- - DASSERT(checkpointState.persistentData.getNumValues() == 0);
113
- - scanSegment(state, &checkpointState.persistentData, 0, state.metadata.numValues);
114
- - state.reclaimAllocatedPages(pageAllocator);
115
- + // Rewrite the segment into a new chunk instead of converting persistentData to an in-memory
116
- + // chunk in place. If the rewrite fails part way (e.g. the buffer pool is full), the checkpoint
117
- + // is rolled back but the segment is kept, so it must still describe its on-disk pages for the
118
- + // data to stay readable and for the next checkpoint to be able to rewrite it.
119
- + auto newData = createInMemoryChunkLike(checkpointState.persistentData, numRows);
120
- + DASSERT(newData->getNumValues() == 0);
121
- + scanSegment(state, newData.get(), 0, state.metadata.numValues);
122
- // TODO(bmwinger): for simple compression types, we can predict whether or not we will need to
123
- // split the segment and avoid having to re-write it multiple times
124
- for (auto& segmentCheckpointState : checkpointState.segmentCheckpointStates) {
125
- - checkpointState.persistentData.write(&segmentCheckpointState.chunkData,
126
- - segmentCheckpointState.startRowInData, segmentCheckpointState.offsetInSegment,
127
- - segmentCheckpointState.numRows);
128
- + newData->write(&segmentCheckpointState.chunkData, segmentCheckpointState.startRowInData,
129
- + segmentCheckpointState.offsetInSegment, segmentCheckpointState.numRows);
130
- }
131
- // Finalize is necessary prior to splitting for strings and lists so that pruned values don't
132
- // have an impact on the number/size of segments It should not be necessary after splitting
133
- // since the function is used to prune unused values (or duplicated dictionary entries in the
134
- // case of strings) and those will never be introduced when splitting.
135
- - checkpointState.persistentData.finalize();
136
- - if (canSplitSegment && checkpointState.persistentData.shouldSplit()) {
137
- - auto newSegments = checkpointState.persistentData.split();
138
- - for (auto& segment : newSegments) {
139
- - segment->flush(pageAllocator);
140
- - }
141
- - return newSegments;
142
- + newData->finalize();
143
- + std::vector<std::unique_ptr<ColumnChunkData>> newSegments;
144
- + if (canSplitSegment && newData->shouldSplit()) {
145
- + newSegments = newData->split();
146
- + } else {
147
- + newSegments.push_back(std::move(newData));
148
- }
149
- - checkpointState.persistentData.flush(pageAllocator);
150
- - return {};
151
- + for (auto& segment : newSegments) {
152
- + segment->flush(pageAllocator);
153
- + }
154
- + // Only free the old pages once the replacement is on disk.
155
- + state.reclaimAllocatedPages(pageAllocator);
156
- + return newSegments;
157
- }
158
-
159
- bool Column::canCheckpointInPlace(const SegmentState& state,
160
- diff --git a/src/storage/table/column_chunk.cpp b/src/storage/table/column_chunk.cpp
161
- index 9adf90426d..4c0f277344 100644
162
- --- a/src/storage/table/column_chunk.cpp
163
- +++ b/src/storage/table/column_chunk.cpp
164
- @@ -311,8 +311,8 @@ void ColumnChunk::checkpoint(Column& column,
165
- }
166
- }
167
- auto segmentEnd = segmentStart + segment->getNumValues();
168
- - // If the segment was split during checkpointing we need to insert the new segments into the
169
- - // ColumnChunk
170
- + // If the segment was rewritten out of place (and possibly split) during checkpointing we
171
- + // need to replace it with the new segments in the ColumnChunk
172
- auto newSegments = column.checkpointSegment(
173
- ColumnCheckpointState(*segment, std::move(segmentCheckpointStates)), pageAllocator);
174
- if (!newSegments.empty()) {
175
- diff --git a/src/storage/table/list_column.cpp b/src/storage/table/list_column.cpp
176
- index 0ac0465896..e4a1ece4e2 100644
177
- --- a/src/storage/table/list_column.cpp
178
- +++ b/src/storage/table/list_column.cpp
179
- @@ -403,10 +403,14 @@ std::vector<std::unique_ptr<ColumnChunkData>> ListColumn::checkpointSegment(
180
- }
181
-
182
- // We do not allow nested splitting of offset/size segments
183
- - offsetColumn->checkpointSegment(
184
- + auto newOffsetChunk = offsetColumn->checkpointSegment(
185
- ColumnCheckpointState(*persistentListChunk.getOffsetColumnChunk(),
186
- std::move(offsetChunkCheckpointStates)),
187
- pageAllocator, false);
188
- + if (!newOffsetChunk.empty()) {
189
- + DASSERT(newOffsetChunk.size() == 1);
190
- + persistentListChunk.setOffsetColumnChunk(std::move(newOffsetChunk[0]));
191
- + }
192
-
193
- // Checkpoint size data.
194
- std::vector<SegmentCheckpointState> sizeChunkCheckpointStates;
195
- @@ -416,9 +420,14 @@ std::vector<std::unique_ptr<ColumnChunkData>> ListColumn::checkpointSegment(
196
- segmentCheckpointState.startRowInData, segmentCheckpointState.offsetInSegment,
197
- segmentCheckpointState.numRows});
198
- }
199
- - sizeColumn->checkpointSegment(ColumnCheckpointState(*persistentListChunk.getSizeColumnChunk(),
200
- - std::move(sizeChunkCheckpointStates)),
201
- + auto newSizeChunk = sizeColumn->checkpointSegment(
202
- + ColumnCheckpointState(*persistentListChunk.getSizeColumnChunk(),
203
- + std::move(sizeChunkCheckpointStates)),
204
- pageAllocator, false);
205
- + if (!newSizeChunk.empty()) {
206
- + DASSERT(newSizeChunk.size() == 1);
207
- + persistentListChunk.setSizeColumnChunk(std::move(newSizeChunk[0]));
208
- + }
209
- // Checkpoint null data.
210
- Column::checkpointNullData(checkpointState, pageAllocator);
211
-
212
- diff --git a/src/storage/table/string_column.cpp b/src/storage/table/string_column.cpp
213
- index b7f94dc3ea..38b508765a 100644
214
- --- a/src/storage/table/string_column.cpp
215
- +++ b/src/storage/table/string_column.cpp
216
- @@ -116,6 +116,9 @@ std::vector<std::unique_ptr<ColumnChunkData>> StringColumn::checkpointSegment(
217
- auto result =
218
- Column::checkpointSegment(std::move(checkpointState), pageAllocator, canSplitSegment);
219
- persistentData.syncNumValues();
220
- + for (auto& segment : result) {
221
- + segment->syncNumValues();
222
- + }
223
- return result;
224
- }
225
-
226
- diff --git a/src/storage/table/struct_column.cpp b/src/storage/table/struct_column.cpp
227
- index b16e3d83ff..673cb6c468 100644
228
- --- a/src/storage/table/struct_column.cpp
229
- +++ b/src/storage/table/struct_column.cpp
230
- @@ -114,6 +114,9 @@ std::vector<std::unique_ptr<ColumnChunkData>> StructColumn::checkpointSegment(
231
- auto result =
232
- Column::checkpointSegment(std::move(checkpointState), pageAllocator, canSplitSegment);
233
- persistentStructChunk.syncNumValues();
234
- + for (auto& segment : result) {
235
- + segment->syncNumValues();
236
- + }
237
- return result;
238
- }
239
-
240
- diff --git a/test/test_files/transaction/set_node/set_empty.test b/test/test_files/transaction/set_node/set_empty.test
241
- index 72dbeefd09..b6e1495e07 100644
242
- --- a/test/test_files/transaction/set_node/set_empty.test
243
- +++ b/test/test_files/transaction/set_node/set_empty.test
244
- @@ -33,3 +33,44 @@
245
- ---- 2
246
- 0|1010101010.300000
247
- 1341|1010101010.200000
248
- +
249
- +-CASE ListInPlaceWithOutOfPlaceOffsetsAndNulls
250
- +-STATEMENT CALL auto_checkpoint=false;
251
- +---- ok
252
- +-STATEMENT CREATE NODE TABLE t2(id INT64 PRIMARY KEY, l INT64[]);
253
- +---- ok
254
- +-STATEMENT UNWIND range(0, 4094) AS i CREATE (:t2 {id: i, l: [0, 1]});
255
- +---- ok
256
- +-STATEMENT CHECKPOINT;
257
- +---- ok
258
- +-STATEMENT MATCH (a:t2) WHERE a.id = 5 SET a.l = [1,0,1,0,1,0,1,0,1,0];
259
- +---- ok
260
- +-STATEMENT MATCH (a:t2) WHERE a.id = 7 SET a.l = NULL;
261
- +---- ok
262
- +-STATEMENT CHECKPOINT;
263
- +---- ok
264
- +-STATEMENT MATCH (a:t2) RETURN COUNT(a.l);
265
- +---- 1
266
- +4094
267
- +-STATEMENT MATCH (a:t2) UNWIND a.l AS x RETURN COUNT(x), SUM(x);
268
- +---- 1
269
- +8196|4098
270
- +-STATEMENT MATCH (a:t2) WHERE a.id = 5 RETURN SIZE(a.l), a.l[1], a.l[2];
271
- +---- 1
272
- +10|1|0
273
- +-STATEMENT MATCH (a:t2) WHERE a.id = 6 RETURN SIZE(a.l), a.l[1], a.l[2];
274
- +---- 1
275
- +2|0|1
276
- +-RELOADDB
277
- +-STATEMENT MATCH (a:t2) RETURN COUNT(a.l);
278
- +---- 1
279
- +4094
280
- +-STATEMENT MATCH (a:t2) UNWIND a.l AS x RETURN COUNT(x), SUM(x);
281
- +---- 1
282
- +8196|4098
283
- +-STATEMENT MATCH (a:t2) WHERE a.id = 5 RETURN SIZE(a.l), a.l[1], a.l[2];
284
- +---- 1
285
- +10|1|0
286
- +-STATEMENT MATCH (a:t2) WHERE a.id = 4094 RETURN SIZE(a.l), a.l[1], a.l[2];
287
- +---- 1
288
- +2|0|1
289
- diff --git a/test/transaction/checkpoint_test.cpp b/test/transaction/checkpoint_test.cpp
290
- index 2777560616..2049d21d19 100644
291
- --- a/test/transaction/checkpoint_test.cpp
292
- +++ b/test/transaction/checkpoint_test.cpp
293
- @@ -13,6 +13,8 @@
294
- #include "catalog/catalog_entry/table_catalog_entry.h"
295
- #include "common/exception/runtime.h"
296
- #include "storage/checkpointer.h"
297
- +#include "storage/page_allocator.h"
298
- +#include "storage/page_manager.h"
299
- #include "storage/storage_manager.h"
300
- #include "storage/table/node_table.h"
301
- #include "storage/table/string_chunk_data.h"
302
- @@ -235,6 +237,110 @@ TEST_F(FlakyCheckpointerTest, RecoverFromCheckpointClearingFilesFailure) {
303
- runTest(flakyCheckpointer);
304
- }
305
-
306
- +// Page allocator that fails the first page allocation. In-place checkpoints do not allocate
307
- +// pages, so during the storage phase of a checkpoint that only updates a node group with
308
- +// persistent data, the first allocation is the flush of an out-of-place column chunk rewrite and
309
- +// the failure lands in the middle of that rewrite.
310
- +class FailFirstAllocationPageAllocator final : public PageAllocator {
311
- +public:
312
- + FailFirstAllocationPageAllocator(PageAllocator& inner, bool& failed)
313
- + : PageAllocator(inner.getDataFH()), inner{inner}, failed{failed} {}
314
- +
315
- + PageRange allocatePageRange(page_idx_t numPages) override {
316
- + if (!failed) {
317
- + failed = true;
318
- + throw RuntimeException("checkpoint failed.");
319
- + }
320
- + return inner.allocatePageRange(numPages);
321
- + }
322
- + void freePageRange(PageRange block) override { inner.freePageRange(block); }
323
- +
324
- +private:
325
- + PageAllocator& inner;
326
- + bool& failed;
327
- +};
328
- +
329
- +class FlakyCheckpointerFailsDuringOutOfPlaceRewrite final : public Checkpointer {
330
- +public:
331
- + FlakyCheckpointerFailsDuringOutOfPlaceRewrite(main::ClientContext& context, bool& failed)
332
- + : Checkpointer(context), failed{failed} {}
333
- +
334
- + bool checkpointStorage() override {
335
- + for (const auto& target : checkpointTargets) {
336
- + FailFirstAllocationPageAllocator pageAllocator(
337
- + *target.storageManager->getDataFH()->getPageManager(), failed);
338
- + const Transaction snapshotTxn(TransactionType::CHECKPOINT,
339
- + Transaction::DUMMY_TRANSACTION_ID, snapshotTS);
340
- + target.storageManager->checkpoint(&clientContext, *target.catalog, snapshotTxn,
341
- + pageAllocator, tableEpochWatermarksByManager.at(target.storageManager));
342
- + }
343
- + throw RuntimeException("expected the injected page allocation failure to fire.");
344
- + }
345
- +
346
- +private:
347
- + bool& failed;
348
- +};
349
- +
350
- +// A checkpoint that fails while a column chunk segment is being rewritten out of place must
351
- +// leave that segment as it was, so that the data stays readable and later checkpoints succeed.
352
- +TEST_F(FlakyCheckpointerTest, RecoverFromFailureDuringOutOfPlaceCheckpoint) {
353
- + if (inMemMode || systemConfig->checkpointThreshold == 0) {
354
- + GTEST_SKIP();
355
- + }
356
- + conn->query("CALL force_checkpoint_on_close=false;");
357
- + conn->query("CALL auto_checkpoint=false;");
358
- + ASSERT_TRUE(
359
- + conn->query("CREATE NODE TABLE test(id INT64 PRIMARY KEY, name STRING);")->isSuccess());
360
- + constexpr int64_t numInitialRows = 3000;
361
- + constexpr int64_t numRows = 6000;
362
- + constexpr std::string_view namePrefix = "a longer name so the pages fill up ";
363
- + auto nameOf = [&](int64_t i) { return std::format("{}{}", namePrefix, i); };
364
- + auto insertRows = [&](int64_t start, int64_t end) {
365
- + auto res = conn->query(std::format("UNWIND range({}, {}) AS i CREATE (a:test {{id: i, "
366
- + "name: concat('{}', CAST(i AS STRING))}});",
367
- + start, end - 1, namePrefix));
368
- + ASSERT_TRUE(res->isSuccess()) << res->getErrorMessage();
369
- + };
370
- + auto checkRows = [&]() {
371
- + auto res = conn->query("MATCH (a:test) RETURN COUNT(a), CAST(SUM(a.id) AS INT64), "
372
- + "CAST(SUM(SIZE(a.name)) AS INT64), MIN(a.name), MAX(a.name);");
373
- + ASSERT_TRUE(res->isSuccess()) << res->getErrorMessage();
374
- + auto row = res->getNext();
375
- + ASSERT_EQ(row->getValue(0)->getValue<int64_t>(), numRows);
376
- + ASSERT_EQ(row->getValue(1)->getValue<int64_t>(), numRows * (numRows - 1) / 2);
377
- + int64_t totalNameSize = 0;
378
- + for (auto i = 0; i < numRows; i++) {
379
- + totalNameSize += nameOf(i).size();
380
- + }
381
- + ASSERT_EQ(row->getValue(2)->getValue<int64_t>(), totalNameSize);
382
- + ASSERT_EQ(row->getValue(3)->getValue<std::string>(), nameOf(0));
383
- + ASSERT_EQ(row->getValue(4)->getValue<std::string>(), nameOf(999));
384
- + };
385
- + insertRows(0, numInitialRows);
386
- + ASSERT_TRUE(conn->query("CHECKPOINT;")->isSuccess());
387
- + // Appending to the persistent node group outgrows the pages of its column chunks, so the
388
- + // next checkpoint has to rewrite them out of place.
389
- + insertRows(numInitialRows, numRows);
390
- +
391
- + auto context = getClientContext(*conn);
392
- + bool failed = false;
393
- + FlakyCheckpointer flakyCheckpointer([&failed](main::ClientContext& ctx) {
394
- + return std::make_unique<FlakyCheckpointerFailsDuringOutOfPlaceRewrite>(ctx, failed);
395
- + });
396
- + flakyCheckpointer.setCheckpointer(*context);
397
- + auto res = conn->query("CHECKPOINT;");
398
- + ASSERT_FALSE(res->isSuccess());
399
- + ASSERT_TRUE(failed);
400
- + checkRows();
401
- +
402
- + FlakyCheckpointer defaultCheckpointer(
403
- + [](main::ClientContext& ctx) { return std::make_unique<Checkpointer>(ctx); });
404
- + defaultCheckpointer.setCheckpointer(*context);
405
- + res = conn->query("CHECKPOINT;");
406
- + ASSERT_TRUE(res->isSuccess()) << res->getErrorMessage();
407
- + checkRows();
408
- +}
409
- +
410
- // Simulates a situation where a database attempts to replay a shadow file from an older database
411
- // with the same path
412
- TEST_F(FlakyCheckpointerTest, ShadowFileDatabaseIDMismatchExistingDB) {
1
+ From 926a0ba10d221eb437add0885ff492e7576bb7c9 Mon Sep 17 00:00:00 2001
2
+ From: kory-io <kory-io@users.noreply.github.com>
3
+ Date: Thu, 24 Sep 2026 18:56:55 -0600
4
+ Subject: [PATCH] fix(storage): keep segment on disk when an out-of-place
5
+ checkpoint fails
6
+
7
+ Column::checkpointColumnChunkOutOfPlace rewrote a segment by switching
8
+ the persistent ColumnChunkData itself to IN_MEMORY (setToInMemory) and
9
+ then resizing, scanning, writing, finalizing and flushing it. Any of
10
+ those steps can throw, for example "Unable to allocate memory! The
11
+ buffer pool is full" from the resize or the scan. The checkpoint is
12
+ then rolled back, but Checkpointer::rollback only discards pending page
13
+ frees and rolls back node-table indexes; it does not touch node group
14
+ state. The node group keeps the half-rewritten segment, which stays
15
+ IN_MEMORY, and with runtime checks enabled (Debug, or RUNTIME_CHECKS=1
16
+ as in CI) every later checkpoint of that table fails the
17
+ `segment->getResidencyState() == ResidencyState::ON_DISK` assertion in
18
+ ColumnChunk::checkpoint, so the database can no longer checkpoint.
19
+
20
+ Rewrite the segment into a new in-memory chunk of the same kind
21
+ instead, and return it once it has been flushed, the same way split
22
+ segments are already returned. The persistent segment object is not
23
+ touched until the rewrite has succeeded, and its pages are only freed
24
+ after that, so a failure leaves it on disk and unchanged and the next
25
+ checkpoint starts from it again. Segments rewritten earlier in the
26
+ same failed checkpoint stay replaced; their old pages are only
27
+ reclaimed after the database is reopened.
28
+
29
+ ColumnChunk::checkpoint already installs returned segments. The nested
30
+ null, list offset and list size chunks, which are checkpointed without
31
+ splitting, now install the returned chunk into their parent.
32
+ StringColumn and StructColumn also call syncNumValues on the returned
33
+ segments, as they already did on the persistent segment.
34
+
35
+ Add FlakyCheckpointerTest.RecoverFromFailureDuringOutOfPlaceCheckpoint.
36
+ It fails a checkpoint at the flush of an out-of-place rewrite, then
37
+ checks that the data is intact and that the next checkpoint succeeds.
38
+ Add an e2e case that checkpoints a list column in place while its
39
+ offsets and nulls are rewritten out of place.
40
+ ---
41
+ src/include/storage/table/column.h | 4 +-
42
+ src/storage/table/column.cpp | 58 +++++++---
43
+ src/storage/table/column_chunk.cpp | 4 +-
44
+ src/storage/table/list_column.cpp | 15 ++-
45
+ src/storage/table/string_column.cpp | 3 +
46
+ src/storage/table/struct_column.cpp | 3 +
47
+ .../transaction/set_node/set_empty.test | 41 +++++++
48
+ test/transaction/checkpoint_test.cpp | 106 ++++++++++++++++++
49
+ 8 files changed, 209 insertions(+), 25 deletions(-)
50
+
51
+ diff --git a/src/include/storage/table/column.h b/src/include/storage/table/column.h
52
+ index 16584cb027..d56fefbf5f 100644
53
+ --- a/src/include/storage/table/column.h
54
+ +++ b/src/include/storage/table/column.h
55
+ @@ -95,8 +95,8 @@ class Column {
56
+ return common::dynamic_cast_checked<TARGET&>(*this);
57
+ }
58
+
59
+ - // Return value is the new segments if segment splitting occurs during an out of place
60
+ - // checkpoint
61
+ + // Return value is the new segment(s) replacing the persistent segment if it was checkpointed
62
+ + // out of place (more than one if it was split), or empty if it was checkpointed in place
63
+ virtual std::vector<std::unique_ptr<ColumnChunkData>> checkpointSegment(
64
+ ColumnCheckpointState&& checkpointState, PageAllocator& pageAllocator,
65
+ bool canSplitSegment = true) const;
66
+ diff --git a/src/storage/table/column.cpp b/src/storage/table/column.cpp
67
+ index 72b9a2f206..9d23776188 100644
68
+ --- a/src/storage/table/column.cpp
69
+ +++ b/src/storage/table/column.cpp
70
+ @@ -3,6 +3,7 @@
71
+ #include <algorithm>
72
+ #include <cstdint>
73
+ #include <memory>
74
+ +#include <tuple>
75
+
76
+ #include "common/assert.h"
77
+ #include "common/data_chunk/sel_vector.h"
78
+ @@ -439,42 +440,63 @@ void Column::checkpointNullData(const ColumnCheckpointState& checkpointState,
79
+ segmentCheckpointState.numRows);
80
+ }
81
+ DASSERT(checkpointState.persistentData.hasNullData());
82
+ - nullColumn->checkpointSegment(
83
+ + auto newNullData = nullColumn->checkpointSegment(
84
+ ColumnCheckpointState(*checkpointState.persistentData.getNullData(),
85
+ std::move(nullSegmentCheckpointStates)),
86
+ pageAllocator, false);
87
+ + if (!newNullData.empty()) {
88
+ + DASSERT(newNullData.size() == 1);
89
+ + auto* nullChunk = &newNullData[0]->cast<NullChunkData>();
90
+ + std::ignore = newNullData[0].release();
91
+ + checkpointState.persistentData.setNullData(std::unique_ptr<NullChunkData>(nullChunk));
92
+ + }
93
+ +}
94
+ +
95
+ +static std::unique_ptr<ColumnChunkData> createInMemoryChunkLike(const ColumnChunkData& chunk,
96
+ + uint64_t capacity) {
97
+ + if (dynamic_cast<const NullChunkData*>(&chunk) != nullptr) {
98
+ + return ColumnChunkFactory::createNullChunkData(chunk.getMemoryManager(),
99
+ + chunk.isCompressionEnabled(), capacity, ResidencyState::IN_MEMORY);
100
+ + }
101
+ + return ColumnChunkFactory::createColumnChunkData(chunk.getMemoryManager(),
102
+ + chunk.getDataType().copy(), chunk.isCompressionEnabled(), capacity,
103
+ + ResidencyState::IN_MEMORY, chunk.hasNullData());
104
+ }
105
+
106
+ std::vector<std::unique_ptr<ColumnChunkData>> Column::checkpointColumnChunkOutOfPlace(
107
+ const SegmentState& state, const ColumnCheckpointState& checkpointState,
108
+ PageAllocator& pageAllocator, bool canSplitSegment) const {
109
+ const auto numRows = std::max(checkpointState.endRowIdxToWrite, state.metadata.numValues);
110
+ - checkpointState.persistentData.setToInMemory();
111
+ - checkpointState.persistentData.resize(numRows);
112
+ - DASSERT(checkpointState.persistentData.getNumValues() == 0);
113
+ - scanSegment(state, &checkpointState.persistentData, 0, state.metadata.numValues);
114
+ - state.reclaimAllocatedPages(pageAllocator);
115
+ + // Rewrite the segment into a new chunk instead of converting persistentData to an in-memory
116
+ + // chunk in place. If the rewrite fails part way (e.g. the buffer pool is full), the checkpoint
117
+ + // is rolled back but the segment is kept, so it must still describe its on-disk pages for the
118
+ + // data to stay readable and for the next checkpoint to be able to rewrite it.
119
+ + auto newData = createInMemoryChunkLike(checkpointState.persistentData, numRows);
120
+ + DASSERT(newData->getNumValues() == 0);
121
+ + scanSegment(state, newData.get(), 0, state.metadata.numValues);
122
+ // TODO(bmwinger): for simple compression types, we can predict whether or not we will need to
123
+ // split the segment and avoid having to re-write it multiple times
124
+ for (auto& segmentCheckpointState : checkpointState.segmentCheckpointStates) {
125
+ - checkpointState.persistentData.write(&segmentCheckpointState.chunkData,
126
+ - segmentCheckpointState.startRowInData, segmentCheckpointState.offsetInSegment,
127
+ - segmentCheckpointState.numRows);
128
+ + newData->write(&segmentCheckpointState.chunkData, segmentCheckpointState.startRowInData,
129
+ + segmentCheckpointState.offsetInSegment, segmentCheckpointState.numRows);
130
+ }
131
+ // Finalize is necessary prior to splitting for strings and lists so that pruned values don't
132
+ // have an impact on the number/size of segments It should not be necessary after splitting
133
+ // since the function is used to prune unused values (or duplicated dictionary entries in the
134
+ // case of strings) and those will never be introduced when splitting.
135
+ - checkpointState.persistentData.finalize();
136
+ - if (canSplitSegment && checkpointState.persistentData.shouldSplit()) {
137
+ - auto newSegments = checkpointState.persistentData.split();
138
+ - for (auto& segment : newSegments) {
139
+ - segment->flush(pageAllocator);
140
+ - }
141
+ - return newSegments;
142
+ + newData->finalize();
143
+ + std::vector<std::unique_ptr<ColumnChunkData>> newSegments;
144
+ + if (canSplitSegment && newData->shouldSplit()) {
145
+ + newSegments = newData->split();
146
+ + } else {
147
+ + newSegments.push_back(std::move(newData));
148
+ }
149
+ - checkpointState.persistentData.flush(pageAllocator);
150
+ - return {};
151
+ + for (auto& segment : newSegments) {
152
+ + segment->flush(pageAllocator);
153
+ + }
154
+ + // Only free the old pages once the replacement is on disk.
155
+ + state.reclaimAllocatedPages(pageAllocator);
156
+ + return newSegments;
157
+ }
158
+
159
+ bool Column::canCheckpointInPlace(const SegmentState& state,
160
+ diff --git a/src/storage/table/column_chunk.cpp b/src/storage/table/column_chunk.cpp
161
+ index 9adf90426d..4c0f277344 100644
162
+ --- a/src/storage/table/column_chunk.cpp
163
+ +++ b/src/storage/table/column_chunk.cpp
164
+ @@ -311,8 +311,8 @@ void ColumnChunk::checkpoint(Column& column,
165
+ }
166
+ }
167
+ auto segmentEnd = segmentStart + segment->getNumValues();
168
+ - // If the segment was split during checkpointing we need to insert the new segments into the
169
+ - // ColumnChunk
170
+ + // If the segment was rewritten out of place (and possibly split) during checkpointing we
171
+ + // need to replace it with the new segments in the ColumnChunk
172
+ auto newSegments = column.checkpointSegment(
173
+ ColumnCheckpointState(*segment, std::move(segmentCheckpointStates)), pageAllocator);
174
+ if (!newSegments.empty()) {
175
+ diff --git a/src/storage/table/list_column.cpp b/src/storage/table/list_column.cpp
176
+ index 0ac0465896..e4a1ece4e2 100644
177
+ --- a/src/storage/table/list_column.cpp
178
+ +++ b/src/storage/table/list_column.cpp
179
+ @@ -403,10 +403,14 @@ std::vector<std::unique_ptr<ColumnChunkData>> ListColumn::checkpointSegment(
180
+ }
181
+
182
+ // We do not allow nested splitting of offset/size segments
183
+ - offsetColumn->checkpointSegment(
184
+ + auto newOffsetChunk = offsetColumn->checkpointSegment(
185
+ ColumnCheckpointState(*persistentListChunk.getOffsetColumnChunk(),
186
+ std::move(offsetChunkCheckpointStates)),
187
+ pageAllocator, false);
188
+ + if (!newOffsetChunk.empty()) {
189
+ + DASSERT(newOffsetChunk.size() == 1);
190
+ + persistentListChunk.setOffsetColumnChunk(std::move(newOffsetChunk[0]));
191
+ + }
192
+
193
+ // Checkpoint size data.
194
+ std::vector<SegmentCheckpointState> sizeChunkCheckpointStates;
195
+ @@ -416,9 +420,14 @@ std::vector<std::unique_ptr<ColumnChunkData>> ListColumn::checkpointSegment(
196
+ segmentCheckpointState.startRowInData, segmentCheckpointState.offsetInSegment,
197
+ segmentCheckpointState.numRows});
198
+ }
199
+ - sizeColumn->checkpointSegment(ColumnCheckpointState(*persistentListChunk.getSizeColumnChunk(),
200
+ - std::move(sizeChunkCheckpointStates)),
201
+ + auto newSizeChunk = sizeColumn->checkpointSegment(
202
+ + ColumnCheckpointState(*persistentListChunk.getSizeColumnChunk(),
203
+ + std::move(sizeChunkCheckpointStates)),
204
+ pageAllocator, false);
205
+ + if (!newSizeChunk.empty()) {
206
+ + DASSERT(newSizeChunk.size() == 1);
207
+ + persistentListChunk.setSizeColumnChunk(std::move(newSizeChunk[0]));
208
+ + }
209
+ // Checkpoint null data.
210
+ Column::checkpointNullData(checkpointState, pageAllocator);
211
+
212
+ diff --git a/src/storage/table/string_column.cpp b/src/storage/table/string_column.cpp
213
+ index b7f94dc3ea..38b508765a 100644
214
+ --- a/src/storage/table/string_column.cpp
215
+ +++ b/src/storage/table/string_column.cpp
216
+ @@ -116,6 +116,9 @@ std::vector<std::unique_ptr<ColumnChunkData>> StringColumn::checkpointSegment(
217
+ auto result =
218
+ Column::checkpointSegment(std::move(checkpointState), pageAllocator, canSplitSegment);
219
+ persistentData.syncNumValues();
220
+ + for (auto& segment : result) {
221
+ + segment->syncNumValues();
222
+ + }
223
+ return result;
224
+ }
225
+
226
+ diff --git a/src/storage/table/struct_column.cpp b/src/storage/table/struct_column.cpp
227
+ index b16e3d83ff..673cb6c468 100644
228
+ --- a/src/storage/table/struct_column.cpp
229
+ +++ b/src/storage/table/struct_column.cpp
230
+ @@ -114,6 +114,9 @@ std::vector<std::unique_ptr<ColumnChunkData>> StructColumn::checkpointSegment(
231
+ auto result =
232
+ Column::checkpointSegment(std::move(checkpointState), pageAllocator, canSplitSegment);
233
+ persistentStructChunk.syncNumValues();
234
+ + for (auto& segment : result) {
235
+ + segment->syncNumValues();
236
+ + }
237
+ return result;
238
+ }
239
+
240
+ diff --git a/test/test_files/transaction/set_node/set_empty.test b/test/test_files/transaction/set_node/set_empty.test
241
+ index 72dbeefd09..b6e1495e07 100644
242
+ --- a/test/test_files/transaction/set_node/set_empty.test
243
+ +++ b/test/test_files/transaction/set_node/set_empty.test
244
+ @@ -33,3 +33,44 @@
245
+ ---- 2
246
+ 0|1010101010.300000
247
+ 1341|1010101010.200000
248
+ +
249
+ +-CASE ListInPlaceWithOutOfPlaceOffsetsAndNulls
250
+ +-STATEMENT CALL auto_checkpoint=false;
251
+ +---- ok
252
+ +-STATEMENT CREATE NODE TABLE t2(id INT64 PRIMARY KEY, l INT64[]);
253
+ +---- ok
254
+ +-STATEMENT UNWIND range(0, 4094) AS i CREATE (:t2 {id: i, l: [0, 1]});
255
+ +---- ok
256
+ +-STATEMENT CHECKPOINT;
257
+ +---- ok
258
+ +-STATEMENT MATCH (a:t2) WHERE a.id = 5 SET a.l = [1,0,1,0,1,0,1,0,1,0];
259
+ +---- ok
260
+ +-STATEMENT MATCH (a:t2) WHERE a.id = 7 SET a.l = NULL;
261
+ +---- ok
262
+ +-STATEMENT CHECKPOINT;
263
+ +---- ok
264
+ +-STATEMENT MATCH (a:t2) RETURN COUNT(a.l);
265
+ +---- 1
266
+ +4094
267
+ +-STATEMENT MATCH (a:t2) UNWIND a.l AS x RETURN COUNT(x), SUM(x);
268
+ +---- 1
269
+ +8196|4098
270
+ +-STATEMENT MATCH (a:t2) WHERE a.id = 5 RETURN SIZE(a.l), a.l[1], a.l[2];
271
+ +---- 1
272
+ +10|1|0
273
+ +-STATEMENT MATCH (a:t2) WHERE a.id = 6 RETURN SIZE(a.l), a.l[1], a.l[2];
274
+ +---- 1
275
+ +2|0|1
276
+ +-RELOADDB
277
+ +-STATEMENT MATCH (a:t2) RETURN COUNT(a.l);
278
+ +---- 1
279
+ +4094
280
+ +-STATEMENT MATCH (a:t2) UNWIND a.l AS x RETURN COUNT(x), SUM(x);
281
+ +---- 1
282
+ +8196|4098
283
+ +-STATEMENT MATCH (a:t2) WHERE a.id = 5 RETURN SIZE(a.l), a.l[1], a.l[2];
284
+ +---- 1
285
+ +10|1|0
286
+ +-STATEMENT MATCH (a:t2) WHERE a.id = 4094 RETURN SIZE(a.l), a.l[1], a.l[2];
287
+ +---- 1
288
+ +2|0|1
289
+ diff --git a/test/transaction/checkpoint_test.cpp b/test/transaction/checkpoint_test.cpp
290
+ index 2777560616..2049d21d19 100644
291
+ --- a/test/transaction/checkpoint_test.cpp
292
+ +++ b/test/transaction/checkpoint_test.cpp
293
+ @@ -13,6 +13,8 @@
294
+ #include "catalog/catalog_entry/table_catalog_entry.h"
295
+ #include "common/exception/runtime.h"
296
+ #include "storage/checkpointer.h"
297
+ +#include "storage/page_allocator.h"
298
+ +#include "storage/page_manager.h"
299
+ #include "storage/storage_manager.h"
300
+ #include "storage/table/node_table.h"
301
+ #include "storage/table/string_chunk_data.h"
302
+ @@ -235,6 +237,110 @@ TEST_F(FlakyCheckpointerTest, RecoverFromCheckpointClearingFilesFailure) {
303
+ runTest(flakyCheckpointer);
304
+ }
305
+
306
+ +// Page allocator that fails the first page allocation. In-place checkpoints do not allocate
307
+ +// pages, so during the storage phase of a checkpoint that only updates a node group with
308
+ +// persistent data, the first allocation is the flush of an out-of-place column chunk rewrite and
309
+ +// the failure lands in the middle of that rewrite.
310
+ +class FailFirstAllocationPageAllocator final : public PageAllocator {
311
+ +public:
312
+ + FailFirstAllocationPageAllocator(PageAllocator& inner, bool& failed)
313
+ + : PageAllocator(inner.getDataFH()), inner{inner}, failed{failed} {}
314
+ +
315
+ + PageRange allocatePageRange(page_idx_t numPages) override {
316
+ + if (!failed) {
317
+ + failed = true;
318
+ + throw RuntimeException("checkpoint failed.");
319
+ + }
320
+ + return inner.allocatePageRange(numPages);
321
+ + }
322
+ + void freePageRange(PageRange block) override { inner.freePageRange(block); }
323
+ +
324
+ +private:
325
+ + PageAllocator& inner;
326
+ + bool& failed;
327
+ +};
328
+ +
329
+ +class FlakyCheckpointerFailsDuringOutOfPlaceRewrite final : public Checkpointer {
330
+ +public:
331
+ + FlakyCheckpointerFailsDuringOutOfPlaceRewrite(main::ClientContext& context, bool& failed)
332
+ + : Checkpointer(context), failed{failed} {}
333
+ +
334
+ + bool checkpointStorage() override {
335
+ + for (const auto& target : checkpointTargets) {
336
+ + FailFirstAllocationPageAllocator pageAllocator(
337
+ + *target.storageManager->getDataFH()->getPageManager(), failed);
338
+ + const Transaction snapshotTxn(TransactionType::CHECKPOINT,
339
+ + Transaction::DUMMY_TRANSACTION_ID, snapshotTS);
340
+ + target.storageManager->checkpoint(&clientContext, *target.catalog, snapshotTxn,
341
+ + pageAllocator, tableEpochWatermarksByManager.at(target.storageManager));
342
+ + }
343
+ + throw RuntimeException("expected the injected page allocation failure to fire.");
344
+ + }
345
+ +
346
+ +private:
347
+ + bool& failed;
348
+ +};
349
+ +
350
+ +// A checkpoint that fails while a column chunk segment is being rewritten out of place must
351
+ +// leave that segment as it was, so that the data stays readable and later checkpoints succeed.
352
+ +TEST_F(FlakyCheckpointerTest, RecoverFromFailureDuringOutOfPlaceCheckpoint) {
353
+ + if (inMemMode || systemConfig->checkpointThreshold == 0) {
354
+ + GTEST_SKIP();
355
+ + }
356
+ + conn->query("CALL force_checkpoint_on_close=false;");
357
+ + conn->query("CALL auto_checkpoint=false;");
358
+ + ASSERT_TRUE(
359
+ + conn->query("CREATE NODE TABLE test(id INT64 PRIMARY KEY, name STRING);")->isSuccess());
360
+ + constexpr int64_t numInitialRows = 3000;
361
+ + constexpr int64_t numRows = 6000;
362
+ + constexpr std::string_view namePrefix = "a longer name so the pages fill up ";
363
+ + auto nameOf = [&](int64_t i) { return std::format("{}{}", namePrefix, i); };
364
+ + auto insertRows = [&](int64_t start, int64_t end) {
365
+ + auto res = conn->query(std::format("UNWIND range({}, {}) AS i CREATE (a:test {{id: i, "
366
+ + "name: concat('{}', CAST(i AS STRING))}});",
367
+ + start, end - 1, namePrefix));
368
+ + ASSERT_TRUE(res->isSuccess()) << res->getErrorMessage();
369
+ + };
370
+ + auto checkRows = [&]() {
371
+ + auto res = conn->query("MATCH (a:test) RETURN COUNT(a), CAST(SUM(a.id) AS INT64), "
372
+ + "CAST(SUM(SIZE(a.name)) AS INT64), MIN(a.name), MAX(a.name);");
373
+ + ASSERT_TRUE(res->isSuccess()) << res->getErrorMessage();
374
+ + auto row = res->getNext();
375
+ + ASSERT_EQ(row->getValue(0)->getValue<int64_t>(), numRows);
376
+ + ASSERT_EQ(row->getValue(1)->getValue<int64_t>(), numRows * (numRows - 1) / 2);
377
+ + int64_t totalNameSize = 0;
378
+ + for (auto i = 0; i < numRows; i++) {
379
+ + totalNameSize += nameOf(i).size();
380
+ + }
381
+ + ASSERT_EQ(row->getValue(2)->getValue<int64_t>(), totalNameSize);
382
+ + ASSERT_EQ(row->getValue(3)->getValue<std::string>(), nameOf(0));
383
+ + ASSERT_EQ(row->getValue(4)->getValue<std::string>(), nameOf(999));
384
+ + };
385
+ + insertRows(0, numInitialRows);
386
+ + ASSERT_TRUE(conn->query("CHECKPOINT;")->isSuccess());
387
+ + // Appending to the persistent node group outgrows the pages of its column chunks, so the
388
+ + // next checkpoint has to rewrite them out of place.
389
+ + insertRows(numInitialRows, numRows);
390
+ +
391
+ + auto context = getClientContext(*conn);
392
+ + bool failed = false;
393
+ + FlakyCheckpointer flakyCheckpointer([&failed](main::ClientContext& ctx) {
394
+ + return std::make_unique<FlakyCheckpointerFailsDuringOutOfPlaceRewrite>(ctx, failed);
395
+ + });
396
+ + flakyCheckpointer.setCheckpointer(*context);
397
+ + auto res = conn->query("CHECKPOINT;");
398
+ + ASSERT_FALSE(res->isSuccess());
399
+ + ASSERT_TRUE(failed);
400
+ + checkRows();
401
+ +
402
+ + FlakyCheckpointer defaultCheckpointer(
403
+ + [](main::ClientContext& ctx) { return std::make_unique<Checkpointer>(ctx); });
404
+ + defaultCheckpointer.setCheckpointer(*context);
405
+ + res = conn->query("CHECKPOINT;");
406
+ + ASSERT_TRUE(res->isSuccess()) << res->getErrorMessage();
407
+ + checkRows();
408
+ +}
409
+ +
410
+ // Simulates a situation where a database attempts to replay a shadow file from an older database
411
+ // with the same path
412
+ TEST_F(FlakyCheckpointerTest, ShadowFileDatabaseIDMismatchExistingDB) {