kenaz 0.3.3 → 0.3.5

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 (413) 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 +14 -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/spec/task_operations.parity.test.js +0 -1
  44. package/core/.formation/tests/hello.txt +2 -2
  45. package/core/.formation/tools/dashboard-models-chain.test.js +0 -6
  46. package/core/.formation/tools/dispatch-detail/cross-project-worktree.md +1 -9
  47. package/core/.formation/tools/dispatch-detail/worktree-subagent-mode.md +2 -2
  48. package/core/.formation/tools/dispatch.js +28 -549
  49. package/core/.formation/tools/mcp-allowlist.test.js +0 -1
  50. package/core/.formation/tools/post-complete/task2345.test.js +0 -5
  51. package/core/.formation/tools/pre-dispatch-snapshot.test.js +0 -100
  52. package/core/.formation/tools/prompt.js +29 -1623
  53. package/core/.formation/tools/prompt.test.js +26 -594
  54. package/core/.kenaz/knowledge/ladybug_2c51da1e_shadow_file_frame_group.patch +163 -163
  55. package/core/.kenaz/knowledge/ladybug_30cf85690e_shadow_page_read_during_checkpoint.patch +393 -393
  56. package/core/.kenaz/knowledge/ladybug_7a17858d3a_node_group_delete_lock.patch +102 -102
  57. package/core/.kenaz/knowledge/ladybug_926a0ba1_column_checkpoint.patch +412 -412
  58. package/core/.kenaz/knowledge/ladybug_a346c4c4b6_pk_index_header_pages.patch +144 -144
  59. package/core/.kenaz/knowledge/ladybug_ba5f38815b_csr_node_group.patch +86 -86
  60. package/core/.kenaz/muninn_episode_write.ps1 +17 -17
  61. package/core/.kenaz/specialist/field_report_watermark.json +1 -1
  62. package/core/.kenaz/specialist/memory_guard_fired.json +1 -1
  63. package/core/.kenaz/specialist/memory_guard_trigger_cache.json +1 -1
  64. package/core/.kenaz/specialist/t1777_probe.txt +1 -1
  65. package/core/.matrix/index.matrix +5 -5
  66. package/core/.specialist/tools/__tests__/freya-cli-contract.test.js +0 -1
  67. package/core/KENAZ_CORE_VERSION +1 -1
  68. package/core/dev/ANSUZ_STARTUP_INJECTION_REVIEW.md +355 -355
  69. package/core/dev/JEV_INTEGRATION_PLAN.md +189 -189
  70. package/core/dev/design/matrix_format_EXAMPLE_memory.matrix +13 -13
  71. package/core/dev/design/matrix_format_EXAMPLE_project_status.matrix +9 -9
  72. package/core/dev/design/matrix_format_EXAMPLE_tasks.matrix +14 -14
  73. package/core/dev/design/matrix_v2_EXAMPLE.matrix +67 -67
  74. package/core/dev/jev-eval/ACTIVE_CORRECTION.md +52 -52
  75. package/core/dev/jev-eval/CONTEXT_REVIEW.md +31 -31
  76. package/core/dev/jev-eval/EVALUATION_STATUS.md +39 -39
  77. package/core/dev/jev-eval/HISTORICAL_CORPUS.md +33 -33
  78. package/core/dev/jev-eval/README.md +99 -99
  79. package/core/dev/jev-eval/THALAMUS_RANKING.md +58 -58
  80. package/core/dev/jev-eval/reports/annotation-pilot-2026-09-20.json +32 -32
  81. package/core/dev/jev-eval/reports/context-review-2026-09-20.json +48 -48
  82. package/core/dev/jev-eval/reports/history-corpus-2026-09-20.json +52 -52
  83. package/core/dev/pipeline/SELF_ENHANCEMENT_STATUS.json.bak +47 -47
  84. package/core/dev/pipeline/tasks/.gitkeep +1 -1
  85. package/core/dev/pipeline/test_results/TASK_TEST_PARALLEL_002_result.txt +27 -27
  86. package/core/dev/scripts/init_git.sh +0 -0
  87. package/core/dev/scripts/init_project.sh +0 -0
  88. package/core/dev/scripts/legacy/dev_agent_scheduler.ps1.bak +536 -536
  89. package/core/dev/scripts/legacy/multi_agent_scheduler.ps1.bak +1978 -1978
  90. package/core/dev/scripts/matrix/src/cli/commands/list-tags.ts.wip +140 -140
  91. package/core/dev/scripts/matrix/src/cli/commands/manage-tags.ts.wip +173 -173
  92. package/core/dev/scripts/matrix/src/cli/commands/stats.ts.wip +168 -168
  93. package/core/dev/scripts/matrix/src/cli/commands/tokens.ts.wip +126 -126
  94. package/core/dev/scripts/multi_agent_scheduler.sh +0 -0
  95. package/core/dev/scripts/run_once.sh +0 -0
  96. package/core/dev/scripts/setup_mcp.sh +0 -0
  97. package/core/dev/scripts/setup_rag.sh +0 -0
  98. package/core/dev/scripts/start_dev_agent.sh +0 -0
  99. package/core/dev/scripts/start_multi_agent.sh +0 -0
  100. package/core/dev/scripts/sync_task_queue.sh +0 -0
  101. package/core/plugins/kenaz/commands/Amelia.md +1 -1
  102. package/core/plugins/kenaz/commands/Ansuz.md +15 -3
  103. package/core/plugins/kenaz/scripts/kenaz-mode.js +11 -1
  104. package/core/plugins/kenaz/scripts/sync.js +6 -2
  105. package/core/plugins/kenaz/scripts/ygg.js +13 -1
  106. package/core/plugins/kenaz/skills/product_manager/SKILL.md +12 -12
  107. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/charts.csv +26 -26
  108. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/colors.csv +97 -97
  109. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/icons.csv +101 -101
  110. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/landing.csv +31 -31
  111. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/products.csv +96 -96
  112. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/react-performance.csv +45 -45
  113. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -54
  114. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -53
  115. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -56
  116. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -53
  117. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -53
  118. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -51
  119. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -59
  120. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -52
  121. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/react.csv +54 -54
  122. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -61
  123. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -54
  124. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -51
  125. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -50
  126. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/styles.csv +68 -68
  127. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/typography.csv +57 -57
  128. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -101
  129. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/ux-guidelines.csv +99 -99
  130. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/web-interface.csv +31 -31
  131. package/core/plugins/kenaz/skills/ui-ux-pro-max/scripts/core.py +253 -253
  132. package/core/plugins/kenaz/skills/ui-ux-pro-max/scripts/design_system.py +1067 -1067
  133. package/core/plugins/kenaz/skills/ui-ux-pro-max/scripts/search.py +106 -106
  134. package/lib/cli.js +11 -4
  135. package/package.json +11 -7
  136. package/vendor/install-kenaz-cli.js +18 -7
  137. package/core/.formation/tools/broadcast.js +0 -103
  138. package/core/.formation/tools/dispatch-dryrun.test.js +0 -54
  139. package/core/.formation/tools/dispatch-notifier.js +0 -84
  140. package/core/.formation/tools/formations.js +0 -1203
  141. package/core/.formation/tools/headless-branch-recovery.test.js +0 -64
  142. package/core/.formation/tools/headless-dryrun.test.js +0 -79
  143. package/core/.formation/tools/headless-v2.js +0 -659
  144. package/core/.formation/tools/headless.js +0 -1071
  145. package/core/.formation/tools/legacy-scheduler.js +0 -1994
  146. package/core/.formation/tools/logger.js +0 -273
  147. package/core/.formation/tools/memory-loader.js +0 -422
  148. package/core/.formation/tools/process-state.js +0 -33
  149. package/core/.formation/tools/remote-conflict.js +0 -98
  150. package/core/.formation/tools/runner.js +0 -1332
  151. package/core/.formation/tools/task-utils.js +0 -344
  152. package/core/.formation/tools/task2028-headless-worktree.test.js +0 -112
  153. package/core/.formation/tools/tests/headless-mcp-args-plumbing.test.js +0 -123
  154. package/core/.formation/tools/worktree-leak.test.js +0 -162
  155. package/core/k-cli/dist/alaya/run.js +0 -91
  156. package/core/k-cli/dist/core/output.js +0 -24
  157. package/core/k-cli/dist/core/process.js +0 -38
  158. package/core/k-cli/dist/core/project.js +0 -80
  159. package/core/k-cli/dist/core/spawn.js +0 -268
  160. package/core/k-cli/dist/core/types.js +0 -2
  161. package/core/k-cli/dist/dashboard/update.js +0 -53
  162. package/core/k-cli/dist/dashboard/version.js +0 -20
  163. package/core/k-cli/dist/dispatch/run.js +0 -241
  164. package/core/k-cli/dist/dispatch/status.js +0 -103
  165. package/core/k-cli/dist/index.js +0 -283
  166. package/core/k-cli/dist/project/initialize.js +0 -301
  167. package/core/k-cli/dist/project/install.js +0 -89
  168. package/core/k-cli/dist/project/status.js +0 -138
  169. package/core/k-cli/dist/specialist/list.js +0 -100
  170. package/core/k-cli/dist/specialist/run.js +0 -93
  171. package/core/k-cli/dist/task/create.js +0 -194
  172. package/core/k-cli/dist/task/delete.js +0 -98
  173. package/core/k-cli/dist/task/get.js +0 -55
  174. package/core/k-cli/dist/task/list.js +0 -58
  175. package/core/k-cli/dist/task/submit.js +0 -156
  176. package/core/k-cli/dist/ygg/run.js +0 -51
  177. package/core/k-cli/node_modules/.bin/yaml +0 -16
  178. package/core/k-cli/node_modules/.bin/yaml.cmd +0 -17
  179. package/core/k-cli/node_modules/.bin/yaml.ps1 +0 -28
  180. package/core/k-cli/node_modules/.package-lock.json +0 -23
  181. package/core/k-cli/node_modules/yaml/LICENSE +0 -13
  182. package/core/k-cli/node_modules/yaml/README.md +0 -172
  183. package/core/k-cli/node_modules/yaml/bin.mjs +0 -11
  184. package/core/k-cli/node_modules/yaml/browser/dist/compose/compose-collection.js +0 -88
  185. package/core/k-cli/node_modules/yaml/browser/dist/compose/compose-doc.js +0 -43
  186. package/core/k-cli/node_modules/yaml/browser/dist/compose/compose-node.js +0 -109
  187. package/core/k-cli/node_modules/yaml/browser/dist/compose/compose-scalar.js +0 -86
  188. package/core/k-cli/node_modules/yaml/browser/dist/compose/composer.js +0 -217
  189. package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-block-map.js +0 -115
  190. package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-block-scalar.js +0 -198
  191. package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-block-seq.js +0 -49
  192. package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-end.js +0 -37
  193. package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-flow-collection.js +0 -207
  194. package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-flow-scalar.js +0 -223
  195. package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-props.js +0 -146
  196. package/core/k-cli/node_modules/yaml/browser/dist/compose/util-contains-newline.js +0 -34
  197. package/core/k-cli/node_modules/yaml/browser/dist/compose/util-empty-scalar-position.js +0 -26
  198. package/core/k-cli/node_modules/yaml/browser/dist/compose/util-flow-indent-check.js +0 -15
  199. package/core/k-cli/node_modules/yaml/browser/dist/compose/util-map-includes.js +0 -13
  200. package/core/k-cli/node_modules/yaml/browser/dist/doc/Document.js +0 -335
  201. package/core/k-cli/node_modules/yaml/browser/dist/doc/anchors.js +0 -71
  202. package/core/k-cli/node_modules/yaml/browser/dist/doc/applyReviver.js +0 -55
  203. package/core/k-cli/node_modules/yaml/browser/dist/doc/createNode.js +0 -88
  204. package/core/k-cli/node_modules/yaml/browser/dist/doc/directives.js +0 -176
  205. package/core/k-cli/node_modules/yaml/browser/dist/errors.js +0 -57
  206. package/core/k-cli/node_modules/yaml/browser/dist/index.js +0 -17
  207. package/core/k-cli/node_modules/yaml/browser/dist/log.js +0 -11
  208. package/core/k-cli/node_modules/yaml/browser/dist/nodes/Alias.js +0 -114
  209. package/core/k-cli/node_modules/yaml/browser/dist/nodes/Collection.js +0 -147
  210. package/core/k-cli/node_modules/yaml/browser/dist/nodes/Node.js +0 -38
  211. package/core/k-cli/node_modules/yaml/browser/dist/nodes/Pair.js +0 -36
  212. package/core/k-cli/node_modules/yaml/browser/dist/nodes/Scalar.js +0 -24
  213. package/core/k-cli/node_modules/yaml/browser/dist/nodes/YAMLMap.js +0 -144
  214. package/core/k-cli/node_modules/yaml/browser/dist/nodes/YAMLSeq.js +0 -113
  215. package/core/k-cli/node_modules/yaml/browser/dist/nodes/addPairToJSMap.js +0 -63
  216. package/core/k-cli/node_modules/yaml/browser/dist/nodes/identity.js +0 -36
  217. package/core/k-cli/node_modules/yaml/browser/dist/nodes/toJS.js +0 -37
  218. package/core/k-cli/node_modules/yaml/browser/dist/parse/cst-scalar.js +0 -214
  219. package/core/k-cli/node_modules/yaml/browser/dist/parse/cst-stringify.js +0 -61
  220. package/core/k-cli/node_modules/yaml/browser/dist/parse/cst-visit.js +0 -97
  221. package/core/k-cli/node_modules/yaml/browser/dist/parse/cst.js +0 -98
  222. package/core/k-cli/node_modules/yaml/browser/dist/parse/lexer.js +0 -717
  223. package/core/k-cli/node_modules/yaml/browser/dist/parse/line-counter.js +0 -39
  224. package/core/k-cli/node_modules/yaml/browser/dist/parse/parser.js +0 -967
  225. package/core/k-cli/node_modules/yaml/browser/dist/public-api.js +0 -102
  226. package/core/k-cli/node_modules/yaml/browser/dist/schema/Schema.js +0 -37
  227. package/core/k-cli/node_modules/yaml/browser/dist/schema/common/map.js +0 -17
  228. package/core/k-cli/node_modules/yaml/browser/dist/schema/common/null.js +0 -15
  229. package/core/k-cli/node_modules/yaml/browser/dist/schema/common/seq.js +0 -17
  230. package/core/k-cli/node_modules/yaml/browser/dist/schema/common/string.js +0 -14
  231. package/core/k-cli/node_modules/yaml/browser/dist/schema/core/bool.js +0 -19
  232. package/core/k-cli/node_modules/yaml/browser/dist/schema/core/float.js +0 -43
  233. package/core/k-cli/node_modules/yaml/browser/dist/schema/core/int.js +0 -38
  234. package/core/k-cli/node_modules/yaml/browser/dist/schema/core/schema.js +0 -23
  235. package/core/k-cli/node_modules/yaml/browser/dist/schema/json/schema.js +0 -62
  236. package/core/k-cli/node_modules/yaml/browser/dist/schema/tags.js +0 -96
  237. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/binary.js +0 -58
  238. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/bool.js +0 -26
  239. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/float.js +0 -46
  240. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/int.js +0 -71
  241. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/merge.js +0 -64
  242. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/omap.js +0 -74
  243. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/pairs.js +0 -78
  244. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/schema.js +0 -39
  245. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/set.js +0 -93
  246. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/timestamp.js +0 -101
  247. package/core/k-cli/node_modules/yaml/browser/dist/stringify/foldFlowLines.js +0 -146
  248. package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringify.js +0 -129
  249. package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyCollection.js +0 -153
  250. package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyComment.js +0 -20
  251. package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyDocument.js +0 -85
  252. package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyNumber.js +0 -24
  253. package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyPair.js +0 -150
  254. package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyString.js +0 -336
  255. package/core/k-cli/node_modules/yaml/browser/dist/util.js +0 -11
  256. package/core/k-cli/node_modules/yaml/browser/dist/visit.js +0 -233
  257. package/core/k-cli/node_modules/yaml/browser/index.js +0 -5
  258. package/core/k-cli/node_modules/yaml/browser/package.json +0 -3
  259. package/core/k-cli/node_modules/yaml/dist/cli.d.ts +0 -8
  260. package/core/k-cli/node_modules/yaml/dist/cli.mjs +0 -201
  261. package/core/k-cli/node_modules/yaml/dist/compose/compose-collection.d.ts +0 -11
  262. package/core/k-cli/node_modules/yaml/dist/compose/compose-collection.js +0 -90
  263. package/core/k-cli/node_modules/yaml/dist/compose/compose-doc.d.ts +0 -7
  264. package/core/k-cli/node_modules/yaml/dist/compose/compose-doc.js +0 -45
  265. package/core/k-cli/node_modules/yaml/dist/compose/compose-node.d.ts +0 -29
  266. package/core/k-cli/node_modules/yaml/dist/compose/compose-node.js +0 -112
  267. package/core/k-cli/node_modules/yaml/dist/compose/compose-scalar.d.ts +0 -5
  268. package/core/k-cli/node_modules/yaml/dist/compose/compose-scalar.js +0 -88
  269. package/core/k-cli/node_modules/yaml/dist/compose/composer.d.ts +0 -63
  270. package/core/k-cli/node_modules/yaml/dist/compose/composer.js +0 -222
  271. package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-map.d.ts +0 -6
  272. package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-map.js +0 -117
  273. package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-scalar.d.ts +0 -11
  274. package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-scalar.js +0 -200
  275. package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-seq.d.ts +0 -6
  276. package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-seq.js +0 -51
  277. package/core/k-cli/node_modules/yaml/dist/compose/resolve-end.d.ts +0 -6
  278. package/core/k-cli/node_modules/yaml/dist/compose/resolve-end.js +0 -39
  279. package/core/k-cli/node_modules/yaml/dist/compose/resolve-flow-collection.d.ts +0 -7
  280. package/core/k-cli/node_modules/yaml/dist/compose/resolve-flow-collection.js +0 -209
  281. package/core/k-cli/node_modules/yaml/dist/compose/resolve-flow-scalar.d.ts +0 -10
  282. package/core/k-cli/node_modules/yaml/dist/compose/resolve-flow-scalar.js +0 -225
  283. package/core/k-cli/node_modules/yaml/dist/compose/resolve-props.d.ts +0 -23
  284. package/core/k-cli/node_modules/yaml/dist/compose/resolve-props.js +0 -148
  285. package/core/k-cli/node_modules/yaml/dist/compose/util-contains-newline.d.ts +0 -2
  286. package/core/k-cli/node_modules/yaml/dist/compose/util-contains-newline.js +0 -36
  287. package/core/k-cli/node_modules/yaml/dist/compose/util-empty-scalar-position.d.ts +0 -2
  288. package/core/k-cli/node_modules/yaml/dist/compose/util-empty-scalar-position.js +0 -28
  289. package/core/k-cli/node_modules/yaml/dist/compose/util-flow-indent-check.d.ts +0 -3
  290. package/core/k-cli/node_modules/yaml/dist/compose/util-flow-indent-check.js +0 -17
  291. package/core/k-cli/node_modules/yaml/dist/compose/util-map-includes.d.ts +0 -4
  292. package/core/k-cli/node_modules/yaml/dist/compose/util-map-includes.js +0 -15
  293. package/core/k-cli/node_modules/yaml/dist/doc/Document.d.ts +0 -141
  294. package/core/k-cli/node_modules/yaml/dist/doc/Document.js +0 -337
  295. package/core/k-cli/node_modules/yaml/dist/doc/anchors.d.ts +0 -24
  296. package/core/k-cli/node_modules/yaml/dist/doc/anchors.js +0 -76
  297. package/core/k-cli/node_modules/yaml/dist/doc/applyReviver.d.ts +0 -9
  298. package/core/k-cli/node_modules/yaml/dist/doc/applyReviver.js +0 -57
  299. package/core/k-cli/node_modules/yaml/dist/doc/createNode.d.ts +0 -17
  300. package/core/k-cli/node_modules/yaml/dist/doc/createNode.js +0 -90
  301. package/core/k-cli/node_modules/yaml/dist/doc/directives.d.ts +0 -49
  302. package/core/k-cli/node_modules/yaml/dist/doc/directives.js +0 -178
  303. package/core/k-cli/node_modules/yaml/dist/errors.d.ts +0 -21
  304. package/core/k-cli/node_modules/yaml/dist/errors.js +0 -62
  305. package/core/k-cli/node_modules/yaml/dist/index.d.ts +0 -25
  306. package/core/k-cli/node_modules/yaml/dist/index.js +0 -50
  307. package/core/k-cli/node_modules/yaml/dist/log.d.ts +0 -3
  308. package/core/k-cli/node_modules/yaml/dist/log.js +0 -19
  309. package/core/k-cli/node_modules/yaml/dist/nodes/Alias.d.ts +0 -29
  310. package/core/k-cli/node_modules/yaml/dist/nodes/Alias.js +0 -116
  311. package/core/k-cli/node_modules/yaml/dist/nodes/Collection.d.ts +0 -73
  312. package/core/k-cli/node_modules/yaml/dist/nodes/Collection.js +0 -151
  313. package/core/k-cli/node_modules/yaml/dist/nodes/Node.d.ts +0 -53
  314. package/core/k-cli/node_modules/yaml/dist/nodes/Node.js +0 -40
  315. package/core/k-cli/node_modules/yaml/dist/nodes/Pair.d.ts +0 -22
  316. package/core/k-cli/node_modules/yaml/dist/nodes/Pair.js +0 -39
  317. package/core/k-cli/node_modules/yaml/dist/nodes/Scalar.d.ts +0 -43
  318. package/core/k-cli/node_modules/yaml/dist/nodes/Scalar.js +0 -27
  319. package/core/k-cli/node_modules/yaml/dist/nodes/YAMLMap.d.ts +0 -53
  320. package/core/k-cli/node_modules/yaml/dist/nodes/YAMLMap.js +0 -147
  321. package/core/k-cli/node_modules/yaml/dist/nodes/YAMLSeq.d.ts +0 -60
  322. package/core/k-cli/node_modules/yaml/dist/nodes/YAMLSeq.js +0 -115
  323. package/core/k-cli/node_modules/yaml/dist/nodes/addPairToJSMap.d.ts +0 -4
  324. package/core/k-cli/node_modules/yaml/dist/nodes/addPairToJSMap.js +0 -65
  325. package/core/k-cli/node_modules/yaml/dist/nodes/identity.d.ts +0 -23
  326. package/core/k-cli/node_modules/yaml/dist/nodes/identity.js +0 -53
  327. package/core/k-cli/node_modules/yaml/dist/nodes/toJS.d.ts +0 -29
  328. package/core/k-cli/node_modules/yaml/dist/nodes/toJS.js +0 -39
  329. package/core/k-cli/node_modules/yaml/dist/options.d.ts +0 -350
  330. package/core/k-cli/node_modules/yaml/dist/parse/cst-scalar.d.ts +0 -64
  331. package/core/k-cli/node_modules/yaml/dist/parse/cst-scalar.js +0 -218
  332. package/core/k-cli/node_modules/yaml/dist/parse/cst-stringify.d.ts +0 -8
  333. package/core/k-cli/node_modules/yaml/dist/parse/cst-stringify.js +0 -63
  334. package/core/k-cli/node_modules/yaml/dist/parse/cst-visit.d.ts +0 -39
  335. package/core/k-cli/node_modules/yaml/dist/parse/cst-visit.js +0 -99
  336. package/core/k-cli/node_modules/yaml/dist/parse/cst.d.ts +0 -109
  337. package/core/k-cli/node_modules/yaml/dist/parse/cst.js +0 -112
  338. package/core/k-cli/node_modules/yaml/dist/parse/lexer.d.ts +0 -87
  339. package/core/k-cli/node_modules/yaml/dist/parse/lexer.js +0 -719
  340. package/core/k-cli/node_modules/yaml/dist/parse/line-counter.d.ts +0 -22
  341. package/core/k-cli/node_modules/yaml/dist/parse/line-counter.js +0 -41
  342. package/core/k-cli/node_modules/yaml/dist/parse/parser.d.ts +0 -84
  343. package/core/k-cli/node_modules/yaml/dist/parse/parser.js +0 -972
  344. package/core/k-cli/node_modules/yaml/dist/public-api.d.ts +0 -44
  345. package/core/k-cli/node_modules/yaml/dist/public-api.js +0 -107
  346. package/core/k-cli/node_modules/yaml/dist/schema/Schema.d.ts +0 -17
  347. package/core/k-cli/node_modules/yaml/dist/schema/Schema.js +0 -39
  348. package/core/k-cli/node_modules/yaml/dist/schema/common/map.d.ts +0 -2
  349. package/core/k-cli/node_modules/yaml/dist/schema/common/map.js +0 -19
  350. package/core/k-cli/node_modules/yaml/dist/schema/common/null.d.ts +0 -4
  351. package/core/k-cli/node_modules/yaml/dist/schema/common/null.js +0 -17
  352. package/core/k-cli/node_modules/yaml/dist/schema/common/seq.d.ts +0 -2
  353. package/core/k-cli/node_modules/yaml/dist/schema/common/seq.js +0 -19
  354. package/core/k-cli/node_modules/yaml/dist/schema/common/string.d.ts +0 -2
  355. package/core/k-cli/node_modules/yaml/dist/schema/common/string.js +0 -16
  356. package/core/k-cli/node_modules/yaml/dist/schema/core/bool.d.ts +0 -4
  357. package/core/k-cli/node_modules/yaml/dist/schema/core/bool.js +0 -21
  358. package/core/k-cli/node_modules/yaml/dist/schema/core/float.d.ts +0 -4
  359. package/core/k-cli/node_modules/yaml/dist/schema/core/float.js +0 -47
  360. package/core/k-cli/node_modules/yaml/dist/schema/core/int.d.ts +0 -4
  361. package/core/k-cli/node_modules/yaml/dist/schema/core/int.js +0 -42
  362. package/core/k-cli/node_modules/yaml/dist/schema/core/schema.d.ts +0 -1
  363. package/core/k-cli/node_modules/yaml/dist/schema/core/schema.js +0 -25
  364. package/core/k-cli/node_modules/yaml/dist/schema/json/schema.d.ts +0 -2
  365. package/core/k-cli/node_modules/yaml/dist/schema/json/schema.js +0 -64
  366. package/core/k-cli/node_modules/yaml/dist/schema/json-schema.d.ts +0 -69
  367. package/core/k-cli/node_modules/yaml/dist/schema/tags.d.ts +0 -48
  368. package/core/k-cli/node_modules/yaml/dist/schema/tags.js +0 -99
  369. package/core/k-cli/node_modules/yaml/dist/schema/types.d.ts +0 -92
  370. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/binary.d.ts +0 -2
  371. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/binary.js +0 -70
  372. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/bool.d.ts +0 -7
  373. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/bool.js +0 -29
  374. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/float.d.ts +0 -4
  375. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/float.js +0 -50
  376. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/int.d.ts +0 -5
  377. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/int.js +0 -76
  378. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/merge.d.ts +0 -9
  379. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/merge.js +0 -68
  380. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/omap.d.ts +0 -22
  381. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/omap.js +0 -77
  382. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/pairs.d.ts +0 -10
  383. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/pairs.js +0 -82
  384. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/schema.d.ts +0 -1
  385. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/schema.js +0 -41
  386. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/set.d.ts +0 -28
  387. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/set.js +0 -96
  388. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/timestamp.d.ts +0 -6
  389. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/timestamp.js +0 -105
  390. package/core/k-cli/node_modules/yaml/dist/stringify/foldFlowLines.d.ts +0 -34
  391. package/core/k-cli/node_modules/yaml/dist/stringify/foldFlowLines.js +0 -151
  392. package/core/k-cli/node_modules/yaml/dist/stringify/stringify.d.ts +0 -21
  393. package/core/k-cli/node_modules/yaml/dist/stringify/stringify.js +0 -132
  394. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyCollection.d.ts +0 -17
  395. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyCollection.js +0 -155
  396. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyComment.d.ts +0 -10
  397. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyComment.js +0 -24
  398. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyDocument.d.ts +0 -4
  399. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyDocument.js +0 -87
  400. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyNumber.d.ts +0 -2
  401. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyNumber.js +0 -26
  402. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyPair.d.ts +0 -3
  403. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyPair.js +0 -152
  404. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyString.d.ts +0 -9
  405. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyString.js +0 -338
  406. package/core/k-cli/node_modules/yaml/dist/test-events.d.ts +0 -4
  407. package/core/k-cli/node_modules/yaml/dist/test-events.js +0 -134
  408. package/core/k-cli/node_modules/yaml/dist/util.d.ts +0 -16
  409. package/core/k-cli/node_modules/yaml/dist/util.js +0 -28
  410. package/core/k-cli/node_modules/yaml/dist/visit.d.ts +0 -102
  411. package/core/k-cli/node_modules/yaml/dist/visit.js +0 -236
  412. package/core/k-cli/node_modules/yaml/package.json +0 -97
  413. 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) {