kenaz 0.3.2 → 0.3.3

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 (390) 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 +19 -30
  4. package/core/.claude/hooks/_ansuz-conversation.js +1 -0
  5. package/core/.claude/hooks/_ansuz-conversation.test.js +6 -0
  6. package/core/.claude/hooks/_run-user-prompt.js +0 -4
  7. package/core/.claude/hooks/_run-user-prompt.test.js +2 -3
  8. package/core/.claude/hooks/session-init.ps1 +49 -49
  9. package/core/.claude/settings.json +0 -37
  10. package/core/.claude/skills/ui-ux-pro-max/data/charts.csv +26 -26
  11. package/core/.claude/skills/ui-ux-pro-max/data/colors.csv +97 -97
  12. package/core/.claude/skills/ui-ux-pro-max/data/icons.csv +101 -101
  13. package/core/.claude/skills/ui-ux-pro-max/data/landing.csv +31 -31
  14. package/core/.claude/skills/ui-ux-pro-max/data/products.csv +96 -96
  15. package/core/.claude/skills/ui-ux-pro-max/data/react-performance.csv +45 -45
  16. package/core/.claude/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -54
  17. package/core/.claude/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -53
  18. package/core/.claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -56
  19. package/core/.claude/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -53
  20. package/core/.claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -53
  21. package/core/.claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -51
  22. package/core/.claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -59
  23. package/core/.claude/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -52
  24. package/core/.claude/skills/ui-ux-pro-max/data/stacks/react.csv +54 -54
  25. package/core/.claude/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -61
  26. package/core/.claude/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -54
  27. package/core/.claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -51
  28. package/core/.claude/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -50
  29. package/core/.claude/skills/ui-ux-pro-max/data/styles.csv +68 -68
  30. package/core/.claude/skills/ui-ux-pro-max/data/typography.csv +57 -57
  31. package/core/.claude/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -101
  32. package/core/.claude/skills/ui-ux-pro-max/data/ux-guidelines.csv +99 -99
  33. package/core/.claude/skills/ui-ux-pro-max/data/web-interface.csv +31 -31
  34. package/core/.claude/skills/ui-ux-pro-max/scripts/core.py +253 -253
  35. package/core/.claude/skills/ui-ux-pro-max/scripts/design_system.py +1067 -1067
  36. package/core/.claude/skills/ui-ux-pro-max/scripts/search.py +106 -106
  37. package/core/.collaboration/core/task-api.js +3 -8
  38. package/core/.formation/tests/hello.txt +2 -2
  39. package/core/.formation/tools/prompt.js +58 -5
  40. package/core/.formation/tools/prompt.test.js +29 -0
  41. package/core/.kenaz/claude-hooks-manifest.json +16 -16
  42. package/core/.kenaz/knowledge/ladybug_2c51da1e_shadow_file_frame_group.patch +163 -163
  43. package/core/.kenaz/knowledge/ladybug_30cf85690e_shadow_page_read_during_checkpoint.patch +393 -393
  44. package/core/.kenaz/knowledge/ladybug_7a17858d3a_node_group_delete_lock.patch +102 -102
  45. package/core/.kenaz/knowledge/ladybug_926a0ba1_column_checkpoint.patch +412 -412
  46. package/core/.kenaz/knowledge/ladybug_a346c4c4b6_pk_index_header_pages.patch +144 -144
  47. package/core/.kenaz/knowledge/ladybug_ba5f38815b_csr_node_group.patch +86 -86
  48. package/core/.kenaz/muninn_episode_write.ps1 +17 -17
  49. package/core/.kenaz/specialist/field_report_watermark.json +1 -1
  50. package/core/.kenaz/specialist/memory_guard_fired.json +1 -1
  51. package/core/.kenaz/specialist/memory_guard_trigger_cache.json +1 -1
  52. package/core/.kenaz/specialist/t1777_probe.txt +1 -1
  53. package/core/.matrix/index.matrix +5 -5
  54. package/core/.specialist/Ansuz/RULES.md +6 -10
  55. package/core/.specialist/tools/__tests__/task-2218-boot-session-id.test.js +17 -1
  56. package/core/.specialist/tools/__tests__/task-2222-session-identity-interleave.test.js +17 -1
  57. package/core/.specialist/tools/__tests__/task-2254-handoff-archive-contract.test.js +6 -5
  58. package/core/.specialist/tools/ansuz-boot-fast.js +44 -100
  59. package/core/.specialist/tools/ansuz-boot-fast.test.js +45 -1
  60. package/core/KENAZ_CORE_VERSION +1 -1
  61. package/core/dev/ANSUZ_STARTUP_INJECTION_REVIEW.md +355 -355
  62. package/core/dev/JEV_INTEGRATION_PLAN.md +189 -189
  63. package/core/dev/design/matrix_format_EXAMPLE_memory.matrix +13 -13
  64. package/core/dev/design/matrix_format_EXAMPLE_project_status.matrix +9 -9
  65. package/core/dev/design/matrix_format_EXAMPLE_tasks.matrix +14 -14
  66. package/core/dev/design/matrix_v2_EXAMPLE.matrix +67 -67
  67. package/core/dev/jev-eval/ACTIVE_CORRECTION.md +52 -52
  68. package/core/dev/jev-eval/CONTEXT_REVIEW.md +31 -31
  69. package/core/dev/jev-eval/EVALUATION_STATUS.md +39 -39
  70. package/core/dev/jev-eval/HISTORICAL_CORPUS.md +33 -33
  71. package/core/dev/jev-eval/README.md +99 -99
  72. package/core/dev/jev-eval/THALAMUS_RANKING.md +58 -58
  73. package/core/dev/jev-eval/reports/annotation-pilot-2026-09-20.json +32 -32
  74. package/core/dev/jev-eval/reports/context-review-2026-09-20.json +48 -48
  75. package/core/dev/jev-eval/reports/history-corpus-2026-09-20.json +52 -52
  76. package/core/dev/pipeline/SELF_ENHANCEMENT_STATUS.json.bak +47 -47
  77. package/core/dev/pipeline/tasks/.gitkeep +1 -1
  78. package/core/dev/pipeline/test_results/TASK_TEST_PARALLEL_002_result.txt +27 -27
  79. package/core/dev/plans/ANSUZ_SLIM_PLAN.md +68 -0
  80. package/core/dev/scripts/init_git.sh +0 -0
  81. package/core/dev/scripts/init_project.sh +0 -0
  82. package/core/dev/scripts/legacy/dev_agent_scheduler.ps1.bak +536 -536
  83. package/core/dev/scripts/legacy/multi_agent_scheduler.ps1.bak +1978 -1978
  84. package/core/dev/scripts/matrix/src/cli/commands/list-tags.ts.wip +140 -140
  85. package/core/dev/scripts/matrix/src/cli/commands/manage-tags.ts.wip +173 -173
  86. package/core/dev/scripts/matrix/src/cli/commands/stats.ts.wip +168 -168
  87. package/core/dev/scripts/matrix/src/cli/commands/tokens.ts.wip +126 -126
  88. package/core/dev/scripts/multi_agent_scheduler.sh +0 -0
  89. package/core/dev/scripts/run_once.sh +0 -0
  90. package/core/dev/scripts/setup_mcp.sh +0 -0
  91. package/core/dev/scripts/setup_rag.sh +0 -0
  92. package/core/dev/scripts/start_dev_agent.sh +0 -0
  93. package/core/dev/scripts/start_multi_agent.sh +0 -0
  94. package/core/dev/scripts/sync_task_queue.sh +0 -0
  95. package/core/k-cli/dist/alaya/run.js +91 -0
  96. package/core/k-cli/dist/core/output.js +24 -0
  97. package/core/k-cli/dist/core/process.js +38 -0
  98. package/core/k-cli/dist/core/project.js +80 -0
  99. package/core/k-cli/dist/core/spawn.js +268 -0
  100. package/core/k-cli/dist/core/types.js +2 -0
  101. package/core/k-cli/dist/dashboard/update.js +53 -0
  102. package/core/k-cli/dist/dashboard/version.js +20 -0
  103. package/core/k-cli/dist/dispatch/run.js +241 -0
  104. package/core/k-cli/dist/dispatch/status.js +103 -0
  105. package/core/k-cli/dist/index.js +283 -0
  106. package/core/k-cli/dist/project/initialize.js +301 -0
  107. package/core/k-cli/dist/project/install.js +89 -0
  108. package/core/k-cli/dist/project/status.js +138 -0
  109. package/core/k-cli/dist/specialist/list.js +100 -0
  110. package/core/k-cli/dist/specialist/run.js +93 -0
  111. package/core/k-cli/dist/task/create.js +194 -0
  112. package/core/k-cli/dist/task/delete.js +98 -0
  113. package/core/k-cli/dist/task/get.js +55 -0
  114. package/core/k-cli/dist/task/list.js +58 -0
  115. package/core/k-cli/dist/task/submit.js +156 -0
  116. package/core/k-cli/dist/ygg/run.js +51 -0
  117. package/core/k-cli/node_modules/.bin/yaml +16 -0
  118. package/core/k-cli/node_modules/.bin/yaml.cmd +17 -0
  119. package/core/k-cli/node_modules/.bin/yaml.ps1 +28 -0
  120. package/core/k-cli/node_modules/.package-lock.json +23 -0
  121. package/core/k-cli/node_modules/yaml/LICENSE +13 -0
  122. package/core/k-cli/node_modules/yaml/README.md +172 -0
  123. package/core/k-cli/node_modules/yaml/bin.mjs +11 -0
  124. package/core/k-cli/node_modules/yaml/browser/dist/compose/compose-collection.js +88 -0
  125. package/core/k-cli/node_modules/yaml/browser/dist/compose/compose-doc.js +43 -0
  126. package/core/k-cli/node_modules/yaml/browser/dist/compose/compose-node.js +109 -0
  127. package/core/k-cli/node_modules/yaml/browser/dist/compose/compose-scalar.js +86 -0
  128. package/core/k-cli/node_modules/yaml/browser/dist/compose/composer.js +217 -0
  129. package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-block-map.js +115 -0
  130. package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-block-scalar.js +198 -0
  131. package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-block-seq.js +49 -0
  132. package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-end.js +37 -0
  133. package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-flow-collection.js +207 -0
  134. package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-flow-scalar.js +223 -0
  135. package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-props.js +146 -0
  136. package/core/k-cli/node_modules/yaml/browser/dist/compose/util-contains-newline.js +34 -0
  137. package/core/k-cli/node_modules/yaml/browser/dist/compose/util-empty-scalar-position.js +26 -0
  138. package/core/k-cli/node_modules/yaml/browser/dist/compose/util-flow-indent-check.js +15 -0
  139. package/core/k-cli/node_modules/yaml/browser/dist/compose/util-map-includes.js +13 -0
  140. package/core/k-cli/node_modules/yaml/browser/dist/doc/Document.js +335 -0
  141. package/core/k-cli/node_modules/yaml/browser/dist/doc/anchors.js +71 -0
  142. package/core/k-cli/node_modules/yaml/browser/dist/doc/applyReviver.js +55 -0
  143. package/core/k-cli/node_modules/yaml/browser/dist/doc/createNode.js +88 -0
  144. package/core/k-cli/node_modules/yaml/browser/dist/doc/directives.js +176 -0
  145. package/core/k-cli/node_modules/yaml/browser/dist/errors.js +57 -0
  146. package/core/k-cli/node_modules/yaml/browser/dist/index.js +17 -0
  147. package/core/k-cli/node_modules/yaml/browser/dist/log.js +11 -0
  148. package/core/k-cli/node_modules/yaml/browser/dist/nodes/Alias.js +114 -0
  149. package/core/k-cli/node_modules/yaml/browser/dist/nodes/Collection.js +147 -0
  150. package/core/k-cli/node_modules/yaml/browser/dist/nodes/Node.js +38 -0
  151. package/core/k-cli/node_modules/yaml/browser/dist/nodes/Pair.js +36 -0
  152. package/core/k-cli/node_modules/yaml/browser/dist/nodes/Scalar.js +24 -0
  153. package/core/k-cli/node_modules/yaml/browser/dist/nodes/YAMLMap.js +144 -0
  154. package/core/k-cli/node_modules/yaml/browser/dist/nodes/YAMLSeq.js +113 -0
  155. package/core/k-cli/node_modules/yaml/browser/dist/nodes/addPairToJSMap.js +63 -0
  156. package/core/k-cli/node_modules/yaml/browser/dist/nodes/identity.js +36 -0
  157. package/core/k-cli/node_modules/yaml/browser/dist/nodes/toJS.js +37 -0
  158. package/core/k-cli/node_modules/yaml/browser/dist/parse/cst-scalar.js +214 -0
  159. package/core/k-cli/node_modules/yaml/browser/dist/parse/cst-stringify.js +61 -0
  160. package/core/k-cli/node_modules/yaml/browser/dist/parse/cst-visit.js +97 -0
  161. package/core/k-cli/node_modules/yaml/browser/dist/parse/cst.js +98 -0
  162. package/core/k-cli/node_modules/yaml/browser/dist/parse/lexer.js +717 -0
  163. package/core/k-cli/node_modules/yaml/browser/dist/parse/line-counter.js +39 -0
  164. package/core/k-cli/node_modules/yaml/browser/dist/parse/parser.js +967 -0
  165. package/core/k-cli/node_modules/yaml/browser/dist/public-api.js +102 -0
  166. package/core/k-cli/node_modules/yaml/browser/dist/schema/Schema.js +37 -0
  167. package/core/k-cli/node_modules/yaml/browser/dist/schema/common/map.js +17 -0
  168. package/core/k-cli/node_modules/yaml/browser/dist/schema/common/null.js +15 -0
  169. package/core/k-cli/node_modules/yaml/browser/dist/schema/common/seq.js +17 -0
  170. package/core/k-cli/node_modules/yaml/browser/dist/schema/common/string.js +14 -0
  171. package/core/k-cli/node_modules/yaml/browser/dist/schema/core/bool.js +19 -0
  172. package/core/k-cli/node_modules/yaml/browser/dist/schema/core/float.js +43 -0
  173. package/core/k-cli/node_modules/yaml/browser/dist/schema/core/int.js +38 -0
  174. package/core/k-cli/node_modules/yaml/browser/dist/schema/core/schema.js +23 -0
  175. package/core/k-cli/node_modules/yaml/browser/dist/schema/json/schema.js +62 -0
  176. package/core/k-cli/node_modules/yaml/browser/dist/schema/tags.js +96 -0
  177. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/binary.js +58 -0
  178. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/bool.js +26 -0
  179. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/float.js +46 -0
  180. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/int.js +71 -0
  181. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/merge.js +64 -0
  182. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/omap.js +74 -0
  183. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/pairs.js +78 -0
  184. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/schema.js +39 -0
  185. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/set.js +93 -0
  186. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/timestamp.js +101 -0
  187. package/core/k-cli/node_modules/yaml/browser/dist/stringify/foldFlowLines.js +146 -0
  188. package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringify.js +129 -0
  189. package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyCollection.js +153 -0
  190. package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyComment.js +20 -0
  191. package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyDocument.js +85 -0
  192. package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyNumber.js +24 -0
  193. package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyPair.js +150 -0
  194. package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyString.js +336 -0
  195. package/core/k-cli/node_modules/yaml/browser/dist/util.js +11 -0
  196. package/core/k-cli/node_modules/yaml/browser/dist/visit.js +233 -0
  197. package/core/k-cli/node_modules/yaml/browser/index.js +5 -0
  198. package/core/k-cli/node_modules/yaml/browser/package.json +3 -0
  199. package/core/k-cli/node_modules/yaml/dist/cli.d.ts +8 -0
  200. package/core/k-cli/node_modules/yaml/dist/cli.mjs +201 -0
  201. package/core/k-cli/node_modules/yaml/dist/compose/compose-collection.d.ts +11 -0
  202. package/core/k-cli/node_modules/yaml/dist/compose/compose-collection.js +90 -0
  203. package/core/k-cli/node_modules/yaml/dist/compose/compose-doc.d.ts +7 -0
  204. package/core/k-cli/node_modules/yaml/dist/compose/compose-doc.js +45 -0
  205. package/core/k-cli/node_modules/yaml/dist/compose/compose-node.d.ts +29 -0
  206. package/core/k-cli/node_modules/yaml/dist/compose/compose-node.js +112 -0
  207. package/core/k-cli/node_modules/yaml/dist/compose/compose-scalar.d.ts +5 -0
  208. package/core/k-cli/node_modules/yaml/dist/compose/compose-scalar.js +88 -0
  209. package/core/k-cli/node_modules/yaml/dist/compose/composer.d.ts +63 -0
  210. package/core/k-cli/node_modules/yaml/dist/compose/composer.js +222 -0
  211. package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-map.d.ts +6 -0
  212. package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-map.js +117 -0
  213. package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-scalar.d.ts +11 -0
  214. package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-scalar.js +200 -0
  215. package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-seq.d.ts +6 -0
  216. package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-seq.js +51 -0
  217. package/core/k-cli/node_modules/yaml/dist/compose/resolve-end.d.ts +6 -0
  218. package/core/k-cli/node_modules/yaml/dist/compose/resolve-end.js +39 -0
  219. package/core/k-cli/node_modules/yaml/dist/compose/resolve-flow-collection.d.ts +7 -0
  220. package/core/k-cli/node_modules/yaml/dist/compose/resolve-flow-collection.js +209 -0
  221. package/core/k-cli/node_modules/yaml/dist/compose/resolve-flow-scalar.d.ts +10 -0
  222. package/core/k-cli/node_modules/yaml/dist/compose/resolve-flow-scalar.js +225 -0
  223. package/core/k-cli/node_modules/yaml/dist/compose/resolve-props.d.ts +23 -0
  224. package/core/k-cli/node_modules/yaml/dist/compose/resolve-props.js +148 -0
  225. package/core/k-cli/node_modules/yaml/dist/compose/util-contains-newline.d.ts +2 -0
  226. package/core/k-cli/node_modules/yaml/dist/compose/util-contains-newline.js +36 -0
  227. package/core/k-cli/node_modules/yaml/dist/compose/util-empty-scalar-position.d.ts +2 -0
  228. package/core/k-cli/node_modules/yaml/dist/compose/util-empty-scalar-position.js +28 -0
  229. package/core/k-cli/node_modules/yaml/dist/compose/util-flow-indent-check.d.ts +3 -0
  230. package/core/k-cli/node_modules/yaml/dist/compose/util-flow-indent-check.js +17 -0
  231. package/core/k-cli/node_modules/yaml/dist/compose/util-map-includes.d.ts +4 -0
  232. package/core/k-cli/node_modules/yaml/dist/compose/util-map-includes.js +15 -0
  233. package/core/k-cli/node_modules/yaml/dist/doc/Document.d.ts +141 -0
  234. package/core/k-cli/node_modules/yaml/dist/doc/Document.js +337 -0
  235. package/core/k-cli/node_modules/yaml/dist/doc/anchors.d.ts +24 -0
  236. package/core/k-cli/node_modules/yaml/dist/doc/anchors.js +76 -0
  237. package/core/k-cli/node_modules/yaml/dist/doc/applyReviver.d.ts +9 -0
  238. package/core/k-cli/node_modules/yaml/dist/doc/applyReviver.js +57 -0
  239. package/core/k-cli/node_modules/yaml/dist/doc/createNode.d.ts +17 -0
  240. package/core/k-cli/node_modules/yaml/dist/doc/createNode.js +90 -0
  241. package/core/k-cli/node_modules/yaml/dist/doc/directives.d.ts +49 -0
  242. package/core/k-cli/node_modules/yaml/dist/doc/directives.js +178 -0
  243. package/core/k-cli/node_modules/yaml/dist/errors.d.ts +21 -0
  244. package/core/k-cli/node_modules/yaml/dist/errors.js +62 -0
  245. package/core/k-cli/node_modules/yaml/dist/index.d.ts +25 -0
  246. package/core/k-cli/node_modules/yaml/dist/index.js +50 -0
  247. package/core/k-cli/node_modules/yaml/dist/log.d.ts +3 -0
  248. package/core/k-cli/node_modules/yaml/dist/log.js +19 -0
  249. package/core/k-cli/node_modules/yaml/dist/nodes/Alias.d.ts +29 -0
  250. package/core/k-cli/node_modules/yaml/dist/nodes/Alias.js +116 -0
  251. package/core/k-cli/node_modules/yaml/dist/nodes/Collection.d.ts +73 -0
  252. package/core/k-cli/node_modules/yaml/dist/nodes/Collection.js +151 -0
  253. package/core/k-cli/node_modules/yaml/dist/nodes/Node.d.ts +53 -0
  254. package/core/k-cli/node_modules/yaml/dist/nodes/Node.js +40 -0
  255. package/core/k-cli/node_modules/yaml/dist/nodes/Pair.d.ts +22 -0
  256. package/core/k-cli/node_modules/yaml/dist/nodes/Pair.js +39 -0
  257. package/core/k-cli/node_modules/yaml/dist/nodes/Scalar.d.ts +43 -0
  258. package/core/k-cli/node_modules/yaml/dist/nodes/Scalar.js +27 -0
  259. package/core/k-cli/node_modules/yaml/dist/nodes/YAMLMap.d.ts +53 -0
  260. package/core/k-cli/node_modules/yaml/dist/nodes/YAMLMap.js +147 -0
  261. package/core/k-cli/node_modules/yaml/dist/nodes/YAMLSeq.d.ts +60 -0
  262. package/core/k-cli/node_modules/yaml/dist/nodes/YAMLSeq.js +115 -0
  263. package/core/k-cli/node_modules/yaml/dist/nodes/addPairToJSMap.d.ts +4 -0
  264. package/core/k-cli/node_modules/yaml/dist/nodes/addPairToJSMap.js +65 -0
  265. package/core/k-cli/node_modules/yaml/dist/nodes/identity.d.ts +23 -0
  266. package/core/k-cli/node_modules/yaml/dist/nodes/identity.js +53 -0
  267. package/core/k-cli/node_modules/yaml/dist/nodes/toJS.d.ts +29 -0
  268. package/core/k-cli/node_modules/yaml/dist/nodes/toJS.js +39 -0
  269. package/core/k-cli/node_modules/yaml/dist/options.d.ts +350 -0
  270. package/core/k-cli/node_modules/yaml/dist/parse/cst-scalar.d.ts +64 -0
  271. package/core/k-cli/node_modules/yaml/dist/parse/cst-scalar.js +218 -0
  272. package/core/k-cli/node_modules/yaml/dist/parse/cst-stringify.d.ts +8 -0
  273. package/core/k-cli/node_modules/yaml/dist/parse/cst-stringify.js +63 -0
  274. package/core/k-cli/node_modules/yaml/dist/parse/cst-visit.d.ts +39 -0
  275. package/core/k-cli/node_modules/yaml/dist/parse/cst-visit.js +99 -0
  276. package/core/k-cli/node_modules/yaml/dist/parse/cst.d.ts +109 -0
  277. package/core/k-cli/node_modules/yaml/dist/parse/cst.js +112 -0
  278. package/core/k-cli/node_modules/yaml/dist/parse/lexer.d.ts +87 -0
  279. package/core/k-cli/node_modules/yaml/dist/parse/lexer.js +719 -0
  280. package/core/k-cli/node_modules/yaml/dist/parse/line-counter.d.ts +22 -0
  281. package/core/k-cli/node_modules/yaml/dist/parse/line-counter.js +41 -0
  282. package/core/k-cli/node_modules/yaml/dist/parse/parser.d.ts +84 -0
  283. package/core/k-cli/node_modules/yaml/dist/parse/parser.js +972 -0
  284. package/core/k-cli/node_modules/yaml/dist/public-api.d.ts +44 -0
  285. package/core/k-cli/node_modules/yaml/dist/public-api.js +107 -0
  286. package/core/k-cli/node_modules/yaml/dist/schema/Schema.d.ts +17 -0
  287. package/core/k-cli/node_modules/yaml/dist/schema/Schema.js +39 -0
  288. package/core/k-cli/node_modules/yaml/dist/schema/common/map.d.ts +2 -0
  289. package/core/k-cli/node_modules/yaml/dist/schema/common/map.js +19 -0
  290. package/core/k-cli/node_modules/yaml/dist/schema/common/null.d.ts +4 -0
  291. package/core/k-cli/node_modules/yaml/dist/schema/common/null.js +17 -0
  292. package/core/k-cli/node_modules/yaml/dist/schema/common/seq.d.ts +2 -0
  293. package/core/k-cli/node_modules/yaml/dist/schema/common/seq.js +19 -0
  294. package/core/k-cli/node_modules/yaml/dist/schema/common/string.d.ts +2 -0
  295. package/core/k-cli/node_modules/yaml/dist/schema/common/string.js +16 -0
  296. package/core/k-cli/node_modules/yaml/dist/schema/core/bool.d.ts +4 -0
  297. package/core/k-cli/node_modules/yaml/dist/schema/core/bool.js +21 -0
  298. package/core/k-cli/node_modules/yaml/dist/schema/core/float.d.ts +4 -0
  299. package/core/k-cli/node_modules/yaml/dist/schema/core/float.js +47 -0
  300. package/core/k-cli/node_modules/yaml/dist/schema/core/int.d.ts +4 -0
  301. package/core/k-cli/node_modules/yaml/dist/schema/core/int.js +42 -0
  302. package/core/k-cli/node_modules/yaml/dist/schema/core/schema.d.ts +1 -0
  303. package/core/k-cli/node_modules/yaml/dist/schema/core/schema.js +25 -0
  304. package/core/k-cli/node_modules/yaml/dist/schema/json/schema.d.ts +2 -0
  305. package/core/k-cli/node_modules/yaml/dist/schema/json/schema.js +64 -0
  306. package/core/k-cli/node_modules/yaml/dist/schema/json-schema.d.ts +69 -0
  307. package/core/k-cli/node_modules/yaml/dist/schema/tags.d.ts +48 -0
  308. package/core/k-cli/node_modules/yaml/dist/schema/tags.js +99 -0
  309. package/core/k-cli/node_modules/yaml/dist/schema/types.d.ts +92 -0
  310. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/binary.d.ts +2 -0
  311. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/binary.js +70 -0
  312. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/bool.d.ts +7 -0
  313. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/bool.js +29 -0
  314. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/float.d.ts +4 -0
  315. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/float.js +50 -0
  316. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/int.d.ts +5 -0
  317. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/int.js +76 -0
  318. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/merge.d.ts +9 -0
  319. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/merge.js +68 -0
  320. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/omap.d.ts +22 -0
  321. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/omap.js +77 -0
  322. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/pairs.d.ts +10 -0
  323. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/pairs.js +82 -0
  324. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/schema.d.ts +1 -0
  325. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/schema.js +41 -0
  326. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/set.d.ts +28 -0
  327. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/set.js +96 -0
  328. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/timestamp.d.ts +6 -0
  329. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/timestamp.js +105 -0
  330. package/core/k-cli/node_modules/yaml/dist/stringify/foldFlowLines.d.ts +34 -0
  331. package/core/k-cli/node_modules/yaml/dist/stringify/foldFlowLines.js +151 -0
  332. package/core/k-cli/node_modules/yaml/dist/stringify/stringify.d.ts +21 -0
  333. package/core/k-cli/node_modules/yaml/dist/stringify/stringify.js +132 -0
  334. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyCollection.d.ts +17 -0
  335. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyCollection.js +155 -0
  336. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyComment.d.ts +10 -0
  337. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyComment.js +24 -0
  338. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyDocument.d.ts +4 -0
  339. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyDocument.js +87 -0
  340. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyNumber.d.ts +2 -0
  341. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyNumber.js +26 -0
  342. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyPair.d.ts +3 -0
  343. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyPair.js +152 -0
  344. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyString.d.ts +9 -0
  345. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyString.js +338 -0
  346. package/core/k-cli/node_modules/yaml/dist/test-events.d.ts +4 -0
  347. package/core/k-cli/node_modules/yaml/dist/test-events.js +134 -0
  348. package/core/k-cli/node_modules/yaml/dist/util.d.ts +16 -0
  349. package/core/k-cli/node_modules/yaml/dist/util.js +28 -0
  350. package/core/k-cli/node_modules/yaml/dist/visit.d.ts +102 -0
  351. package/core/k-cli/node_modules/yaml/dist/visit.js +236 -0
  352. package/core/k-cli/node_modules/yaml/package.json +97 -0
  353. package/core/k-cli/node_modules/yaml/util.js +2 -0
  354. package/core/plugins/kenaz/commands/Ansuz.md +19 -30
  355. package/core/plugins/kenaz/hooks/hooks.json +1 -5
  356. package/core/plugins/kenaz/hooks/kenaz-hook.js +0 -4
  357. package/core/plugins/kenaz/scripts/kenaz.test.js +1 -1
  358. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/charts.csv +26 -26
  359. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/colors.csv +97 -97
  360. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/icons.csv +101 -101
  361. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/landing.csv +31 -31
  362. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/products.csv +96 -96
  363. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/react-performance.csv +45 -45
  364. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -54
  365. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -53
  366. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -56
  367. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -53
  368. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -53
  369. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -51
  370. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -59
  371. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -52
  372. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/react.csv +54 -54
  373. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -61
  374. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -54
  375. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -51
  376. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -50
  377. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/styles.csv +68 -68
  378. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/typography.csv +57 -57
  379. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -101
  380. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/ux-guidelines.csv +99 -99
  381. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/web-interface.csv +31 -31
  382. package/core/plugins/kenaz/skills/ui-ux-pro-max/scripts/core.py +253 -253
  383. package/core/plugins/kenaz/skills/ui-ux-pro-max/scripts/design_system.py +1067 -1067
  384. package/core/plugins/kenaz/skills/ui-ux-pro-max/scripts/search.py +106 -106
  385. package/lib/cli.js +36 -2
  386. package/lib/cli.test.js +24 -0
  387. package/lib/dashboard.js +10 -0
  388. package/package.json +6 -6
  389. package/core/.specialist/tools/__tests__/task-1863-boot-lease-reuse.test.js +0 -99
  390. package/core/.specialist/tools/__tests__/task-1977-banner-cap.test.js +0 -129
@@ -1,393 +1,393 @@
1
- diff --git a/src/include/storage/shadow_file.h b/src/include/storage/shadow_file.h
2
- index 6b186b167..a968b3af6 100644
3
- --- a/src/include/storage/shadow_file.h
4
- +++ b/src/include/storage/shadow_file.h
5
- @@ -1,5 +1,9 @@
6
- #pragma once
7
-
8
- +#include <atomic>
9
- +#include <functional>
10
- +#include <shared_mutex>
11
- +
12
- #include "common/types/uuid.h"
13
- #include "storage/file_handle.h"
14
-
15
- @@ -24,11 +28,17 @@ struct ShadowFileHeader {
16
- static_assert(std::is_trivially_copyable_v<ShadowFileHeader>);
17
-
18
- class BufferManager;
19
- -// NOTE: This class is NOT thread-safe for now, as we are not checkpointing in parallel yet.
20
- +// NOTE: Only the checkpointing thread may create, publish or clear shadow pages. Read
21
- +// transactions running concurrently with a checkpoint may only use readShadowVersionIfExists.
22
- class ShadowFile {
23
- public:
24
- ShadowFile(BufferManager& bm, common::VirtualFileSystem* vfs, const std::string& databasePath);
25
-
26
- + // Mutex protocol: hasShadowPage, getShadowPage, clearShadowPage, createShadowPage and
27
- + // publishShadowPage are for the checkpointing thread only. It is the only writer of
28
- + // shadowPagesMap, so it may read the map without taking mtx; the functions that modify the
29
- + // map take mtx exclusively. Read transactions running concurrently with a checkpoint must not
30
- + // call these and must use readShadowVersionIfExists, which takes mtx shared.
31
- // TODO(Guodong): Remove originalFile param.
32
- bool hasShadowPage(common::file_idx_t originalFile, common::page_idx_t originalPage) const {
33
- return shadowPagesMap.contains(originalFile) &&
34
- @@ -37,8 +47,21 @@ class ShadowFile {
35
- void clearShadowPage(common::file_idx_t originalFile, common::page_idx_t originalPage);
36
- common::page_idx_t getShadowPage(common::file_idx_t originalFile,
37
- common::page_idx_t originalPage) const;
38
- - common::page_idx_t getOrCreateShadowPage(common::file_idx_t originalFile,
39
- + // Adds a new shadow page for originalPage. The page is not visible to
40
- + // readShadowVersionIfExists (nor hasShadowPage) until publishShadowPage is called, so the
41
- + // caller can fill it with the contents of the original page first. Must not be called while
42
- + // a published shadow page of a column data page is pinned (see mtx).
43
- + common::page_idx_t createShadowPage(common::file_idx_t originalFile,
44
- common::page_idx_t originalPage);
45
- + void publishShadowPage(common::file_idx_t originalFile, common::page_idx_t originalPage,
46
- + common::page_idx_t shadowPageIdx);
47
- + // Reads the shadow version of a page if the current checkpoint has one, and returns false
48
- + // without calling readOp otherwise. Unlike the functions above, this is safe to call from
49
- + // read transactions running concurrently with a checkpoint: a checkpoint publishes the new
50
- + // metadata of in-place updated column chunks before the shadow pages are applied to the data
51
- + // file, so those readers must see the shadow version of such pages.
52
- + bool readShadowVersionIfExists(common::file_idx_t originalFile, common::page_idx_t originalPage,
53
- + const std::function<void(uint8_t*)>& readOp) const;
54
-
55
- FileHandle& getShadowingFH() const { return *shadowingFH; }
56
-
57
- @@ -83,6 +106,15 @@ class ShadowFile {
58
- std::unordered_map<common::page_idx_t, common::page_idx_t>>
59
- shadowPagesMap;
60
- std::vector<ShadowPageRecord> shadowPageRecords;
61
- + // Protects shadowPagesMap against concurrent readers (see readShadowVersionIfExists). Only
62
- + // the checkpointing thread modifies the map.
63
- + // Readers hold mtx shared while spinning in optimisticRead on a locked shadow page of a page
64
- + // they reach through ColumnReadWriter::readFromPage (column data pages). The checkpointing
65
- + // thread therefore must not hold such a published shadow page pinned while it takes mtx
66
- + // exclusively (create, publish, clear or reset), or it would deadlock with those readers.
67
- + mutable std::shared_mutex mtx;
68
- + // Fast path for readers: false when there is no shadow page.
69
- + std::atomic<bool> hasShadowPages{false};
70
- };
71
-
72
- } // namespace storage
73
- diff --git a/src/storage/shadow_file.cpp b/src/storage/shadow_file.cpp
74
- index 9811166c6..8984d1eb8 100644
75
- --- a/src/storage/shadow_file.cpp
76
- +++ b/src/storage/shadow_file.cpp
77
- @@ -45,6 +45,7 @@ void ShadowFile::setDatabasePath(const std::string& databasePath_) {
78
- }
79
-
80
- void ShadowFile::clearShadowPage(file_idx_t originalFile, page_idx_t originalPage) {
81
- + std::unique_lock lck{mtx};
82
- if (hasShadowPage(originalFile, originalPage)) {
83
- shadowPagesMap.at(originalFile).erase(originalPage);
84
- if (shadowPagesMap.at(originalFile).empty()) {
85
- @@ -53,20 +54,44 @@ void ShadowFile::clearShadowPage(file_idx_t originalFile, page_idx_t originalPag
86
- }
87
- }
88
-
89
- -page_idx_t ShadowFile::getOrCreateShadowPage(file_idx_t originalFile, page_idx_t originalPage) {
90
- - if (hasShadowPage(originalFile, originalPage)) {
91
- - return shadowPagesMap[originalFile][originalPage];
92
- - }
93
- +page_idx_t ShadowFile::createShadowPage(file_idx_t originalFile, page_idx_t originalPage) {
94
- + DASSERT(!hasShadowPage(originalFile, originalPage));
95
- + std::unique_lock lck{mtx};
96
- const auto shadowPageIdx = getOrCreateShadowingFH()->addNewPage();
97
- - shadowPagesMap[originalFile][originalPage] = shadowPageIdx;
98
- + // Records must stay in the same order as the shadow pages (see applyShadowPages). Readers
99
- + // only use shadowPagesMap, so the page is not visible to them until it is published.
100
- shadowPageRecords.push_back({originalFile, originalPage});
101
- return shadowPageIdx;
102
- }
103
-
104
- +void ShadowFile::publishShadowPage(file_idx_t originalFile, page_idx_t originalPage,
105
- + page_idx_t shadowPageIdx) {
106
- + std::unique_lock lck{mtx};
107
- + shadowPagesMap[originalFile][originalPage] = shadowPageIdx;
108
- + // Pairs with the acquire load in readShadowVersionIfExists.
109
- + hasShadowPages.store(true, std::memory_order_release);
110
- +}
111
- +
112
- page_idx_t ShadowFile::getShadowPage(file_idx_t originalFile, page_idx_t originalPage) const {
113
- DASSERT(hasShadowPage(originalFile, originalPage));
114
- return shadowPagesMap.at(originalFile).at(originalPage);
115
- }
116
- +
117
- +bool ShadowFile::readShadowVersionIfExists(file_idx_t originalFile, page_idx_t originalPage,
118
- + const std::function<void(uint8_t*)>& readOp) const {
119
- + if (!hasShadowPages.load(std::memory_order_acquire)) {
120
- + return false;
121
- + }
122
- + std::shared_lock lck{mtx};
123
- + if (!hasShadowPage(originalFile, originalPage)) {
124
- + return false;
125
- + }
126
- + // Optimistic reads let concurrent readers share the page and retry if the checkpointer is
127
- + // updating it at the same time.
128
- + shadowingFH->optimisticReadPage(shadowPagesMap.at(originalFile).at(originalPage), readOp);
129
- + return true;
130
- +}
131
- +
132
- void ShadowFile::applyShadowPages(StorageManager& storageManager, ClientContext& context) const {
133
- const auto pageBuffer = std::make_unique<uint8_t[]>(LBUG_PAGE_SIZE);
134
- page_idx_t shadowPageIdx = 1; // Skip header page.
135
- @@ -191,6 +216,8 @@ void ShadowFile::flushAll(main::ClientContext& context) const {
136
-
137
- void ShadowFile::clear(BufferManager& bm) {
138
- DASSERT(shadowingFH);
139
- + // Concurrent readers may be reading shadow pages (see readShadowVersionIfExists).
140
- + std::unique_lock lck{mtx};
141
- // Evict pages, truncate to zero pages (which also truncates the on-disk file), then unlink
142
- // the path and drop the stale fd so no leftover .shadow file exists at rest (a present
143
- // .shadow is treated as "checkpoint in progress" by recovery/startup probes). The
144
- @@ -203,6 +230,7 @@ void ShadowFile::clear(BufferManager& bm) {
145
- vfs->removeFileIfExists(shadowFilePath);
146
- shadowPagesMap.clear();
147
- shadowPageRecords.clear();
148
- + hasShadowPages.store(false, std::memory_order_relaxed);
149
- }
150
-
151
- void ShadowFile::reset() {
152
- @@ -218,6 +246,7 @@ void ShadowFile::reset() {
153
- if (shadowingFH == nullptr) {
154
- return;
155
- }
156
- + std::unique_lock lck{mtx};
157
- // If clear() already ran (the normal checkpoint flow calls both), the file was truncated,
158
- // unlinked and the fd dropped; just make sure the in-memory maps are empty.
159
- if (shadowingFH->getFileInfo() != nullptr) {
160
- @@ -228,6 +257,7 @@ void ShadowFile::reset() {
161
- }
162
- shadowPagesMap.clear();
163
- shadowPageRecords.clear();
164
- + hasShadowPages.store(false, std::memory_order_relaxed);
165
- }
166
-
167
- FileHandle* ShadowFile::getOrCreateShadowingFH() {
168
- diff --git a/src/storage/shadow_utils.cpp b/src/storage/shadow_utils.cpp
169
- index 572990231..57538f3ff 100644
170
- --- a/src/storage/shadow_utils.cpp
171
- +++ b/src/storage/shadow_utils.cpp
172
- @@ -12,14 +12,16 @@ namespace storage {
173
- ShadowPageAndFrame ShadowUtils::createShadowVersionIfNecessaryAndPinPage(page_idx_t originalPage,
174
- bool skipReadingOriginalPage, FileHandle& fileHandle, ShadowFile& shadowFile) {
175
- DASSERT(!fileHandle.isInMemoryMode());
176
- - const auto hasShadowPage = shadowFile.hasShadowPage(fileHandle.getFileIndex(), originalPage);
177
- - auto shadowPage = shadowFile.getOrCreateShadowPage(fileHandle.getFileIndex(), originalPage);
178
- + const auto fileIdx = fileHandle.getFileIndex();
179
- + page_idx_t shadowPage = INVALID_PAGE_IDX;
180
- uint8_t* shadowFrame = nullptr;
181
- try {
182
- - if (hasShadowPage) {
183
- + if (shadowFile.hasShadowPage(fileIdx, originalPage)) {
184
- + shadowPage = shadowFile.getShadowPage(fileIdx, originalPage);
185
- shadowFrame =
186
- shadowFile.getShadowingFH().pinPage(shadowPage, PageReadPolicy::READ_PAGE);
187
- } else {
188
- + shadowPage = shadowFile.createShadowPage(fileIdx, originalPage);
189
- shadowFrame =
190
- shadowFile.getShadowingFH().pinPage(shadowPage, PageReadPolicy::DONT_READ_PAGE);
191
- if (!skipReadingOriginalPage) {
192
- @@ -27,6 +29,11 @@ ShadowPageAndFrame ShadowUtils::createShadowVersionIfNecessaryAndPinPage(page_id
193
- memcpy(shadowFrame, frame, LBUG_PAGE_SIZE);
194
- });
195
- }
196
- + // Publish the shadow page only once it holds the original contents, since read
197
- + // transactions may read shadow pages concurrently (see
198
- + // ShadowFile::readShadowVersionIfExists). The page stays locked until it is unpinned,
199
- + // so concurrent optimistic readers wait for the caller's update to finish.
200
- + shadowFile.publishShadowPage(fileIdx, originalPage, shadowPage);
201
- }
202
- // The shadow page existing already does not mean that it's already dirty
203
- // It may have been flushed to disk to free memory and then read again
204
- diff --git a/src/storage/table/column_reader_writer.cpp b/src/storage/table/column_reader_writer.cpp
205
- index fb71533d3..3e768de2f 100644
206
- --- a/src/storage/table/column_reader_writer.cpp
207
- +++ b/src/storage/table/column_reader_writer.cpp
208
- @@ -8,6 +8,7 @@
209
- #include "common/vector/value_vector.h"
210
- #include "storage/compression/float_compression.h"
211
- #include "storage/file_handle.h"
212
- +#include "storage/shadow_file.h"
213
- #include "storage/shadow_utils.h"
214
- #include "storage/storage_utils.h"
215
- #include "storage/table/column_chunk_data.h"
216
- @@ -462,6 +463,12 @@ void ColumnReadWriter::readFromPage(page_idx_t pageIdx,
217
- if (pageIdx == INVALID_PAGE_IDX) {
218
- return readFunc(nullptr);
219
- }
220
- + // A checkpoint in progress may already have published metadata which refers to in-place
221
- + // updates that so far only exist in shadow pages.
222
- + if (shadowFile &&
223
- + shadowFile->readShadowVersionIfExists(dataFH->getFileIndex(), pageIdx, readFunc)) {
224
- + return;
225
- + }
226
- dataFH->optimisticReadPage(pageIdx, readFunc);
227
- }
228
-
229
- diff --git a/src/transaction/transaction_manager.cpp b/src/transaction/transaction_manager.cpp
230
- index 32e155213..48cbd8a2a 100644
231
- --- a/src/transaction/transaction_manager.cpp
232
- +++ b/src/transaction/transaction_manager.cpp
233
- @@ -303,8 +303,10 @@ void TransactionManager::tryCheckpoint(main::ClientContext& clientContext) {
234
- void TransactionManager::checkpointNoLock(main::ClientContext& clientContext) {
235
- QueryProgressScope progress{clientContext, 0.01};
236
- // We only need to wait for active write transactions to leave the system before
237
- - // checkpointing. Read transactions can continue safely because they use MVCC snapshot
238
- - // isolation and shadow pages are applied with per-page locking.
239
- + // checkpointing. Read transactions can continue because they use MVCC snapshot isolation.
240
- + // Column chunk metadata of in-place checkpointed data is published before the shadow pages
241
- + // are applied, so column reads resolve such in-flight pages through the shadow file (see
242
- + // ColumnReadWriter::readFromPage); applying shadow pages uses per-page locking.
243
- UniqLock writeGate;
244
- try {
245
- writeGate = stopNewWriteTransactionsAndWaitUntilAllWriteTransactionsLeave();
246
- diff --git a/test/transaction/checkpoint_test.cpp b/test/transaction/checkpoint_test.cpp
247
- index 0a9899dca..277756061 100644
248
- --- a/test/transaction/checkpoint_test.cpp
249
- +++ b/test/transaction/checkpoint_test.cpp
250
- @@ -2,14 +2,20 @@
251
- #include <chrono>
252
- #include <cstdlib>
253
- #include <fstream>
254
- +#include <functional>
255
- #include <future>
256
- #include <mutex>
257
- #include <thread>
258
- +#include <unordered_set>
259
-
260
- #include "api_test/private_api_test.h"
261
- +#include "catalog/catalog.h"
262
- +#include "catalog/catalog_entry/table_catalog_entry.h"
263
- #include "common/exception/runtime.h"
264
- #include "storage/checkpointer.h"
265
- #include "storage/storage_manager.h"
266
- +#include "storage/table/node_table.h"
267
- +#include "storage/table/string_chunk_data.h"
268
- #include "storage/wal/wal.h"
269
- #include "test_env.h"
270
- #include "transaction/transaction_manager.h"
271
- @@ -308,6 +314,122 @@ TEST_F(FlakyCheckpointerTest, ShadowFileDatabaseIDMismatchCorruptedDB) {
272
- EXPECT_THROW(createDBAndConn(), InternalException);
273
- }
274
-
275
- +// A checkpoint publishes the new chunk metadata of a node group during its storage phase, but
276
- +// in-place updates to existing pages are only written to the shadow file until the shadow pages
277
- +// are applied at the end of the checkpoint. Read-only transactions are not blocked by a
278
- +// checkpoint, so a scan that starts in between must not observe the new metadata together with
279
- +// the old page contents (e.g. appended string dictionary offsets that are not on disk yet).
280
- +class CheckpointerWithReadBeforeApplyingShadowPages final : public Checkpointer {
281
- +public:
282
- + CheckpointerWithReadBeforeApplyingShadowPages(main::ClientContext& clientContext,
283
- + std::function<void()> readFunc)
284
- + : Checkpointer(clientContext), readFunc(std::move(readFunc)) {}
285
- +
286
- + void logCheckpointAndApplyShadowPages(bool walRotated) override {
287
- + readFunc();
288
- + Checkpointer::logCheckpointAndApplyShadowPages(walRotated);
289
- + }
290
- +
291
- +private:
292
- + std::function<void()> readFunc;
293
- +};
294
- +
295
- +#ifndef __SINGLE_THREADED__
296
- +TEST_F(FlakyCheckpointerTest, ReadBeforeShadowPagesAreAppliedSeesCommittedStrings) {
297
- + if (inMemMode || systemConfig->checkpointThreshold == 0) {
298
- + GTEST_SKIP();
299
- + }
300
- + conn->query("CALL force_checkpoint_on_close=false;");
301
- + conn->query("CALL auto_checkpoint=false;");
302
- + ASSERT_TRUE(
303
- + conn->query("CREATE NODE TABLE test(id INT64 PRIMARY KEY, name STRING);")->isSuccess());
304
- + // Strings longer than the inline limit, few enough that the appended values below fit in
305
- + // the pages already allocated to the dictionary, so the checkpoint updates them in place.
306
- + auto getName = [](uint64_t i) { return std::format("persistent string value {:04}", i); };
307
- + auto insertRows = [&](uint64_t start, uint64_t end) {
308
- + for (auto i = start; i < end; i++) {
309
- + auto res =
310
- + conn->query(std::format("CREATE (:test {{id: {}, name: '{}'}});", i, getName(i)));
311
- + ASSERT_TRUE(res->isSuccess()) << res->getErrorMessage();
312
- + }
313
- + };
314
- + constexpr uint64_t numInitialRows = 20;
315
- + constexpr uint64_t numRows = 25;
316
- + insertRows(0, numInitialRows);
317
- + ASSERT_TRUE(conn->query("CHECKPOINT;")->isSuccess());
318
- + insertRows(numInitialRows, numRows);
319
- +
320
- + // Pages of the persistent `name` column chunk (dictionary offsets, string data and indices).
321
- + // An in-place checkpoint of the column updates these pages through the shadow file, while an
322
- + // out-of-place checkpoint writes new pages and leaves these untouched.
323
- + auto context = getClientContext(*conn);
324
- + auto storageManager = StorageManager::Get(*context);
325
- + const auto tableEntry = catalog::Catalog::Get(*context)->getTableCatalogEntry(
326
- + &DUMMY_CHECKPOINT_TRANSACTION, "test");
327
- + auto& nodeTable = storageManager->getTable(tableEntry->getTableID())->cast<NodeTable>();
328
- + const auto* persistentGroup = nodeTable.getNodeGroup(0)->getChunkedNodeGroup(0);
329
- + ASSERT_EQ(persistentGroup->getResidencyState(), ResidencyState::ON_DISK);
330
- + std::unordered_set<page_idx_t> namePages;
331
- + for (const auto* segment :
332
- + persistentGroup->getColumnChunk(tableEntry->getColumnID("name")).getSegments()) {
333
- + const auto& stringChunk = segment->cast<StringChunkData>();
334
- + for (const auto* chunk :
335
- + std::initializer_list<const ColumnChunkData*>{stringChunk.getIndexColumnChunk(),
336
- + stringChunk.getDictionaryChunk().getOffsetChunk(),
337
- + stringChunk.getDictionaryChunk().getStringDataChunk()}) {
338
- + const auto& metadata = chunk->getMetadata();
339
- + for (auto i = 0u; i < metadata.getNumPages(); i++) {
340
- + namePages.insert(metadata.getStartPageIdx() + i);
341
- + }
342
- + }
343
- + }
344
- + ASSERT_FALSE(namePages.empty());
345
- +
346
- + bool readRan = false;
347
- + uint64_t numShadowedNamePages = 0;
348
- + std::string readError;
349
- + std::vector<std::string> readNames;
350
- + auto readFunc = [&]() {
351
- + // Make sure the `name` column was checkpointed in place, i.e. its updated pages are still
352
- + // pending in the shadow file; otherwise the read below would not read shadow pages.
353
- + auto& shadowFile = storageManager->getShadowFile();
354
- + for (const auto pageIdx : namePages) {
355
- + if (shadowFile.hasShadowPage(storageManager->getDataFH()->getFileIndex(), pageIdx)) {
356
- + numShadowedNamePages++;
357
- + }
358
- + }
359
- + // Run on a separate thread with its own connection, like a concurrent reader would.
360
- + std::thread reader([&]() {
361
- + auto readConn = std::make_unique<main::Connection>(database.get());
362
- + auto res = readConn->query("MATCH (t:test) RETURN t.name ORDER BY t.id;");
363
- + readRan = true;
364
- + if (!res->isSuccess()) {
365
- + readError = res->getErrorMessage();
366
- + return;
367
- + }
368
- + while (res->hasNext()) {
369
- + readNames.push_back(res->getNext()->getValue(0)->getValue<std::string>());
370
- + }
371
- + });
372
- + reader.join();
373
- + };
374
- + FlakyCheckpointer checkpointer([&](main::ClientContext& clientContext) {
375
- + return std::make_unique<CheckpointerWithReadBeforeApplyingShadowPages>(clientContext,
376
- + readFunc);
377
- + });
378
- + checkpointer.setCheckpointer(*context);
379
- + auto res = conn->query("CHECKPOINT;");
380
- + ASSERT_TRUE(res->isSuccess()) << res->getErrorMessage();
381
- + ASSERT_TRUE(readRan);
382
- + ASSERT_GT(numShadowedNamePages, 0u);
383
- + ASSERT_TRUE(readError.empty()) << readError;
384
- + ASSERT_EQ(readNames.size(), numRows);
385
- + for (auto i = 0u; i < numRows; i++) {
386
- + EXPECT_EQ(readNames[i], getName(i));
387
- + }
388
- +}
389
- +#endif // __SINGLE_THREADED__
390
- +
391
- // ─────────────────────────────────────────────────────────────────────────────
392
- // ReviewFixesTest
393
- // Targeted regression tests for the three fixes made in response to adsharma's
1
+ diff --git a/src/include/storage/shadow_file.h b/src/include/storage/shadow_file.h
2
+ index 6b186b167..a968b3af6 100644
3
+ --- a/src/include/storage/shadow_file.h
4
+ +++ b/src/include/storage/shadow_file.h
5
+ @@ -1,5 +1,9 @@
6
+ #pragma once
7
+
8
+ +#include <atomic>
9
+ +#include <functional>
10
+ +#include <shared_mutex>
11
+ +
12
+ #include "common/types/uuid.h"
13
+ #include "storage/file_handle.h"
14
+
15
+ @@ -24,11 +28,17 @@ struct ShadowFileHeader {
16
+ static_assert(std::is_trivially_copyable_v<ShadowFileHeader>);
17
+
18
+ class BufferManager;
19
+ -// NOTE: This class is NOT thread-safe for now, as we are not checkpointing in parallel yet.
20
+ +// NOTE: Only the checkpointing thread may create, publish or clear shadow pages. Read
21
+ +// transactions running concurrently with a checkpoint may only use readShadowVersionIfExists.
22
+ class ShadowFile {
23
+ public:
24
+ ShadowFile(BufferManager& bm, common::VirtualFileSystem* vfs, const std::string& databasePath);
25
+
26
+ + // Mutex protocol: hasShadowPage, getShadowPage, clearShadowPage, createShadowPage and
27
+ + // publishShadowPage are for the checkpointing thread only. It is the only writer of
28
+ + // shadowPagesMap, so it may read the map without taking mtx; the functions that modify the
29
+ + // map take mtx exclusively. Read transactions running concurrently with a checkpoint must not
30
+ + // call these and must use readShadowVersionIfExists, which takes mtx shared.
31
+ // TODO(Guodong): Remove originalFile param.
32
+ bool hasShadowPage(common::file_idx_t originalFile, common::page_idx_t originalPage) const {
33
+ return shadowPagesMap.contains(originalFile) &&
34
+ @@ -37,8 +47,21 @@ class ShadowFile {
35
+ void clearShadowPage(common::file_idx_t originalFile, common::page_idx_t originalPage);
36
+ common::page_idx_t getShadowPage(common::file_idx_t originalFile,
37
+ common::page_idx_t originalPage) const;
38
+ - common::page_idx_t getOrCreateShadowPage(common::file_idx_t originalFile,
39
+ + // Adds a new shadow page for originalPage. The page is not visible to
40
+ + // readShadowVersionIfExists (nor hasShadowPage) until publishShadowPage is called, so the
41
+ + // caller can fill it with the contents of the original page first. Must not be called while
42
+ + // a published shadow page of a column data page is pinned (see mtx).
43
+ + common::page_idx_t createShadowPage(common::file_idx_t originalFile,
44
+ common::page_idx_t originalPage);
45
+ + void publishShadowPage(common::file_idx_t originalFile, common::page_idx_t originalPage,
46
+ + common::page_idx_t shadowPageIdx);
47
+ + // Reads the shadow version of a page if the current checkpoint has one, and returns false
48
+ + // without calling readOp otherwise. Unlike the functions above, this is safe to call from
49
+ + // read transactions running concurrently with a checkpoint: a checkpoint publishes the new
50
+ + // metadata of in-place updated column chunks before the shadow pages are applied to the data
51
+ + // file, so those readers must see the shadow version of such pages.
52
+ + bool readShadowVersionIfExists(common::file_idx_t originalFile, common::page_idx_t originalPage,
53
+ + const std::function<void(uint8_t*)>& readOp) const;
54
+
55
+ FileHandle& getShadowingFH() const { return *shadowingFH; }
56
+
57
+ @@ -83,6 +106,15 @@ class ShadowFile {
58
+ std::unordered_map<common::page_idx_t, common::page_idx_t>>
59
+ shadowPagesMap;
60
+ std::vector<ShadowPageRecord> shadowPageRecords;
61
+ + // Protects shadowPagesMap against concurrent readers (see readShadowVersionIfExists). Only
62
+ + // the checkpointing thread modifies the map.
63
+ + // Readers hold mtx shared while spinning in optimisticRead on a locked shadow page of a page
64
+ + // they reach through ColumnReadWriter::readFromPage (column data pages). The checkpointing
65
+ + // thread therefore must not hold such a published shadow page pinned while it takes mtx
66
+ + // exclusively (create, publish, clear or reset), or it would deadlock with those readers.
67
+ + mutable std::shared_mutex mtx;
68
+ + // Fast path for readers: false when there is no shadow page.
69
+ + std::atomic<bool> hasShadowPages{false};
70
+ };
71
+
72
+ } // namespace storage
73
+ diff --git a/src/storage/shadow_file.cpp b/src/storage/shadow_file.cpp
74
+ index 9811166c6..8984d1eb8 100644
75
+ --- a/src/storage/shadow_file.cpp
76
+ +++ b/src/storage/shadow_file.cpp
77
+ @@ -45,6 +45,7 @@ void ShadowFile::setDatabasePath(const std::string& databasePath_) {
78
+ }
79
+
80
+ void ShadowFile::clearShadowPage(file_idx_t originalFile, page_idx_t originalPage) {
81
+ + std::unique_lock lck{mtx};
82
+ if (hasShadowPage(originalFile, originalPage)) {
83
+ shadowPagesMap.at(originalFile).erase(originalPage);
84
+ if (shadowPagesMap.at(originalFile).empty()) {
85
+ @@ -53,20 +54,44 @@ void ShadowFile::clearShadowPage(file_idx_t originalFile, page_idx_t originalPag
86
+ }
87
+ }
88
+
89
+ -page_idx_t ShadowFile::getOrCreateShadowPage(file_idx_t originalFile, page_idx_t originalPage) {
90
+ - if (hasShadowPage(originalFile, originalPage)) {
91
+ - return shadowPagesMap[originalFile][originalPage];
92
+ - }
93
+ +page_idx_t ShadowFile::createShadowPage(file_idx_t originalFile, page_idx_t originalPage) {
94
+ + DASSERT(!hasShadowPage(originalFile, originalPage));
95
+ + std::unique_lock lck{mtx};
96
+ const auto shadowPageIdx = getOrCreateShadowingFH()->addNewPage();
97
+ - shadowPagesMap[originalFile][originalPage] = shadowPageIdx;
98
+ + // Records must stay in the same order as the shadow pages (see applyShadowPages). Readers
99
+ + // only use shadowPagesMap, so the page is not visible to them until it is published.
100
+ shadowPageRecords.push_back({originalFile, originalPage});
101
+ return shadowPageIdx;
102
+ }
103
+
104
+ +void ShadowFile::publishShadowPage(file_idx_t originalFile, page_idx_t originalPage,
105
+ + page_idx_t shadowPageIdx) {
106
+ + std::unique_lock lck{mtx};
107
+ + shadowPagesMap[originalFile][originalPage] = shadowPageIdx;
108
+ + // Pairs with the acquire load in readShadowVersionIfExists.
109
+ + hasShadowPages.store(true, std::memory_order_release);
110
+ +}
111
+ +
112
+ page_idx_t ShadowFile::getShadowPage(file_idx_t originalFile, page_idx_t originalPage) const {
113
+ DASSERT(hasShadowPage(originalFile, originalPage));
114
+ return shadowPagesMap.at(originalFile).at(originalPage);
115
+ }
116
+ +
117
+ +bool ShadowFile::readShadowVersionIfExists(file_idx_t originalFile, page_idx_t originalPage,
118
+ + const std::function<void(uint8_t*)>& readOp) const {
119
+ + if (!hasShadowPages.load(std::memory_order_acquire)) {
120
+ + return false;
121
+ + }
122
+ + std::shared_lock lck{mtx};
123
+ + if (!hasShadowPage(originalFile, originalPage)) {
124
+ + return false;
125
+ + }
126
+ + // Optimistic reads let concurrent readers share the page and retry if the checkpointer is
127
+ + // updating it at the same time.
128
+ + shadowingFH->optimisticReadPage(shadowPagesMap.at(originalFile).at(originalPage), readOp);
129
+ + return true;
130
+ +}
131
+ +
132
+ void ShadowFile::applyShadowPages(StorageManager& storageManager, ClientContext& context) const {
133
+ const auto pageBuffer = std::make_unique<uint8_t[]>(LBUG_PAGE_SIZE);
134
+ page_idx_t shadowPageIdx = 1; // Skip header page.
135
+ @@ -191,6 +216,8 @@ void ShadowFile::flushAll(main::ClientContext& context) const {
136
+
137
+ void ShadowFile::clear(BufferManager& bm) {
138
+ DASSERT(shadowingFH);
139
+ + // Concurrent readers may be reading shadow pages (see readShadowVersionIfExists).
140
+ + std::unique_lock lck{mtx};
141
+ // Evict pages, truncate to zero pages (which also truncates the on-disk file), then unlink
142
+ // the path and drop the stale fd so no leftover .shadow file exists at rest (a present
143
+ // .shadow is treated as "checkpoint in progress" by recovery/startup probes). The
144
+ @@ -203,6 +230,7 @@ void ShadowFile::clear(BufferManager& bm) {
145
+ vfs->removeFileIfExists(shadowFilePath);
146
+ shadowPagesMap.clear();
147
+ shadowPageRecords.clear();
148
+ + hasShadowPages.store(false, std::memory_order_relaxed);
149
+ }
150
+
151
+ void ShadowFile::reset() {
152
+ @@ -218,6 +246,7 @@ void ShadowFile::reset() {
153
+ if (shadowingFH == nullptr) {
154
+ return;
155
+ }
156
+ + std::unique_lock lck{mtx};
157
+ // If clear() already ran (the normal checkpoint flow calls both), the file was truncated,
158
+ // unlinked and the fd dropped; just make sure the in-memory maps are empty.
159
+ if (shadowingFH->getFileInfo() != nullptr) {
160
+ @@ -228,6 +257,7 @@ void ShadowFile::reset() {
161
+ }
162
+ shadowPagesMap.clear();
163
+ shadowPageRecords.clear();
164
+ + hasShadowPages.store(false, std::memory_order_relaxed);
165
+ }
166
+
167
+ FileHandle* ShadowFile::getOrCreateShadowingFH() {
168
+ diff --git a/src/storage/shadow_utils.cpp b/src/storage/shadow_utils.cpp
169
+ index 572990231..57538f3ff 100644
170
+ --- a/src/storage/shadow_utils.cpp
171
+ +++ b/src/storage/shadow_utils.cpp
172
+ @@ -12,14 +12,16 @@ namespace storage {
173
+ ShadowPageAndFrame ShadowUtils::createShadowVersionIfNecessaryAndPinPage(page_idx_t originalPage,
174
+ bool skipReadingOriginalPage, FileHandle& fileHandle, ShadowFile& shadowFile) {
175
+ DASSERT(!fileHandle.isInMemoryMode());
176
+ - const auto hasShadowPage = shadowFile.hasShadowPage(fileHandle.getFileIndex(), originalPage);
177
+ - auto shadowPage = shadowFile.getOrCreateShadowPage(fileHandle.getFileIndex(), originalPage);
178
+ + const auto fileIdx = fileHandle.getFileIndex();
179
+ + page_idx_t shadowPage = INVALID_PAGE_IDX;
180
+ uint8_t* shadowFrame = nullptr;
181
+ try {
182
+ - if (hasShadowPage) {
183
+ + if (shadowFile.hasShadowPage(fileIdx, originalPage)) {
184
+ + shadowPage = shadowFile.getShadowPage(fileIdx, originalPage);
185
+ shadowFrame =
186
+ shadowFile.getShadowingFH().pinPage(shadowPage, PageReadPolicy::READ_PAGE);
187
+ } else {
188
+ + shadowPage = shadowFile.createShadowPage(fileIdx, originalPage);
189
+ shadowFrame =
190
+ shadowFile.getShadowingFH().pinPage(shadowPage, PageReadPolicy::DONT_READ_PAGE);
191
+ if (!skipReadingOriginalPage) {
192
+ @@ -27,6 +29,11 @@ ShadowPageAndFrame ShadowUtils::createShadowVersionIfNecessaryAndPinPage(page_id
193
+ memcpy(shadowFrame, frame, LBUG_PAGE_SIZE);
194
+ });
195
+ }
196
+ + // Publish the shadow page only once it holds the original contents, since read
197
+ + // transactions may read shadow pages concurrently (see
198
+ + // ShadowFile::readShadowVersionIfExists). The page stays locked until it is unpinned,
199
+ + // so concurrent optimistic readers wait for the caller's update to finish.
200
+ + shadowFile.publishShadowPage(fileIdx, originalPage, shadowPage);
201
+ }
202
+ // The shadow page existing already does not mean that it's already dirty
203
+ // It may have been flushed to disk to free memory and then read again
204
+ diff --git a/src/storage/table/column_reader_writer.cpp b/src/storage/table/column_reader_writer.cpp
205
+ index fb71533d3..3e768de2f 100644
206
+ --- a/src/storage/table/column_reader_writer.cpp
207
+ +++ b/src/storage/table/column_reader_writer.cpp
208
+ @@ -8,6 +8,7 @@
209
+ #include "common/vector/value_vector.h"
210
+ #include "storage/compression/float_compression.h"
211
+ #include "storage/file_handle.h"
212
+ +#include "storage/shadow_file.h"
213
+ #include "storage/shadow_utils.h"
214
+ #include "storage/storage_utils.h"
215
+ #include "storage/table/column_chunk_data.h"
216
+ @@ -462,6 +463,12 @@ void ColumnReadWriter::readFromPage(page_idx_t pageIdx,
217
+ if (pageIdx == INVALID_PAGE_IDX) {
218
+ return readFunc(nullptr);
219
+ }
220
+ + // A checkpoint in progress may already have published metadata which refers to in-place
221
+ + // updates that so far only exist in shadow pages.
222
+ + if (shadowFile &&
223
+ + shadowFile->readShadowVersionIfExists(dataFH->getFileIndex(), pageIdx, readFunc)) {
224
+ + return;
225
+ + }
226
+ dataFH->optimisticReadPage(pageIdx, readFunc);
227
+ }
228
+
229
+ diff --git a/src/transaction/transaction_manager.cpp b/src/transaction/transaction_manager.cpp
230
+ index 32e155213..48cbd8a2a 100644
231
+ --- a/src/transaction/transaction_manager.cpp
232
+ +++ b/src/transaction/transaction_manager.cpp
233
+ @@ -303,8 +303,10 @@ void TransactionManager::tryCheckpoint(main::ClientContext& clientContext) {
234
+ void TransactionManager::checkpointNoLock(main::ClientContext& clientContext) {
235
+ QueryProgressScope progress{clientContext, 0.01};
236
+ // We only need to wait for active write transactions to leave the system before
237
+ - // checkpointing. Read transactions can continue safely because they use MVCC snapshot
238
+ - // isolation and shadow pages are applied with per-page locking.
239
+ + // checkpointing. Read transactions can continue because they use MVCC snapshot isolation.
240
+ + // Column chunk metadata of in-place checkpointed data is published before the shadow pages
241
+ + // are applied, so column reads resolve such in-flight pages through the shadow file (see
242
+ + // ColumnReadWriter::readFromPage); applying shadow pages uses per-page locking.
243
+ UniqLock writeGate;
244
+ try {
245
+ writeGate = stopNewWriteTransactionsAndWaitUntilAllWriteTransactionsLeave();
246
+ diff --git a/test/transaction/checkpoint_test.cpp b/test/transaction/checkpoint_test.cpp
247
+ index 0a9899dca..277756061 100644
248
+ --- a/test/transaction/checkpoint_test.cpp
249
+ +++ b/test/transaction/checkpoint_test.cpp
250
+ @@ -2,14 +2,20 @@
251
+ #include <chrono>
252
+ #include <cstdlib>
253
+ #include <fstream>
254
+ +#include <functional>
255
+ #include <future>
256
+ #include <mutex>
257
+ #include <thread>
258
+ +#include <unordered_set>
259
+
260
+ #include "api_test/private_api_test.h"
261
+ +#include "catalog/catalog.h"
262
+ +#include "catalog/catalog_entry/table_catalog_entry.h"
263
+ #include "common/exception/runtime.h"
264
+ #include "storage/checkpointer.h"
265
+ #include "storage/storage_manager.h"
266
+ +#include "storage/table/node_table.h"
267
+ +#include "storage/table/string_chunk_data.h"
268
+ #include "storage/wal/wal.h"
269
+ #include "test_env.h"
270
+ #include "transaction/transaction_manager.h"
271
+ @@ -308,6 +314,122 @@ TEST_F(FlakyCheckpointerTest, ShadowFileDatabaseIDMismatchCorruptedDB) {
272
+ EXPECT_THROW(createDBAndConn(), InternalException);
273
+ }
274
+
275
+ +// A checkpoint publishes the new chunk metadata of a node group during its storage phase, but
276
+ +// in-place updates to existing pages are only written to the shadow file until the shadow pages
277
+ +// are applied at the end of the checkpoint. Read-only transactions are not blocked by a
278
+ +// checkpoint, so a scan that starts in between must not observe the new metadata together with
279
+ +// the old page contents (e.g. appended string dictionary offsets that are not on disk yet).
280
+ +class CheckpointerWithReadBeforeApplyingShadowPages final : public Checkpointer {
281
+ +public:
282
+ + CheckpointerWithReadBeforeApplyingShadowPages(main::ClientContext& clientContext,
283
+ + std::function<void()> readFunc)
284
+ + : Checkpointer(clientContext), readFunc(std::move(readFunc)) {}
285
+ +
286
+ + void logCheckpointAndApplyShadowPages(bool walRotated) override {
287
+ + readFunc();
288
+ + Checkpointer::logCheckpointAndApplyShadowPages(walRotated);
289
+ + }
290
+ +
291
+ +private:
292
+ + std::function<void()> readFunc;
293
+ +};
294
+ +
295
+ +#ifndef __SINGLE_THREADED__
296
+ +TEST_F(FlakyCheckpointerTest, ReadBeforeShadowPagesAreAppliedSeesCommittedStrings) {
297
+ + if (inMemMode || systemConfig->checkpointThreshold == 0) {
298
+ + GTEST_SKIP();
299
+ + }
300
+ + conn->query("CALL force_checkpoint_on_close=false;");
301
+ + conn->query("CALL auto_checkpoint=false;");
302
+ + ASSERT_TRUE(
303
+ + conn->query("CREATE NODE TABLE test(id INT64 PRIMARY KEY, name STRING);")->isSuccess());
304
+ + // Strings longer than the inline limit, few enough that the appended values below fit in
305
+ + // the pages already allocated to the dictionary, so the checkpoint updates them in place.
306
+ + auto getName = [](uint64_t i) { return std::format("persistent string value {:04}", i); };
307
+ + auto insertRows = [&](uint64_t start, uint64_t end) {
308
+ + for (auto i = start; i < end; i++) {
309
+ + auto res =
310
+ + conn->query(std::format("CREATE (:test {{id: {}, name: '{}'}});", i, getName(i)));
311
+ + ASSERT_TRUE(res->isSuccess()) << res->getErrorMessage();
312
+ + }
313
+ + };
314
+ + constexpr uint64_t numInitialRows = 20;
315
+ + constexpr uint64_t numRows = 25;
316
+ + insertRows(0, numInitialRows);
317
+ + ASSERT_TRUE(conn->query("CHECKPOINT;")->isSuccess());
318
+ + insertRows(numInitialRows, numRows);
319
+ +
320
+ + // Pages of the persistent `name` column chunk (dictionary offsets, string data and indices).
321
+ + // An in-place checkpoint of the column updates these pages through the shadow file, while an
322
+ + // out-of-place checkpoint writes new pages and leaves these untouched.
323
+ + auto context = getClientContext(*conn);
324
+ + auto storageManager = StorageManager::Get(*context);
325
+ + const auto tableEntry = catalog::Catalog::Get(*context)->getTableCatalogEntry(
326
+ + &DUMMY_CHECKPOINT_TRANSACTION, "test");
327
+ + auto& nodeTable = storageManager->getTable(tableEntry->getTableID())->cast<NodeTable>();
328
+ + const auto* persistentGroup = nodeTable.getNodeGroup(0)->getChunkedNodeGroup(0);
329
+ + ASSERT_EQ(persistentGroup->getResidencyState(), ResidencyState::ON_DISK);
330
+ + std::unordered_set<page_idx_t> namePages;
331
+ + for (const auto* segment :
332
+ + persistentGroup->getColumnChunk(tableEntry->getColumnID("name")).getSegments()) {
333
+ + const auto& stringChunk = segment->cast<StringChunkData>();
334
+ + for (const auto* chunk :
335
+ + std::initializer_list<const ColumnChunkData*>{stringChunk.getIndexColumnChunk(),
336
+ + stringChunk.getDictionaryChunk().getOffsetChunk(),
337
+ + stringChunk.getDictionaryChunk().getStringDataChunk()}) {
338
+ + const auto& metadata = chunk->getMetadata();
339
+ + for (auto i = 0u; i < metadata.getNumPages(); i++) {
340
+ + namePages.insert(metadata.getStartPageIdx() + i);
341
+ + }
342
+ + }
343
+ + }
344
+ + ASSERT_FALSE(namePages.empty());
345
+ +
346
+ + bool readRan = false;
347
+ + uint64_t numShadowedNamePages = 0;
348
+ + std::string readError;
349
+ + std::vector<std::string> readNames;
350
+ + auto readFunc = [&]() {
351
+ + // Make sure the `name` column was checkpointed in place, i.e. its updated pages are still
352
+ + // pending in the shadow file; otherwise the read below would not read shadow pages.
353
+ + auto& shadowFile = storageManager->getShadowFile();
354
+ + for (const auto pageIdx : namePages) {
355
+ + if (shadowFile.hasShadowPage(storageManager->getDataFH()->getFileIndex(), pageIdx)) {
356
+ + numShadowedNamePages++;
357
+ + }
358
+ + }
359
+ + // Run on a separate thread with its own connection, like a concurrent reader would.
360
+ + std::thread reader([&]() {
361
+ + auto readConn = std::make_unique<main::Connection>(database.get());
362
+ + auto res = readConn->query("MATCH (t:test) RETURN t.name ORDER BY t.id;");
363
+ + readRan = true;
364
+ + if (!res->isSuccess()) {
365
+ + readError = res->getErrorMessage();
366
+ + return;
367
+ + }
368
+ + while (res->hasNext()) {
369
+ + readNames.push_back(res->getNext()->getValue(0)->getValue<std::string>());
370
+ + }
371
+ + });
372
+ + reader.join();
373
+ + };
374
+ + FlakyCheckpointer checkpointer([&](main::ClientContext& clientContext) {
375
+ + return std::make_unique<CheckpointerWithReadBeforeApplyingShadowPages>(clientContext,
376
+ + readFunc);
377
+ + });
378
+ + checkpointer.setCheckpointer(*context);
379
+ + auto res = conn->query("CHECKPOINT;");
380
+ + ASSERT_TRUE(res->isSuccess()) << res->getErrorMessage();
381
+ + ASSERT_TRUE(readRan);
382
+ + ASSERT_GT(numShadowedNamePages, 0u);
383
+ + ASSERT_TRUE(readError.empty()) << readError;
384
+ + ASSERT_EQ(readNames.size(), numRows);
385
+ + for (auto i = 0u; i < numRows; i++) {
386
+ + EXPECT_EQ(readNames[i], getName(i));
387
+ + }
388
+ +}
389
+ +#endif // __SINGLE_THREADED__
390
+ +
391
+ // ─────────────────────────────────────────────────────────────────────────────
392
+ // ReviewFixesTest
393
+ // Targeted regression tests for the three fixes made in response to adsharma's