canopycms 0.0.68-int.98 → 0.1.0

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 (688) hide show
  1. package/CHANGELOG.md +2131 -0
  2. package/README.md +47 -63
  3. package/dist/ai/generate.d.ts +5 -0
  4. package/dist/ai/generate.js +244 -115
  5. package/dist/ai/index.d.ts +1 -1
  6. package/dist/ai/json-to-markdown.d.ts +21 -1
  7. package/dist/ai/json-to-markdown.js +120 -57
  8. package/dist/ai/references.d.ts +72 -0
  9. package/dist/ai/references.js +223 -0
  10. package/dist/ai/resolve-branch.js +9 -2
  11. package/dist/ai/to-plain-text.d.ts +17 -8
  12. package/dist/ai/to-plain-text.js +18 -15
  13. package/dist/ai/types.d.ts +84 -3
  14. package/dist/api/admin-branch-health.d.ts +24 -1
  15. package/dist/api/admin-branch-health.js +52 -20
  16. package/dist/api/admin.d.ts +23 -5
  17. package/dist/api/admin.js +36 -1
  18. package/dist/api/assets.js +88 -78
  19. package/dist/api/branch-create-window.d.ts +12 -0
  20. package/dist/api/branch-create-window.js +13 -0
  21. package/dist/api/branch-merge.d.ts +1 -1
  22. package/dist/api/branch-merge.js +1 -1
  23. package/dist/api/branch-review.d.ts +3 -3
  24. package/dist/api/branch-review.js +16 -7
  25. package/dist/api/branch-status.d.ts +16 -6
  26. package/dist/api/branch-status.js +57 -12
  27. package/dist/api/branch-withdraw.d.ts +2 -2
  28. package/dist/api/branch-withdraw.js +8 -3
  29. package/dist/api/branch.d.ts +25 -12
  30. package/dist/api/branch.js +164 -103
  31. package/dist/api/client.d.ts +78 -58
  32. package/dist/api/client.js +150 -117
  33. package/dist/api/comments.d.ts +12 -3
  34. package/dist/api/comments.js +118 -9
  35. package/dist/api/content.d.ts +9 -9
  36. package/dist/api/content.js +131 -22
  37. package/dist/api/entries-constants.d.ts +14 -3
  38. package/dist/api/entries-constants.js +14 -3
  39. package/dist/api/entries.d.ts +40 -2
  40. package/dist/api/entries.js +102 -6
  41. package/dist/api/github-sync.d.ts +19 -0
  42. package/dist/api/github-sync.js +50 -3
  43. package/dist/api/groups.d.ts +4 -4
  44. package/dist/api/groups.js +2 -1
  45. package/dist/api/guards.js +23 -3
  46. package/dist/api/index.d.ts +3 -1
  47. package/dist/api/index.js +1 -0
  48. package/dist/api/permissions.d.ts +21 -2
  49. package/dist/api/permissions.js +50 -3
  50. package/dist/api/request-deadline.d.ts +59 -0
  51. package/dist/api/request-deadline.js +73 -0
  52. package/dist/api/resolve-references.js +8 -7
  53. package/dist/api/route-builder.d.ts +14 -0
  54. package/dist/api/route-builder.js +14 -1
  55. package/dist/api/schema.d.ts +11 -9
  56. package/dist/api/schema.js +2 -0
  57. package/dist/api/settings-helpers.d.ts +5 -1
  58. package/dist/api/settings-helpers.js +10 -2
  59. package/dist/api/types.d.ts +18 -0
  60. package/dist/api/users-constants.d.ts +6 -0
  61. package/dist/api/users-constants.js +6 -0
  62. package/dist/assets/asset-url.d.ts +28 -16
  63. package/dist/assets/asset-url.js +51 -25
  64. package/dist/assets/finalize.js +3 -0
  65. package/dist/assets/index.d.ts +1 -1
  66. package/dist/assets/materialize.d.ts +153 -0
  67. package/dist/assets/materialize.js +402 -0
  68. package/dist/assets/store-local.d.ts +19 -5
  69. package/dist/assets/store-local.js +69 -10
  70. package/dist/assets/store-s3.d.ts +38 -7
  71. package/dist/assets/store-s3.js +128 -22
  72. package/dist/assets/transform-directives.d.ts +52 -13
  73. package/dist/assets/transform-directives.js +87 -27
  74. package/dist/assets/transform.d.ts +3 -3
  75. package/dist/assets/transform.js +13 -8
  76. package/dist/assets/types.d.ts +31 -4
  77. package/dist/auth/cache.d.ts +1 -1
  78. package/dist/auth/cache.js +1 -1
  79. package/dist/auth/caching-auth-plugin.d.ts +2 -0
  80. package/dist/auth/caching-auth-plugin.js +12 -2
  81. package/dist/auth/file-based-auth-cache.d.ts +8 -0
  82. package/dist/auth/file-based-auth-cache.js +18 -8
  83. package/dist/auth/person-name.d.ts +5 -0
  84. package/dist/auth/person-name.js +8 -0
  85. package/dist/auth/plugin.d.ts +10 -1
  86. package/dist/auth/types.d.ts +2 -0
  87. package/dist/auth/user-metadata-lookup.d.ts +21 -0
  88. package/dist/auth/user-metadata-lookup.js +116 -0
  89. package/dist/authorization/branch.d.ts +1 -1
  90. package/dist/authorization/branch.js +12 -13
  91. package/dist/authorization/groups/schema.d.ts +4 -4
  92. package/dist/authorization/helpers.d.ts +8 -0
  93. package/dist/authorization/helpers.js +8 -0
  94. package/dist/authorization/index.d.ts +1 -1
  95. package/dist/authorization/index.js +1 -1
  96. package/dist/authorization/protected-branch.d.ts +5 -4
  97. package/dist/authorization/protected-branch.js +7 -5
  98. package/dist/branch-health.d.ts +35 -8
  99. package/dist/branch-health.js +54 -18
  100. package/dist/branch-metadata-error.d.ts +27 -0
  101. package/dist/branch-metadata-error.js +23 -0
  102. package/dist/branch-metadata-file.d.ts +205 -24
  103. package/dist/branch-metadata-file.js +126 -25
  104. package/dist/branch-metadata.d.ts +45 -6
  105. package/dist/branch-metadata.js +142 -50
  106. package/dist/branch-provisioning.d.ts +167 -0
  107. package/dist/branch-provisioning.js +501 -0
  108. package/dist/branch-registry.d.ts +8 -0
  109. package/dist/branch-registry.js +61 -9
  110. package/dist/branch-schema-cache.d.ts +29 -12
  111. package/dist/branch-schema-cache.js +64 -21
  112. package/dist/branch-sparse.d.ts +23 -0
  113. package/dist/branch-sparse.js +118 -0
  114. package/dist/branch-workspace.d.ts +42 -3
  115. package/dist/branch-workspace.js +239 -100
  116. package/dist/build/asset-refs.d.ts +94 -0
  117. package/dist/build/asset-refs.js +267 -0
  118. package/dist/build/generate-ai-content.js +5 -0
  119. package/dist/build/inline-flight.d.ts +37 -0
  120. package/dist/build/inline-flight.js +142 -0
  121. package/dist/cli/asset-refs.d.ts +52 -0
  122. package/dist/cli/asset-refs.js +194 -0
  123. package/dist/cli/cli.d.ts +15 -1
  124. package/dist/cli/cli.js +8543 -3193
  125. package/dist/cli/configured-asset-store.d.ts +12 -0
  126. package/dist/cli/configured-asset-store.js +46 -0
  127. package/dist/cli/generate-ai-content.js +4244 -1072
  128. package/dist/cli/init.js +217 -126
  129. package/dist/cli/project-detect.d.ts +2 -0
  130. package/dist/cli/project-detect.js +2 -0
  131. package/dist/cli/template-files/Dockerfile.cms.template +13 -5
  132. package/dist/cli/template-files/cms-stack.ts.template +16 -13
  133. package/dist/client.d.ts +1 -2
  134. package/dist/client.js +1 -2
  135. package/dist/comment-store.d.ts +25 -0
  136. package/dist/comment-store.js +43 -8
  137. package/dist/config/flatten.js +2 -0
  138. package/dist/config/helpers.js +2 -2
  139. package/dist/config/index.d.ts +1 -1
  140. package/dist/config/schemas/config.d.ts +12 -29
  141. package/dist/config/schemas/config.js +9 -12
  142. package/dist/config/schemas/field.d.ts +3 -0
  143. package/dist/config/schemas/field.js +58 -4
  144. package/dist/config/schemas/media.d.ts +4 -13
  145. package/dist/config/schemas/media.js +5 -8
  146. package/dist/config/schemas/url.d.ts +2 -20
  147. package/dist/config/schemas/url.js +2 -25
  148. package/dist/config/types.d.ts +116 -22
  149. package/dist/config/types.js +3 -9
  150. package/dist/config/validation.d.ts +25 -4
  151. package/dist/config/validation.js +116 -3
  152. package/dist/content-id-index.js +2 -2
  153. package/dist/content-index-generation.d.ts +1 -1
  154. package/dist/content-index-generation.js +1 -1
  155. package/dist/content-listing.d.ts +10 -1
  156. package/dist/content-listing.js +22 -5
  157. package/dist/content-reader.d.ts +9 -0
  158. package/dist/content-reader.js +16 -11
  159. package/dist/content-store.d.ts +75 -12
  160. package/dist/content-store.js +249 -86
  161. package/dist/content-tree.js +2 -2
  162. package/dist/context.js +5 -2
  163. package/dist/dangling-reference-log.d.ts +17 -0
  164. package/dist/dangling-reference-log.js +29 -0
  165. package/dist/dev-content-watcher.d.ts +1 -1
  166. package/dist/dev-content-watcher.js +1 -1
  167. package/dist/editor/BranchManager.d.ts +23 -7
  168. package/dist/editor/BranchManager.js +119 -54
  169. package/dist/editor/CanopyEditor.d.ts +1 -1
  170. package/dist/editor/CanopyEditor.js +1 -2
  171. package/dist/editor/CommentsPanel.d.ts +14 -0
  172. package/dist/editor/CommentsPanel.js +68 -43
  173. package/dist/editor/Editor.d.ts +11 -5
  174. package/dist/editor/Editor.js +891 -218
  175. package/dist/editor/EditorAuthGate.d.ts +7 -1
  176. package/dist/editor/EditorAuthGate.js +41 -9
  177. package/dist/editor/EntryNavigator.d.ts +22 -0
  178. package/dist/editor/EntryNavigator.js +353 -105
  179. package/dist/editor/FormHeader.d.ts +25 -0
  180. package/dist/editor/FormHeader.js +82 -0
  181. package/dist/editor/FormRenderer.d.ts +27 -3
  182. package/dist/editor/FormRenderer.js +171 -76
  183. package/dist/editor/PreviewFrame.d.ts +40 -0
  184. package/dist/editor/PreviewFrame.js +215 -0
  185. package/dist/editor/PreviewPane.d.ts +47 -0
  186. package/dist/editor/PreviewPane.js +147 -0
  187. package/dist/editor/PreviewToolbarTools.d.ts +22 -0
  188. package/dist/editor/PreviewToolbarTools.js +73 -0
  189. package/dist/editor/admin/SystemHealthPanel.d.ts +2 -1
  190. package/dist/editor/admin/SystemHealthPanel.js +163 -33
  191. package/dist/editor/admin/useSystemHealth.d.ts +18 -0
  192. package/dist/editor/admin/useSystemHealth.js +70 -1
  193. package/dist/editor/branch-status.d.ts +17 -0
  194. package/dist/editor/branch-status.js +29 -0
  195. package/dist/editor/canopy-path.d.ts +9 -6
  196. package/dist/editor/canopy-path.js +8 -11
  197. package/dist/editor/client-reference-resolver.d.ts +43 -16
  198. package/dist/editor/client-reference-resolver.js +155 -64
  199. package/dist/editor/comments/BranchComments.d.ts +1 -1
  200. package/dist/editor/comments/BranchComments.js +2 -2
  201. package/dist/editor/comments/CloseRefusedNotice.d.ts +4 -0
  202. package/dist/editor/comments/CloseRefusedNotice.js +7 -0
  203. package/dist/editor/comments/CommentThreadsPopover.d.ts +47 -0
  204. package/dist/editor/comments/CommentThreadsPopover.js +274 -0
  205. package/dist/editor/comments/CommentWriteAlert.d.ts +11 -0
  206. package/dist/editor/comments/CommentWriteAlert.js +13 -0
  207. package/dist/editor/comments/EntryComments.d.ts +10 -4
  208. package/dist/editor/comments/EntryComments.js +14 -17
  209. package/dist/editor/comments/FieldWrapper.d.ts +18 -4
  210. package/dist/editor/comments/FieldWrapper.js +62 -28
  211. package/dist/editor/comments/InlineCommentThread.d.ts +9 -2
  212. package/dist/editor/comments/InlineCommentThread.js +11 -47
  213. package/dist/editor/comments/ThreadCarousel.d.ts +1 -1
  214. package/dist/editor/comments/ThreadCarousel.js +18 -21
  215. package/dist/editor/comments/ThreadReplyControls.d.ts +28 -0
  216. package/dist/editor/comments/ThreadReplyControls.js +92 -0
  217. package/dist/editor/comments/field-comment-context.d.ts +14 -0
  218. package/dist/editor/comments/field-comment-context.js +14 -0
  219. package/dist/editor/comments/form-comments.d.ts +48 -0
  220. package/dist/editor/comments/form-comments.js +178 -0
  221. package/dist/editor/comments/item-anchor-copy.d.ts +6 -0
  222. package/dist/editor/comments/item-anchor-copy.js +6 -0
  223. package/dist/editor/comments/move-focus-if-unmoved.d.ts +6 -0
  224. package/dist/editor/comments/move-focus-if-unmoved.js +11 -0
  225. package/dist/editor/comments/popover-placement.d.ts +18 -0
  226. package/dist/editor/comments/popover-placement.js +58 -0
  227. package/dist/editor/comments/use-tracked-writes.d.ts +16 -0
  228. package/dist/editor/comments/use-tracked-writes.js +53 -0
  229. package/dist/editor/components/BranchAccessList.d.ts +15 -0
  230. package/dist/editor/components/BranchAccessList.js +52 -0
  231. package/dist/editor/components/BranchLockBanner.d.ts +22 -0
  232. package/dist/editor/components/BranchLockBanner.js +39 -0
  233. package/dist/editor/components/BranchPill.d.ts +32 -0
  234. package/dist/editor/components/BranchPill.js +33 -0
  235. package/dist/editor/components/BranchesDrawer.d.ts +11 -0
  236. package/dist/editor/components/BranchesDrawer.js +5 -0
  237. package/dist/editor/components/ConfirmDeleteModal.d.ts +6 -1
  238. package/dist/editor/components/ConfirmDeleteModal.js +3 -2
  239. package/dist/editor/components/DestructiveMenuItem.d.ts +7 -0
  240. package/dist/editor/components/DestructiveMenuItem.js +8 -0
  241. package/dist/editor/components/EditorCrashScreen.d.ts +17 -0
  242. package/dist/editor/components/EditorCrashScreen.js +22 -0
  243. package/dist/editor/components/EditorErrorBoundary.d.ts +38 -0
  244. package/dist/editor/components/EditorErrorBoundary.js +40 -0
  245. package/dist/editor/components/EditorHeader.d.ts +56 -106
  246. package/dist/editor/components/EditorHeader.js +130 -131
  247. package/dist/editor/components/EditorRail.d.ts +49 -0
  248. package/dist/editor/components/EditorRail.js +65 -0
  249. package/dist/editor/components/EntryBreadcrumb.d.ts +33 -0
  250. package/dist/editor/components/EntryBreadcrumb.js +56 -0
  251. package/dist/editor/components/EntryCreateModal.d.ts +12 -7
  252. package/dist/editor/components/EntryCreateModal.js +51 -57
  253. package/dist/editor/components/NoEditPermissionNotice.d.ts +6 -5
  254. package/dist/editor/components/NoEditPermissionNotice.js +5 -4
  255. package/dist/editor/components/QuickOpenPalette.d.ts +22 -0
  256. package/dist/editor/components/QuickOpenPalette.js +66 -0
  257. package/dist/editor/components/ReadOnlyDraftNotice.d.ts +11 -0
  258. package/dist/editor/components/ReadOnlyDraftNotice.js +12 -0
  259. package/dist/editor/components/ReasonedMenuItem.d.ts +15 -0
  260. package/dist/editor/components/ReasonedMenuItem.js +15 -0
  261. package/dist/editor/components/ReferencedByList.d.ts +11 -0
  262. package/dist/editor/components/ReferencedByList.js +28 -0
  263. package/dist/editor/components/RelativeTime.d.ts +10 -0
  264. package/dist/editor/components/RelativeTime.js +10 -0
  265. package/dist/editor/components/RenameEntryModal.d.ts +9 -1
  266. package/dist/editor/components/RenameEntryModal.js +19 -31
  267. package/dist/editor/components/RetryToastMessage.d.ts +9 -0
  268. package/dist/editor/components/RetryToastMessage.js +6 -0
  269. package/dist/editor/components/SaveAllResult.d.ts +25 -0
  270. package/dist/editor/components/SaveAllResult.js +86 -0
  271. package/dist/editor/components/SaveSplitButton.d.ts +30 -0
  272. package/dist/editor/components/SaveSplitButton.js +108 -0
  273. package/dist/editor/components/StagedChangesDrawer.d.ts +39 -0
  274. package/dist/editor/components/StagedChangesDrawer.js +92 -0
  275. package/dist/editor/components/UnavailableEntryNotice.d.ts +6 -0
  276. package/dist/editor/components/UnavailableEntryNotice.js +9 -0
  277. package/dist/editor/components/UnavailableTypeMessage.d.ts +6 -0
  278. package/dist/editor/components/UnavailableTypeMessage.js +9 -0
  279. package/dist/editor/components/UndoToastMessage.d.ts +18 -0
  280. package/dist/editor/components/UndoToastMessage.js +10 -0
  281. package/dist/editor/components/UserBadge.d.ts +2 -5
  282. package/dist/editor/components/UserBadge.js +62 -33
  283. package/dist/editor/components/ValidationSummary.d.ts +14 -0
  284. package/dist/editor/components/ValidationSummary.js +24 -0
  285. package/dist/editor/components/index.d.ts +2 -2
  286. package/dist/editor/components/index.js +2 -2
  287. package/dist/editor/context/ApiClientContext.d.ts +8 -2
  288. package/dist/editor/context/ApiClientContext.js +19 -8
  289. package/dist/editor/context/AssetContext.d.ts +13 -35
  290. package/dist/editor/context/AssetContext.js +12 -31
  291. package/dist/editor/context/EditorStateContext.d.ts +4 -4
  292. package/dist/editor/context/EditorStateContext.js +4 -4
  293. package/dist/editor/context/SWRProvider.d.ts +3 -3
  294. package/dist/editor/context/SiteMdxAllowContext.d.ts +4 -0
  295. package/dist/editor/context/SiteMdxAllowContext.js +7 -0
  296. package/dist/editor/context/index.d.ts +2 -1
  297. package/dist/editor/context/index.js +2 -1
  298. package/dist/editor/contrast.d.ts +19 -0
  299. package/dist/editor/contrast.js +41 -0
  300. package/dist/editor/copy.d.ts +18 -0
  301. package/dist/editor/copy.js +18 -0
  302. package/dist/editor/editor-config.js +16 -0
  303. package/dist/editor/editor-utils.d.ts +34 -8
  304. package/dist/editor/editor-utils.js +42 -20
  305. package/dist/editor/entry-search.d.ts +20 -0
  306. package/dist/editor/entry-search.js +34 -0
  307. package/dist/editor/field-breadcrumb.d.ts +12 -0
  308. package/dist/editor/field-breadcrumb.js +85 -0
  309. package/dist/editor/field-props.d.ts +86 -0
  310. package/dist/editor/field-props.js +27 -0
  311. package/dist/editor/field-target.d.ts +30 -0
  312. package/dist/editor/field-target.js +77 -0
  313. package/dist/editor/fields/BlockField.d.ts +12 -0
  314. package/dist/editor/fields/BlockField.js +89 -73
  315. package/dist/editor/fields/CodeField.d.ts +2 -0
  316. package/dist/editor/fields/CodeField.js +6 -2
  317. package/dist/editor/fields/DateTimeField.d.ts +4 -29
  318. package/dist/editor/fields/DateTimeField.js +79 -41
  319. package/dist/editor/fields/ExpandAllButton.d.ts +6 -0
  320. package/dist/editor/fields/ExpandAllButton.js +4 -0
  321. package/dist/editor/fields/FieldCrashFallback.d.ts +17 -0
  322. package/dist/editor/fields/FieldCrashFallback.js +30 -0
  323. package/dist/editor/fields/FieldLabel.d.ts +37 -0
  324. package/dist/editor/fields/FieldLabel.js +19 -0
  325. package/dist/editor/fields/ImageField.d.ts +4 -0
  326. package/dist/editor/fields/ImageField.js +29 -14
  327. package/dist/editor/fields/InlineGroupField.js +4 -3
  328. package/dist/editor/fields/ItemList.d.ts +42 -0
  329. package/dist/editor/fields/ItemList.js +130 -0
  330. package/dist/editor/fields/MarkdownField.d.ts +32 -0
  331. package/dist/editor/fields/MarkdownField.js +223 -46
  332. package/dist/editor/fields/MdxImageDialog.js +1 -1
  333. package/dist/editor/fields/NumberField.d.ts +2 -0
  334. package/dist/editor/fields/NumberField.js +8 -5
  335. package/dist/editor/fields/NumberListField.d.ts +2 -0
  336. package/dist/editor/fields/NumberListField.js +19 -16
  337. package/dist/editor/fields/ObjectField.d.ts +2 -7
  338. package/dist/editor/fields/ObjectField.js +15 -5
  339. package/dist/editor/fields/ObjectListField.d.ts +29 -0
  340. package/dist/editor/fields/ObjectListField.js +57 -0
  341. package/dist/editor/fields/ReferenceField.d.ts +2 -0
  342. package/dist/editor/fields/ReferenceField.js +34 -9
  343. package/dist/editor/fields/SelectField.d.ts +2 -0
  344. package/dist/editor/fields/SelectField.js +11 -2
  345. package/dist/editor/fields/StringListField.d.ts +4 -1
  346. package/dist/editor/fields/StringListField.js +22 -8
  347. package/dist/editor/fields/TextField.d.ts +2 -0
  348. package/dist/editor/fields/TextField.js +6 -2
  349. package/dist/editor/fields/ToggleField.d.ts +2 -0
  350. package/dist/editor/fields/ToggleField.js +8 -3
  351. package/dist/editor/fields/entry-link/InsertEntryLink.d.ts +1 -1
  352. package/dist/editor/fields/entry-link/InsertEntryLink.js +1 -1
  353. package/dist/editor/fields/form-expansion.d.ts +9 -0
  354. package/dist/editor/fields/form-expansion.js +2 -0
  355. package/dist/editor/fields/item-expansion.d.ts +15 -0
  356. package/dist/editor/fields/item-expansion.js +60 -0
  357. package/dist/editor/fields/list-item-keys.d.ts +8 -0
  358. package/dist/editor/fields/list-item-keys.js +50 -0
  359. package/dist/editor/fields/list-item-title.d.ts +20 -0
  360. package/dist/editor/fields/list-item-title.js +85 -0
  361. package/dist/editor/fields/markdown-export-options.d.ts +2 -0
  362. package/dist/editor/fields/markdown-export-options.js +8 -0
  363. package/dist/editor/fields/markdown-fidelity-visitors.d.ts +11 -0
  364. package/dist/editor/fields/markdown-fidelity-visitors.js +160 -0
  365. package/dist/editor/fields/mdx-jsx-support.d.ts +5 -1
  366. package/dist/editor/fields/mdx-jsx-support.js +157 -69
  367. package/dist/editor/fields/rich-text-failures.d.ts +5 -0
  368. package/dist/editor/fields/rich-text-failures.js +26 -0
  369. package/dist/editor/fields/toolbar-wrap.d.ts +7 -0
  370. package/dist/editor/fields/toolbar-wrap.js +35 -0
  371. package/dist/editor/form-outline.d.ts +13 -0
  372. package/dist/editor/form-outline.js +70 -0
  373. package/dist/editor/group-manager/GroupCard.d.ts +4 -1
  374. package/dist/editor/group-manager/GroupCard.js +12 -3
  375. package/dist/editor/group-manager/GroupForm.js +1 -1
  376. package/dist/editor/group-manager/InternalGroupsTab.d.ts +2 -1
  377. package/dist/editor/group-manager/InternalGroupsTab.js +4 -4
  378. package/dist/editor/group-manager/MemberList.d.ts +2 -1
  379. package/dist/editor/group-manager/MemberList.js +15 -4
  380. package/dist/editor/group-manager/hooks/useGroupState.d.ts +5 -0
  381. package/dist/editor/group-manager/hooks/useGroupState.js +30 -7
  382. package/dist/editor/group-manager/index.js +34 -5
  383. package/dist/editor/group-manager/types.d.ts +5 -2
  384. package/dist/editor/hooks/create-branch-request.d.ts +20 -0
  385. package/dist/editor/hooks/create-branch-request.js +54 -0
  386. package/dist/editor/hooks/save-all.d.ts +68 -0
  387. package/dist/editor/hooks/save-all.js +61 -0
  388. package/dist/editor/hooks/useBranchActions.d.ts +11 -1
  389. package/dist/editor/hooks/useBranchActions.js +42 -19
  390. package/dist/editor/hooks/useBranchManager.d.ts +21 -0
  391. package/dist/editor/hooks/useBranchManager.js +321 -125
  392. package/dist/editor/hooks/useBranchesData.d.ts +8 -4
  393. package/dist/editor/hooks/useBranchesData.js +9 -5
  394. package/dist/editor/hooks/useCommentSystem.d.ts +14 -2
  395. package/dist/editor/hooks/useCommentSystem.js +79 -55
  396. package/dist/editor/hooks/useCommentsData.d.ts +11 -7
  397. package/dist/editor/hooks/useCommentsData.js +20 -19
  398. package/dist/editor/hooks/useDraftManager.d.ts +77 -3
  399. package/dist/editor/hooks/useDraftManager.js +887 -94
  400. package/dist/editor/hooks/useEditorLayout.d.ts +53 -7
  401. package/dist/editor/hooks/useEditorLayout.js +98 -20
  402. package/dist/editor/hooks/useEntriesData.d.ts +2 -1
  403. package/dist/editor/hooks/useEntriesData.js +18 -4
  404. package/dist/editor/hooks/useEntryManager.d.ts +41 -2
  405. package/dist/editor/hooks/useEntryManager.js +227 -41
  406. package/dist/editor/hooks/useGroupManager.js +13 -16
  407. package/dist/editor/hooks/usePermissionManager.js +14 -6
  408. package/dist/editor/hooks/usePreviewMarks.d.ts +21 -0
  409. package/dist/editor/hooks/usePreviewMarks.js +33 -0
  410. package/dist/editor/hooks/useReferenceResolution.d.ts +10 -3
  411. package/dist/editor/hooks/useReferenceResolution.js +34 -177
  412. package/dist/editor/hooks/useSchemaManager.d.ts +25 -6
  413. package/dist/editor/hooks/useSchemaManager.js +97 -39
  414. package/dist/editor/hooks/useUserMetadata.d.ts +14 -4
  415. package/dist/editor/hooks/useUserMetadata.js +76 -47
  416. package/dist/editor/hooks/user-metadata-batcher.d.ts +8 -0
  417. package/dist/editor/hooks/user-metadata-batcher.js +33 -0
  418. package/dist/editor/media/AssetCard.d.ts +6 -7
  419. package/dist/editor/media/AssetCard.js +24 -31
  420. package/dist/editor/media/AssetListRow.d.ts +16 -0
  421. package/dist/editor/media/AssetListRow.js +41 -0
  422. package/dist/editor/media/CropStep.js +6 -1
  423. package/dist/editor/media/MediaLibrary.d.ts +4 -3
  424. package/dist/editor/media/MediaLibrary.js +11 -4
  425. package/dist/editor/media/MediaLibraryBody.d.ts +3 -3
  426. package/dist/editor/media/MediaLibraryBody.js +195 -80
  427. package/dist/editor/media/MediaLibrarySkeleton.d.ts +6 -0
  428. package/dist/editor/media/MediaLibrarySkeleton.js +14 -0
  429. package/dist/editor/media/crop-math.d.ts +3 -4
  430. package/dist/editor/media/crop-math.js +11 -22
  431. package/dist/editor/media/editor-image-src.d.ts +8 -0
  432. package/dist/editor/media/editor-image-src.js +10 -0
  433. package/dist/editor/media/format-bytes.d.ts +2 -0
  434. package/dist/editor/media/format-bytes.js +23 -0
  435. package/dist/editor/media/media-library-prefs.d.ts +23 -0
  436. package/dist/editor/media/media-library-prefs.js +49 -0
  437. package/dist/editor/media/media-library-view.d.ts +34 -0
  438. package/dist/editor/media/media-library-view.js +40 -0
  439. package/dist/editor/media/useAssetLibrary.d.ts +30 -0
  440. package/dist/editor/media/useAssetLibrary.js +126 -0
  441. package/dist/editor/media/useStableAssetOrder.d.ts +11 -0
  442. package/dist/editor/media/useStableAssetOrder.js +37 -0
  443. package/dist/editor/media/useThumbnailFailure.d.ts +6 -0
  444. package/dist/editor/media/useThumbnailFailure.js +16 -0
  445. package/dist/editor/permission-manager/GroupSelector.js +2 -2
  446. package/dist/editor/permission-manager/PermissionEditor.d.ts +1 -1
  447. package/dist/editor/permission-manager/PermissionEditor.js +15 -4
  448. package/dist/editor/permission-manager/PermissionLevelBadge.js +1 -1
  449. package/dist/editor/permission-manager/PermissionTree.d.ts +1 -1
  450. package/dist/editor/permission-manager/UserSelector.d.ts +1 -1
  451. package/dist/editor/permission-manager/UserSelector.js +3 -3
  452. package/dist/editor/permission-manager/index.js +12 -3
  453. package/dist/editor/permission-manager/types.d.ts +5 -2
  454. package/dist/editor/platform.d.ts +3 -0
  455. package/dist/editor/platform.js +9 -0
  456. package/dist/editor/preview-asset-base.d.ts +9 -0
  457. package/dist/editor/preview-asset-base.js +17 -0
  458. package/dist/editor/preview-bridge.d.ts +51 -27
  459. package/dist/editor/preview-bridge.js +103 -144
  460. package/dist/editor/preview-marks.d.ts +15 -0
  461. package/dist/editor/preview-marks.js +74 -0
  462. package/dist/editor/preview-path.d.ts +12 -0
  463. package/dist/editor/preview-path.js +45 -1
  464. package/dist/editor/preview-scale.d.ts +27 -0
  465. package/dist/editor/preview-scale.js +34 -0
  466. package/dist/editor/raw-asset-base.d.ts +5 -0
  467. package/dist/editor/raw-asset-base.js +14 -0
  468. package/dist/editor/relative-time.d.ts +4 -4
  469. package/dist/editor/relative-time.js +16 -8
  470. package/dist/editor/schema-editor/CollectionEditor.d.ts +20 -3
  471. package/dist/editor/schema-editor/CollectionEditor.js +361 -62
  472. package/dist/editor/schema-editor/EntryTypeEditor.d.ts +5 -1
  473. package/dist/editor/schema-editor/EntryTypeEditor.js +11 -6
  474. package/dist/editor/schema-editor/collection-draft.d.ts +22 -0
  475. package/dist/editor/schema-editor/collection-draft.js +40 -0
  476. package/dist/editor/schema-editor/index.d.ts +1 -1
  477. package/dist/editor/schema-label.d.ts +10 -0
  478. package/dist/editor/schema-label.js +17 -0
  479. package/dist/editor/settings-conflict.d.ts +5 -0
  480. package/dist/editor/settings-conflict.js +8 -0
  481. package/dist/editor/shell/ActivityRail.d.ts +37 -0
  482. package/dist/editor/shell/ActivityRail.js +91 -0
  483. package/dist/editor/shell/EditorShell.d.ts +17 -0
  484. package/dist/editor/shell/EditorShell.js +31 -0
  485. package/dist/editor/shell/PaneLayout.d.ts +28 -0
  486. package/dist/editor/shell/PaneLayout.js +116 -0
  487. package/dist/editor/shell/SidePanelHost.d.ts +35 -0
  488. package/dist/editor/shell/SidePanelHost.js +184 -0
  489. package/dist/editor/shell/TopBar.d.ts +12 -0
  490. package/dist/editor/shell/TopBar.js +14 -0
  491. package/dist/editor/shell/host-chrome.d.ts +16 -0
  492. package/dist/editor/shell/host-chrome.js +69 -0
  493. package/dist/editor/slug.d.ts +16 -0
  494. package/dist/editor/slug.js +43 -0
  495. package/dist/editor/theme.d.ts +2 -1
  496. package/dist/editor/theme.js +314 -13
  497. package/dist/editor/unanswered.d.ts +17 -0
  498. package/dist/editor/unanswered.js +26 -0
  499. package/dist/editor/unavailable-entry-type.d.ts +20 -0
  500. package/dist/editor/unavailable-entry-type.js +28 -0
  501. package/dist/editor/undo-removal.d.ts +57 -0
  502. package/dist/editor/undo-removal.js +163 -0
  503. package/dist/editor/user-display.d.ts +4 -0
  504. package/dist/editor/user-display.js +5 -0
  505. package/dist/editor/utils/confirm-modal.d.ts +8 -0
  506. package/dist/editor/utils/confirm-modal.js +12 -0
  507. package/dist/editor/utils/editor-errors.d.ts +19 -0
  508. package/dist/editor/utils/editor-errors.js +32 -0
  509. package/dist/editor/utils/focus-return.d.ts +56 -0
  510. package/dist/editor/utils/focus-return.js +106 -0
  511. package/dist/entry-link-resolver.d.ts +11 -1
  512. package/dist/entry-link-resolver.js +65 -5
  513. package/dist/entry-schema-registry.d.ts +3 -3
  514. package/dist/entry-schema-registry.js +7 -4
  515. package/dist/entry-schema.d.ts +49 -3
  516. package/dist/entry-schema.js +18 -0
  517. package/dist/git-manager.d.ts +172 -10
  518. package/dist/git-manager.js +449 -107
  519. package/dist/github-service.d.ts +39 -2
  520. package/dist/github-service.js +101 -7
  521. package/dist/http/handler.js +74 -9
  522. package/dist/http/types.d.ts +2 -0
  523. package/dist/http/worker-not-ready.d.ts +9 -4
  524. package/dist/http/worker-not-ready.js +59 -6
  525. package/dist/index.d.ts +2 -0
  526. package/dist/index.js +3 -0
  527. package/dist/item-keys.d.ts +46 -0
  528. package/dist/item-keys.js +235 -0
  529. package/dist/operating-mode/editor-mode-check.d.ts +15 -0
  530. package/dist/operating-mode/editor-mode-check.js +24 -0
  531. package/dist/operating-mode/mode-env.d.ts +13 -14
  532. package/dist/operating-mode/mode-env.js +13 -14
  533. package/dist/operating-mode/types.d.ts +2 -2
  534. package/dist/paths/branch.d.ts +0 -1
  535. package/dist/paths/branch.js +1 -8
  536. package/dist/paths/index.d.ts +1 -1
  537. package/dist/paths/index.js +1 -1
  538. package/dist/paths/validation.js +0 -2
  539. package/dist/preview.d.ts +8 -0
  540. package/dist/preview.js +7 -0
  541. package/dist/reference-resolver.js +1 -0
  542. package/dist/resolve-canopy-user.d.ts +1 -1
  543. package/dist/resolve-canopy-user.js +1 -1
  544. package/dist/schema/meta-loader.d.ts +18 -8
  545. package/dist/schema/meta-loader.js +42 -9
  546. package/dist/schema/registry-fingerprint.d.ts +14 -0
  547. package/dist/schema/registry-fingerprint.js +63 -0
  548. package/dist/schema/resolver.d.ts +7 -4
  549. package/dist/schema/resolver.js +7 -6
  550. package/dist/schema/schema-store.d.ts +7 -10
  551. package/dist/schema/schema-store.js +13 -11
  552. package/dist/schema/schema-unavailable-error.d.ts +11 -0
  553. package/dist/schema/schema-unavailable-error.js +15 -0
  554. package/dist/schema/types.d.ts +22 -0
  555. package/dist/schema-registry-record.d.ts +35 -0
  556. package/dist/schema-registry-record.js +134 -0
  557. package/dist/server.d.ts +79 -9
  558. package/dist/server.js +62 -8
  559. package/dist/services.d.ts +33 -5
  560. package/dist/services.js +155 -37
  561. package/dist/settings-workspace.d.ts +23 -3
  562. package/dist/settings-workspace.js +159 -45
  563. package/dist/stamp-on-publish.d.ts +36 -0
  564. package/dist/stamp-on-publish.js +71 -0
  565. package/dist/static/index.d.ts +58 -5
  566. package/dist/static/index.js +133 -3
  567. package/dist/static/seo.d.ts +5 -5
  568. package/dist/static/seo.js +5 -5
  569. package/dist/submission-attribution.d.ts +8 -0
  570. package/dist/submission-attribution.js +22 -0
  571. package/dist/system-users.d.ts +12 -0
  572. package/dist/system-users.js +12 -0
  573. package/dist/task-queue/cms-task-queue.d.ts +1 -1
  574. package/dist/task-queue/cms-task-queue.js +1 -1
  575. package/dist/task-queue/index.d.ts +1 -1
  576. package/dist/task-queue/index.js +1 -1
  577. package/dist/task-queue/task-queue.d.ts +6 -0
  578. package/dist/task-queue/task-queue.js +29 -0
  579. package/dist/task-queue/worker-status.d.ts +35 -9
  580. package/dist/task-queue/worker-status.js +84 -17
  581. package/dist/types.d.ts +90 -9
  582. package/dist/url-collision.js +3 -3
  583. package/dist/url-exclusivity-fixtures.d.ts +8 -1
  584. package/dist/url-exclusivity-fixtures.js +11 -1
  585. package/dist/user.js +2 -1
  586. package/dist/utils/atomic-write.js +2 -1
  587. package/dist/utils/base-branch.d.ts +13 -0
  588. package/dist/utils/base-branch.js +23 -0
  589. package/dist/utils/content-serialize.d.ts +15 -7
  590. package/dist/utils/content-serialize.js +353 -51
  591. package/dist/utils/content-write-lock.d.ts +6 -1
  592. package/dist/utils/content-write-lock.js +21 -6
  593. package/dist/utils/debug.d.ts +1 -1
  594. package/dist/utils/debug.js +5 -3
  595. package/dist/utils/error.js +13 -2
  596. package/dist/utils/escape-text.d.ts +7 -0
  597. package/dist/utils/escape-text.js +27 -0
  598. package/dist/utils/fs.d.ts +12 -4
  599. package/dist/utils/fs.js +18 -5
  600. package/dist/utils/git.d.ts +21 -1
  601. package/dist/utils/git.js +63 -8
  602. package/dist/utils/json-source-splice.d.ts +14 -0
  603. package/dist/utils/json-source-splice.js +120 -0
  604. package/dist/utils/markdown-body-splice.d.ts +39 -0
  605. package/dist/utils/markdown-body-splice.js +421 -0
  606. package/dist/utils/occ-json-write.js +18 -12
  607. package/dist/utils/provision-log.d.ts +25 -0
  608. package/dist/utils/provision-log.js +49 -0
  609. package/dist/utils/provisioning-lock.d.ts +32 -1
  610. package/dist/utils/provisioning-lock.js +78 -32
  611. package/dist/utils/sanitize-href.d.ts +9 -23
  612. package/dist/utils/sanitize-href.js +14 -28
  613. package/dist/utils/title-field.d.ts +10 -0
  614. package/dist/utils/title-field.js +47 -0
  615. package/dist/utils/yaml-source-splice.d.ts +58 -0
  616. package/dist/utils/yaml-source-splice.js +515 -0
  617. package/dist/validation/block-structural-keys.d.ts +1 -1
  618. package/dist/validation/block-structural-keys.js +1 -1
  619. package/dist/validation/deletion-checker.d.ts +21 -29
  620. package/dist/validation/deletion-checker.js +41 -117
  621. package/dist/validation/entry-validator.d.ts +6 -10
  622. package/dist/validation/entry-validator.js +19 -26
  623. package/dist/validation/field-traversal.d.ts +22 -0
  624. package/dist/validation/field-traversal.js +39 -8
  625. package/dist/validation/markdown-safety.d.ts +68 -0
  626. package/dist/validation/markdown-safety.js +596 -0
  627. package/dist/validation/mdx-allowlist.d.ts +55 -0
  628. package/dist/validation/mdx-allowlist.js +183 -0
  629. package/dist/validation/reference-validator.d.ts +2 -0
  630. package/dist/validation/reference-validator.js +3 -0
  631. package/dist/version.d.ts +1 -1
  632. package/dist/version.js +1 -1
  633. package/dist/worker/branch-policy.d.ts +124 -0
  634. package/dist/worker/branch-policy.js +243 -0
  635. package/dist/worker/bundle-header.d.ts +117 -0
  636. package/dist/worker/bundle-header.js +253 -0
  637. package/dist/worker/cms-worker.d.ts +236 -142
  638. package/dist/worker/cms-worker.js +772 -285
  639. package/dist/worker/gateway-egress.d.ts +43 -0
  640. package/dist/worker/gateway-egress.js +209 -0
  641. package/dist/worker/gateway-instance.d.ts +42 -0
  642. package/dist/worker/gateway-instance.js +129 -0
  643. package/dist/worker/git-process.d.ts +81 -0
  644. package/dist/worker/git-process.js +234 -0
  645. package/dist/worker/git-sync.d.ts +34 -17
  646. package/dist/worker/git-sync.js +394 -131
  647. package/dist/worker/github-auth.d.ts +9 -9
  648. package/dist/worker/github-auth.js +13 -15
  649. package/dist/worker/github-gateway-http.d.ts +62 -0
  650. package/dist/worker/github-gateway-http.js +977 -0
  651. package/dist/worker/github-gateway-wire.d.ts +590 -0
  652. package/dist/worker/github-gateway-wire.js +488 -0
  653. package/dist/worker/github-gateway.d.ts +238 -0
  654. package/dist/worker/github-gateway.js +726 -0
  655. package/dist/worker/github-mirror-upkeep.d.ts +75 -0
  656. package/dist/worker/github-mirror-upkeep.js +266 -0
  657. package/dist/worker/github-mirror.d.ts +364 -0
  658. package/dist/worker/github-mirror.js +1030 -0
  659. package/dist/worker/github-transfer.d.ts +56 -0
  660. package/dist/worker/github-transfer.js +444 -0
  661. package/dist/worker/history-rewrite.d.ts +4 -5
  662. package/dist/worker/history-rewrite.js +12 -17
  663. package/dist/worker/provisioned-workspace.d.ts +5 -5
  664. package/dist/worker/provisioned-workspace.js +5 -5
  665. package/dist/worker/rebase.d.ts +1 -1
  666. package/dist/worker/rebase.js +58 -54
  667. package/dist/worker/remote-git-maintenance.d.ts +3 -0
  668. package/dist/worker/remote-git-maintenance.js +12 -0
  669. package/dist/worker/schema-gate.d.ts +49 -0
  670. package/dist/worker/schema-gate.js +226 -0
  671. package/dist/worker/self-checks.d.ts +107 -0
  672. package/dist/worker/self-checks.js +358 -0
  673. package/dist/worker/shared-repo-git.d.ts +96 -0
  674. package/dist/worker/shared-repo-git.js +459 -0
  675. package/dist/worker/sparse-cone.d.ts +20 -0
  676. package/dist/worker/sparse-cone.js +136 -0
  677. package/dist/worker/stale-mirror.d.ts +82 -0
  678. package/dist/worker/stale-mirror.js +130 -0
  679. package/dist/worker/task-runner.d.ts +5 -3
  680. package/dist/worker/task-runner.js +287 -186
  681. package/dist/worker/worker-context.d.ts +36 -37
  682. package/package.json +35 -7
  683. package/dist/editor/EditorPanes.d.ts +0 -10
  684. package/dist/editor/EditorPanes.js +0 -98
  685. package/dist/editor/components/EditorFooter.d.ts +0 -11
  686. package/dist/editor/components/EditorFooter.js +0 -9
  687. package/dist/editor/components/EditorSidebar.d.ts +0 -67
  688. package/dist/editor/components/EditorSidebar.js +0 -22
@@ -1,6 +1,5 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
- import { simpleGit } from 'simple-git';
4
3
  import { BranchMetadataFileManager, buildMergedBranchUpdate, getBranchMetadataFileManager, } from '../branch-metadata.js';
5
4
  import { extractIdFromFilename } from '../content-id-index.js';
6
5
  import { invalidateBranchContentCaches } from '../content-index-generation.js';
@@ -14,6 +13,7 @@ import { isCanopyInternalPath, isRebaseInProgress } from '../utils/git.js';
14
13
  import { TRACKED_CANOPY_STATE_FIX, restoreRetiredSchemaCache, splitByUpstreamTracking, trackedCanopyStateChanges, } from './canopy-state.js';
15
14
  import { enqueueGitHubPush, forcePublishToLocalRemote, markHistoryRewritten, readPublishedSha, reconcilePendingRewrite, } from './history-rewrite.js';
16
15
  import { workerLog, workerLogWarn } from './log.js';
16
+ import { SHARED_REPO_STATUS_ARGS, assertNoIncomingSubmodules, assertSharedRepoConfig, fetchFromRemoteGit, sharedRepoGit, } from './shared-repo-git.js';
17
17
  /**
18
18
  * Poll GitHub for a submitted/approved branch's PR resolution. Those branches
19
19
  * sit outside the rebase loop and get no other signal that their PR resolved --
@@ -32,12 +32,7 @@ export async function pollMergeState(ctx, branchDir, branchPath, metaFile) {
32
32
  const controller = new AbortController();
33
33
  const timer = setTimeout(() => controller.abort(), ctx.taskTimeoutMs);
34
34
  try {
35
- const { data } = await ctx.octokit().pulls.get({
36
- owner: ctx.githubOwner,
37
- repo: ctx.githubRepo,
38
- pull_number: prNumber,
39
- request: { signal: controller.signal },
40
- });
35
+ const data = await ctx.github().getPullRequest(prNumber, controller.signal);
41
36
  if (data.merged) {
42
37
  const meta = getBranchMetadataFileManager(branchPath, ctx.contentBranchesPath);
43
38
  // GitHub's actual merge time when available; buildMergedBranchUpdate
@@ -176,7 +171,7 @@ async function runRebaseRounds(ctx, branchGit, branchDir, fetchedBaseTip, isLock
176
171
  }
177
172
  catch (rebaseErr) {
178
173
  nextAction = 'continue';
179
- const st = await branchGit.status();
174
+ const st = await branchGit.status([...SHARED_REPO_STATUS_ARGS]);
180
175
  if (st.conflicted.length > 0) {
181
176
  await ctx.afterConflictDetectedForTesting();
182
177
  // During rebase, --theirs = the branch being replayed (the editor's
@@ -199,20 +194,23 @@ async function runRebaseRounds(ctx, branchGit, branchDir, fetchedBaseTip, isLock
199
194
  //
200
195
  // A per-file resolution that STILL fails routes into the `!completed`
201
196
  // path below, which aborts and records, rather than escaping.
197
+ //
198
+ // `--sparse` on add/rm: a conflicted path outside a sparse clone's cone
199
+ // is materialised for the conflict, but git refuses to stage it without.
202
200
  const conflictKind = new Map(st.files.map((f) => [f.path, `${f.index}${f.working_dir}`]));
203
201
  let resolutionFailure;
204
202
  for (const file of st.conflicted) {
205
203
  const kind = conflictKind.get(file);
206
204
  try {
207
205
  if (kind === 'UD') {
208
- await branchGit.raw(['rm', '-f', '--', file]);
206
+ await branchGit.raw(['rm', '-f', '--sparse', '--', file]);
209
207
  }
210
208
  else if (kind === 'DU') {
211
- await branchGit.add(file);
209
+ await branchGit.raw(['add', '--sparse', '--', file]);
212
210
  }
213
211
  else {
214
- await branchGit.raw(['checkout', '--theirs', file]);
215
- await branchGit.add(file);
212
+ await branchGit.raw(['checkout', '--theirs', '--', file]);
213
+ await branchGit.raw(['add', '--sparse', '--', file]);
216
214
  }
217
215
  }
218
216
  catch (resolveErr) {
@@ -242,9 +240,7 @@ async function runRebaseRounds(ctx, branchGit, branchDir, fetchedBaseTip, isLock
242
240
  else {
243
241
  // Unexpected error — abort and leave the branch behind.
244
242
  // conflictStatus/conflictFiles are deliberately not updated: the
245
- // rebase did not complete, so the true conflict state is unknown, and
246
- // the existing (possibly stale) metadata is preserved until the next
247
- // successful rebase cycle corrects it.
243
+ // rebase did not complete, so it replaced nothing to record.
248
244
  workerLogWarn(` Unexpected rebase error in ${branchDir}: ${msg || 'Unknown error'}`);
249
245
  failureReason = msg || 'Unknown error';
250
246
  await branchGit.rebase(['--abort']).catch(() => { });
@@ -304,6 +300,21 @@ function conflictFilesToContentIds(conflictedFiles, contentRoot) {
304
300
  .filter((id) => id !== null);
305
301
  return [...new Set(conflictIds)];
306
302
  }
303
+ /**
304
+ * The conflict record after a rebase: the IDs already recorded plus this
305
+ * rebase's. A conflict means this branch's version replaced base's change to
306
+ * that entry, which a later clean rebase does not undo, so the record only
307
+ * grows until the branch merges (buildMergedBranchUpdate clears it).
308
+ *
309
+ * Pure.
310
+ */
311
+ function recordConflicts(recorded, found) {
312
+ const conflictFiles = [...new Set([...(recorded ?? []), ...found])];
313
+ return {
314
+ conflictStatus: conflictFiles.length > 0 ? 'conflicts-detected' : 'clean',
315
+ conflictFiles,
316
+ };
317
+ }
307
318
  /**
308
319
  * [SYNC-H1] Carry a just-rebased history forward into `remote.git`, and queue
309
320
  * the GitHub hop.
@@ -378,6 +389,12 @@ export async function runRebaseCycle(ctx) {
378
389
  return { rebased, skippedDirty, skippedLocked, failed };
379
390
  }
380
391
  for (const branchDir of branchDirs) {
392
+ // A draining worker finishes the branch it is rebasing and starts no other:
393
+ // a rebase killed mid-branch is recovered lossily on the next boot.
394
+ if (ctx.isDraining()) {
395
+ workerLog(' Rebase cycle stopped: the worker is draining');
396
+ break;
397
+ }
381
398
  // Known structural entries under content-branches/, not branch workspaces:
382
399
  // branches.json (registry snapshot) and dot-prefixed entries
383
400
  // (.canopy-meta/, transient lock dirs). Skipped SILENTLY so the no-.git
@@ -436,7 +453,7 @@ export async function runRebaseCycle(ctx) {
436
453
  /**
437
454
  * {@link rebaseOneBranch} under the branch's provisioning lock, so no git step races a clone of
438
455
  * this directory. A branch whose lock is held elsewhere is `skippedLocked` and retried next cycle;
439
- * one with no `branch.json` yet is skipped as not provisioned. Lock order: provisioning, then the
456
+ * one with no `branch.json` is skipped. Lock order: provisioning, then the
440
457
  * content-write lock inside it; see `provisioned-workspace.ts` for why that order cannot deadlock.
441
458
  * Never throws, like rebaseOneBranch.
442
459
  */
@@ -455,10 +472,10 @@ async function holdAndRebaseOneBranch(ctx, branchDir, branchPath) {
455
472
  workerLog(` Skipping ${branchDir}: provisioning lock held elsewhere (retrying next cycle)`);
456
473
  return { kind: 'skippedLocked' };
457
474
  }
458
- if (hold.kind === 'not-provisioned') {
459
- workerLog(` Skipping ${branchDir}: not yet provisioned (no branch.json)`);
475
+ // Silent: a directory without branch.json is a staging-era leftover, which
476
+ // repairBranchDirResidue (git-sync.ts) quarantines and logs at the cycle's start.
477
+ if (hold.kind === 'not-provisioned')
460
478
  return { kind: 'none' };
461
- }
462
479
  try {
463
480
  return await rebaseOneBranch(ctx, branchDir, branchPath, hold.isCompromised);
464
481
  }
@@ -499,15 +516,11 @@ async function rebaseOneBranch(ctx, branchDir, branchPath, isProvisioningLockCom
499
516
  workerLog(` Skipping ${branchDir} (${branchStatus})`);
500
517
  return { kind: 'none' };
501
518
  }
502
- const branchGit = simpleGit({
503
- baseDir: branchPath,
504
- // Keep git non-interactive during rebase/merge so it never blocks on an
505
- // editor. simple-git >=3.32 requires opting in to set core.editor; the
506
- // value is a hardcoded literal ("true", the shell no-op), not user input,
507
- // so allowUnsafeEditor carries no injection risk here.
508
- config: ['core.editor=true'],
509
- unsafe: { allowUnsafeEditor: true },
510
- });
519
+ // Throws into the catch below, which records the refusal on the branch: a planted key here
520
+ // would run in this worker's rebase, merge and checkout.
521
+ await assertSharedRepoConfig(branchPath, 'worktree');
522
+ // The pins also keep git non-interactive (core.editor=true): a rebase never waits on an editor.
523
+ const branchGit = sharedRepoGit(branchPath, 'worktree');
511
524
  // [SYNC-C1] Take the branch's cross-host content-write lock BEFORE the
512
525
  // dirty check, and hold it for the whole rebase. The dirty check alone is
513
526
  // check-then-act, and its residual window spans fetch, replay and N
@@ -574,7 +587,7 @@ async function rebaseOneBranch(ctx, branchDir, branchPath, isProvisioningLockCom
574
587
  // happen SILENTLY, so anything modified beyond the rebase's own conflict
575
588
  // state is logged by path first. That log is the operator's only record.
576
589
  if (await isRebaseInProgress(branchPath)) {
577
- const preAbort = await branchGit.status().catch(() => null);
590
+ const preAbort = await branchGit.status([...SHARED_REPO_STATUS_ARGS]).catch(() => null);
578
591
  // Keyed on the WORKING-TREE column ONLY. The two porcelain columns mean
579
592
  // different things here, and conflating them reports false data loss on
580
593
  // essentially every conflict-wedged recovery (verified against real
@@ -621,10 +634,10 @@ async function rebaseOneBranch(ctx, branchDir, branchPath, isProvisioningLockCom
621
634
  // Skip dirty branches — the editor has changes that cannot be rebased.
622
635
  // Inside the lock, so no write can land between this check and the rebase
623
636
  // below. canopycms's own untracked state is not dirt.
624
- let dirtyCheck = await branchGit.status();
637
+ let dirtyCheck = await branchGit.status([...SHARED_REPO_STATUS_ARGS]);
625
638
  if (await restoreRetiredSchemaCache(branchGit, dirtyCheck)) {
626
639
  workerLog(` ${branchDir}: restored the retired in-tree schema cache`);
627
- dirtyCheck = await branchGit.status();
640
+ dirtyCheck = await branchGit.status([...SHARED_REPO_STATUS_ARGS]);
628
641
  }
629
642
  const editorDirt = dirtyCheck.files.filter((f) => !isCanopyInternalPath(f.path));
630
643
  if (editorDirt.length > 0) {
@@ -638,7 +651,7 @@ async function rebaseOneBranch(ctx, branchDir, branchPath, isProvisioningLockCom
638
651
  // the bytes stay untouched, and the wedge is recorded for an operator.
639
652
  const trackedState = trackedCanopyStateChanges(dirtyCheck);
640
653
  if (trackedState.length > 0) {
641
- await branchGit.fetch(ctx.remoteGitPath, ctx.baseBranch);
654
+ await fetchFromRemoteGit(branchGit, ctx.remoteGitPath, ctx.baseBranch);
642
655
  const baseTip = (await branchGit.revparse(['FETCH_HEAD'])).trim();
643
656
  const { stillTracked } = await splitByUpstreamTracking(branchGit, trackedState, baseTip);
644
657
  const reason = stillTracked.length > 0
@@ -672,7 +685,7 @@ async function rebaseOneBranch(ctx, branchDir, branchPath, isProvisioningLockCom
672
685
  marker: metaFile.branch.historyRewrittenFrom,
673
686
  });
674
687
  }
675
- await branchGit.fetch(ctx.remoteGitPath, ctx.baseBranch);
688
+ await fetchFromRemoteGit(branchGit, ctx.remoteGitPath, ctx.baseBranch);
676
689
  // rev-list, not status.behind, which needs an upstream tracking branch
677
690
  // that checkoutBranch's fallback paths do not always configure. Against
678
691
  // the just-fetched tip: a fetch by path updates no remote-tracking ref.
@@ -691,26 +704,17 @@ async function rebaseOneBranch(ctx, branchDir, branchPath, isProvisioningLockCom
691
704
  // `metaFile` snapshot from before the fetch/rev-list above, so a
692
705
  // concurrent editor-driven metadata change in that window is not
693
706
  // clobbered by a stale skip decision.
707
+ //
708
+ // Catching up leaves the conflict record alone: it names entries whose
709
+ // base change this branch's version replaced, which stays true until
710
+ // the branch merges (see recordConflicts). A lingering rebaseFailure
711
+ // is cleared, or it sticks as a stale warning forever: nothing else
712
+ // touches a caught-up branch, so no other save site clears it.
694
713
  const currentMeta = await BranchMetadataFileManager.loadOnly(branchPath);
695
- const conflictStatus = currentMeta?.branch.conflictStatus;
696
- const conflictFiles = currentMeta?.branch.conflictFiles;
697
- const conflictAlreadyClean = (conflictStatus === undefined || conflictStatus === 'clean') &&
698
- (conflictFiles === undefined || conflictFiles.length === 0);
699
- // A lingering rebaseFailure must also be cleared once the branch
700
- // catches up clean, or it sticks as a stale warning forever: nothing
701
- // else touches a caught-up branch, so no other save site clears it.
702
- const alreadyClean = conflictAlreadyClean && currentMeta?.branch.rebaseFailure === undefined;
703
- if (alreadyClean) {
714
+ if (currentMeta?.branch.rebaseFailure === undefined) {
704
715
  return { kind: 'none' };
705
716
  }
706
- await meta.save({
707
- branch: {
708
- name: branchDir,
709
- conflictStatus: 'clean',
710
- conflictFiles: [],
711
- rebaseFailure: undefined,
712
- },
713
- });
717
+ await meta.save({ branch: { name: branchDir, rebaseFailure: undefined } });
714
718
  return { kind: 'none' };
715
719
  }
716
720
  workerLog(`Rebasing ${branchDir} (${behindCount} commits behind)...`);
@@ -720,6 +724,7 @@ async function rebaseOneBranch(ctx, branchDir, branchPath, isProvisioningLockCom
720
724
  // the rebase makes unreadable.
721
725
  const preRebaseHead = (await branchGit.revparse(['HEAD'])).trim();
722
726
  const publishedSha = canPublish ? await readPublishedSha(ctx, branchRef) : null;
727
+ await assertNoIncomingSubmodules(branchGit, branchPath, preRebaseHead, fetchedBaseTip);
723
728
  const { completed, conflictedFiles, failureReason } = await runRebaseRounds(ctx, branchGit, branchDir, fetchedBaseTip, lockCompromised);
724
729
  // Outside the round loop's try/catch, so a throwing test hook can
725
730
  // never be misread as a rebase error.
@@ -785,16 +790,15 @@ async function rebaseOneBranch(ctx, branchDir, branchPath, isProvisioningLockCom
785
790
  workerLog(hadConflicts
786
791
  ? ` Rebased ${branchDir} (kept branch version for ${conflictIdsDeduped.length} conflicting file(s))`
787
792
  : ` Rebased ${branchDir} successfully`);
788
- await meta.save({
793
+ await meta.update((existing) => ({
789
794
  branch: {
790
795
  name: branchDir,
791
- conflictStatus: hadConflicts ? 'conflicts-detected' : 'clean',
792
- conflictFiles: conflictIdsDeduped,
796
+ ...recordConflicts(existing?.branch.conflictFiles, conflictIdsDeduped),
793
797
  // The cycle completed, so clear any prior failure record regardless
794
798
  // of the conflict outcome.
795
799
  rebaseFailure: undefined,
796
800
  },
797
- });
801
+ }));
798
802
  didRebase = true;
799
803
  await carryForwardRewrittenHistory(ctx, {
800
804
  branchPath,
@@ -0,0 +1,3 @@
1
+ import { type BareRemoteRepackResult } from '../git-manager.js';
2
+ /** The worker's per-cycle repack of `remote.git` (git-manager.ts owns the rule), logged. */
3
+ export declare function maintainRemoteGit(gitDir: string): Promise<BareRemoteRepackResult>;
@@ -0,0 +1,12 @@
1
+ import { repackBareRemoteIfNeeded } from '../git-manager.js';
2
+ import { workerLog } from './log.js';
3
+ import { sharedRepoGit } from './shared-repo-git.js';
4
+ /** The worker's per-cycle repack of `remote.git` (git-manager.ts owns the rule), logged. */
5
+ export async function maintainRemoteGit(gitDir) {
6
+ const result = await repackBareRemoteIfNeeded(gitDir, sharedRepoGit(gitDir, 'bare'));
7
+ if (result.repacked) {
8
+ workerLog(`remote.git maintenance: loose ${result.before.loose}→${result.after.loose} ` +
9
+ `packs ${result.before.packs}→${result.after.packs} in ${result.ms} ms`);
10
+ }
11
+ return result;
12
+ }
@@ -0,0 +1,49 @@
1
+ import type { simpleGit } from 'simple-git';
2
+ import type { BaseSchemaHold } from '../types.js';
3
+ /** How long the base branch may wait for its oldest missing schema before the worker advances it anyway. */
4
+ export declare const DEFAULT_SCHEMA_HOLD_MAX_MS: number;
5
+ type Git = ReturnType<typeof simpleGit>;
6
+ export type SchemaGateDecision = {
7
+ kind: 'advance';
8
+ }
9
+ /** Advance anyway: the oldest missing schema has waited its bound. `hold` names them all. */
10
+ | {
11
+ kind: 'advance-expired';
12
+ hold: BaseSchemaHold;
13
+ } | {
14
+ kind: 'hold';
15
+ hold: BaseSchemaHold;
16
+ };
17
+ /**
18
+ * The hold in the status file a previous worker process left, so a restart or a lock takeover
19
+ * keeps each schema's first-seen time and cannot reset the bound. Tolerant: an unreadable or
20
+ * malformed file carries nothing.
21
+ */
22
+ export declare function readCarriedBaseHold(taskDir: string): Promise<BaseSchemaHold | undefined>;
23
+ /**
24
+ * Whether the base branch may fast-forward from `currentSha` to `incomingSha` while the serving
25
+ * editor runs the registry in the schema-registry record (schema-registry-record.ts).
26
+ *
27
+ * Holds only for a name that a `.collection.json` changed between the two tips references at the
28
+ * incoming one, that the record lacks, and that the current base does not already reference:
29
+ * holding cannot repair a reference already live, and a merge that changes no collection meta
30
+ * reads no meta blob but those of symlinks.
31
+ *
32
+ * Fails open: with no readable record (an editor too old to write one, one not yet started, a
33
+ * failed write), or when the trees cannot be read, the base advances as if there were no gate.
34
+ * When each name was first seen missing is carried in `previous.firstSeen` across cycles and
35
+ * worker restarts; once the oldest missing name has waited `maxHoldMs` the base advances with an
36
+ * error naming them, since a name no deploy will ever supply would otherwise freeze every content
37
+ * update.
38
+ */
39
+ export declare function decideBaseAdvance(input: {
40
+ git: Git;
41
+ contentBranchesPath: string;
42
+ baseBranch: string;
43
+ currentSha: string;
44
+ incomingSha: string;
45
+ previous: BaseSchemaHold | undefined;
46
+ maxHoldMs: number;
47
+ now?: Date;
48
+ }): Promise<SchemaGateDecision>;
49
+ export {};
@@ -0,0 +1,226 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { readSchemaRegistryRecord } from '../schema-registry-record.js';
4
+ import { WORKER_STATUS_FILE } from '../task-queue/worker-status.js';
5
+ import { getErrorMessage } from '../utils/error.js';
6
+ import { MAX_REPORTED_PATHS } from './canopy-state.js';
7
+ import { workerLogError, workerLogWarn } from './log.js';
8
+ /** How long the base branch may wait for its oldest missing schema before the worker advances it anyway. */
9
+ export const DEFAULT_SCHEMA_HOLD_MAX_MS = 30 * 60_000;
10
+ const COLLECTION_META_FILE = '.collection.json';
11
+ const SYMLINK_MODE = '120000';
12
+ /**
13
+ * `contentRoot` as a literal pathspec: repo-relative with no `./` or trailing slash, or null for
14
+ * the repository root. Literal, so a directory named like pathspec magic (`:x`) reads as itself.
15
+ */
16
+ function contentPathspec(contentRoot) {
17
+ const normalized = path.posix.normalize(contentRoot.replace(/\\/g, '/')).replace(/\/+$/, '');
18
+ return normalized === '.' || normalized === '' ? null : `:(literal)${normalized}`;
19
+ }
20
+ function withPathspec(args, pathspec) {
21
+ return pathspec ? [...args, '--', pathspec] : args;
22
+ }
23
+ /** Schema names a `.collection.json` blob's entry types reference; none when it does not parse. */
24
+ function referencedSchemas(raw) {
25
+ let parsed;
26
+ try {
27
+ parsed = JSON.parse(raw);
28
+ }
29
+ catch {
30
+ return [];
31
+ }
32
+ if (typeof parsed !== 'object' || parsed === null || !('entries' in parsed))
33
+ return [];
34
+ const { entries } = parsed;
35
+ if (!Array.isArray(entries))
36
+ return [];
37
+ const names = [];
38
+ for (const entry of entries) {
39
+ if (typeof entry !== 'object' || entry === null || !('schema' in entry))
40
+ continue;
41
+ if (typeof entry.schema === 'string' && entry.schema.length > 0)
42
+ names.push(entry.schema);
43
+ }
44
+ return names;
45
+ }
46
+ /** Every `.collection.json` under `pathspec` at `commit`, by repo-relative path. */
47
+ async function listCollectionMeta(git, commit, pathspec) {
48
+ const listing = await git.raw(withPathspec(['ls-tree', '-r', '-z', '--full-tree', commit], pathspec));
49
+ const entries = new Map();
50
+ for (const line of listing.split('\0')) {
51
+ // `<mode> <type> <oid>\t<path>`
52
+ const tab = line.indexOf('\t');
53
+ if (tab < 0)
54
+ continue;
55
+ const filePath = line.slice(tab + 1);
56
+ if (path.posix.basename(filePath) !== COLLECTION_META_FILE)
57
+ continue;
58
+ const [mode, type, oid] = line.slice(0, tab).split(' ');
59
+ if (type === 'blob' && mode && oid)
60
+ entries.set(filePath, { mode, oid });
61
+ }
62
+ return entries;
63
+ }
64
+ /**
65
+ * The schema names one meta file references at `commit`. A symlink is followed one hop, to a
66
+ * relative target inside the repository; the editor's `fs.readFile` follows every hop, so a
67
+ * longer chain or an absolute target reads as no references and fails open.
68
+ */
69
+ async function metaReferences(git, commit, filePath, entry, blobCache) {
70
+ if (entry.mode !== SYMLINK_MODE) {
71
+ const cached = blobCache.get(entry.oid);
72
+ if (cached)
73
+ return cached;
74
+ const names = referencedSchemas(await git.raw(['cat-file', 'blob', entry.oid]));
75
+ blobCache.set(entry.oid, names);
76
+ return names;
77
+ }
78
+ const linkText = await git.raw(['cat-file', 'blob', entry.oid]);
79
+ const target = path.posix.normalize(path.posix.join(path.posix.dirname(filePath), linkText));
80
+ if (target.startsWith('../') || path.posix.isAbsolute(target))
81
+ return [];
82
+ try {
83
+ return referencedSchemas(await git.raw(['cat-file', 'blob', `${commit}:${target}`]));
84
+ }
85
+ catch {
86
+ return [];
87
+ }
88
+ }
89
+ const isString = (value) => typeof value === 'string';
90
+ const isStringArray = (value) => Array.isArray(value) && value.every(isString);
91
+ function isBaseSchemaHold(value) {
92
+ if (typeof value !== 'object' || value === null)
93
+ return false;
94
+ const hold = value;
95
+ const { firstSeen, editorBuild } = hold;
96
+ return (isString(hold.since) &&
97
+ typeof firstSeen === 'object' &&
98
+ firstSeen !== null &&
99
+ Object.values(firstSeen).every(isString) &&
100
+ isString(hold.incomingSha) &&
101
+ isStringArray(hold.missingSchemas) &&
102
+ isStringArray(hold.files) &&
103
+ typeof hold.fileCount === 'number' &&
104
+ typeof editorBuild === 'object' &&
105
+ editorBuild !== null &&
106
+ 'canopycmsVersion' in editorBuild &&
107
+ isString(editorBuild.canopycmsVersion) &&
108
+ isString(hold.editorRecordedAt) &&
109
+ (hold.expired === undefined || hold.expired === true));
110
+ }
111
+ /**
112
+ * The hold in the status file a previous worker process left, so a restart or a lock takeover
113
+ * keeps each schema's first-seen time and cannot reset the bound. Tolerant: an unreadable or
114
+ * malformed file carries nothing.
115
+ */
116
+ export async function readCarriedBaseHold(taskDir) {
117
+ try {
118
+ const parsed = JSON.parse(await fs.readFile(path.join(taskDir, WORKER_STATUS_FILE), 'utf-8'));
119
+ if (typeof parsed !== 'object' || parsed === null || !('baseHold' in parsed))
120
+ return undefined;
121
+ return isBaseSchemaHold(parsed.baseHold) ? parsed.baseHold : undefined;
122
+ }
123
+ catch {
124
+ return undefined;
125
+ }
126
+ }
127
+ /**
128
+ * Whether the base branch may fast-forward from `currentSha` to `incomingSha` while the serving
129
+ * editor runs the registry in the schema-registry record (schema-registry-record.ts).
130
+ *
131
+ * Holds only for a name that a `.collection.json` changed between the two tips references at the
132
+ * incoming one, that the record lacks, and that the current base does not already reference:
133
+ * holding cannot repair a reference already live, and a merge that changes no collection meta
134
+ * reads no meta blob but those of symlinks.
135
+ *
136
+ * Fails open: with no readable record (an editor too old to write one, one not yet started, a
137
+ * failed write), or when the trees cannot be read, the base advances as if there were no gate.
138
+ * When each name was first seen missing is carried in `previous.firstSeen` across cycles and
139
+ * worker restarts; once the oldest missing name has waited `maxHoldMs` the base advances with an
140
+ * error naming them, since a name no deploy will ever supply would otherwise freeze every content
141
+ * update.
142
+ */
143
+ export async function decideBaseAdvance(input) {
144
+ const { git, baseBranch, currentSha, incomingSha, previous, maxHoldMs } = input;
145
+ const now = input.now ?? new Date();
146
+ let record;
147
+ try {
148
+ record = await readSchemaRegistryRecord(input.contentBranchesPath);
149
+ }
150
+ catch (err) {
151
+ workerLogWarn(`Schema gate (${baseBranch}): could not read the schema-registry record, not holding: ${getErrorMessage(err)}`);
152
+ return { kind: 'advance' };
153
+ }
154
+ if (!record)
155
+ return { kind: 'advance' };
156
+ // Name -> the incoming meta files referencing it, for names the record lacks.
157
+ const missing = new Map();
158
+ try {
159
+ const pathspec = contentPathspec(record.contentRoot);
160
+ const diff = await git.raw(withPathspec(['diff-tree', '-r', '-z', '--name-only', '--no-renames', currentSha, incomingSha], pathspec));
161
+ const changed = new Set(diff.split('\0').filter((file) => path.posix.basename(file) === COLLECTION_META_FILE));
162
+ const incomingMeta = await listCollectionMeta(git, incomingSha, pathspec);
163
+ // A symlink's target can change without the link itself changing.
164
+ for (const [file, entry] of incomingMeta)
165
+ if (entry.mode === SYMLINK_MODE)
166
+ changed.add(file);
167
+ const known = new Set(record.schemas);
168
+ const blobCache = new Map();
169
+ for (const file of changed) {
170
+ const entry = incomingMeta.get(file);
171
+ if (!entry)
172
+ continue;
173
+ for (const name of await metaReferences(git, incomingSha, file, entry, blobCache)) {
174
+ if (!known.has(name))
175
+ missing.set(name, [...(missing.get(name) ?? []), file]);
176
+ }
177
+ }
178
+ if (missing.size > 0) {
179
+ for (const [file, entry] of await listCollectionMeta(git, currentSha, pathspec)) {
180
+ for (const name of await metaReferences(git, currentSha, file, entry, blobCache)) {
181
+ missing.delete(name);
182
+ }
183
+ }
184
+ }
185
+ }
186
+ catch (err) {
187
+ workerLogWarn(`Schema gate (${baseBranch}): could not read collection meta, not holding: ${getErrorMessage(err)}`);
188
+ return { kind: 'advance' };
189
+ }
190
+ if (missing.size === 0)
191
+ return { kind: 'advance' };
192
+ const missingSchemas = [...missing.keys()].sort();
193
+ const carried = previous?.firstSeen ?? {};
194
+ const firstSeenAt = missingSchemas.map((name) => {
195
+ const at = Object.prototype.hasOwnProperty.call(carried, name)
196
+ ? Date.parse(carried[name])
197
+ : Number.NaN;
198
+ // An unparseable or future carried time restarts that name's wait, never freezes it.
199
+ return [name, Number.isFinite(at) && at <= now.getTime() ? at : now.getTime()];
200
+ });
201
+ // The oldest missing name bounds the hold: past its bound, holding cannot help it, and
202
+ // waiting on newer names as well would let a stream of schema merges hold the base for good.
203
+ const sinceMs = Math.min(...firstSeenAt.map(([, at]) => at));
204
+ const waiting = now.getTime() - sinceMs < maxHoldMs;
205
+ const files = [...new Set([...missing.values()].flat())].sort();
206
+ const hold = {
207
+ since: new Date(sinceMs).toISOString(),
208
+ // fromEntries defines own properties, so a name like `__proto__` is recorded as itself.
209
+ firstSeen: Object.fromEntries(firstSeenAt.map(([name, at]) => [name, new Date(at).toISOString()])),
210
+ incomingSha,
211
+ missingSchemas,
212
+ files: files.slice(0, MAX_REPORTED_PATHS),
213
+ fileCount: files.length,
214
+ editorBuild: record.build,
215
+ editorRecordedAt: record.recordedAt,
216
+ };
217
+ if (waiting)
218
+ return { kind: 'hold', hold };
219
+ workerLogError(`Schema gate (${baseBranch}): held since ${hold.since} for ${missingSchemas.join(', ')} ` +
220
+ `(referenced by ${hold.files.join(', ')}${files.length > hold.files.length ? ', …' : ''}), ` +
221
+ `which the serving editor` +
222
+ `${record.build.sourceRevision ? ` (built from ${record.build.sourceRevision})` : ''} ` +
223
+ `does not define. Advancing anyway; content types using them are unavailable until an editor ` +
224
+ `image defining them is deployed.`);
225
+ return { kind: 'advance-expired', hold: { ...hold, expired: true } };
226
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * The boot self-checks of the worker process split (.claude/future-tasks/resolved/
3
+ * worker-shared-repo-git-process-split.md §5): each proves a sandbox layer the split rests on took
4
+ * effect, because systemd applies some directives silently or not at all (`IPAddressDeny` without
5
+ * cgroup-BPF). Every check fails closed: what it cannot read counts against it. They only report;
6
+ * the entrypoint that runs them decides to stop (D3: they are fatal).
7
+ *
8
+ * Every path is read under `root` (`/` on an instance), so a test can stand in a fake tree.
9
+ */
10
+ /** One check's outcome. `detail` names what was seen, never a secret's value. */
11
+ export interface SelfCheckResult {
12
+ check: SelfCheckName;
13
+ ok: boolean;
14
+ detail: string;
15
+ }
16
+ export type SelfCheckName = 'imds' | 'network-namespace' | 'proc' | 'home' | 'secrets-in-env' | 'efs-unreadable' | 'core-pattern' | 'core-limit' | 'socket-access';
17
+ /**
18
+ * How a TCP connect ended: it connected, it failed with an errno code, or it timed out.
19
+ * @internal Exported for tests.
20
+ */
21
+ export type ConnectOutcome = 'connected' | 'timeout' | {
22
+ code: string;
23
+ };
24
+ export interface SelfCheckEnvironment {
25
+ /** Where `/proc`, `/home` and `/mnt/efs` are read from. Default `/`. */
26
+ root?: string;
27
+ /** The process environment the secret check reads. Default `process.env`. */
28
+ env?: Readonly<Record<string, string | undefined>>;
29
+ /** The uid every visible process must have. Default this process's. */
30
+ uid?: number;
31
+ /** Test seam: the TCP connect the IMDS check makes. */
32
+ connect?: (host: string, port: number, timeoutMs: number) => Promise<ConnectOutcome>;
33
+ /**
34
+ * The gateway's listening descriptor when systemd passed it (socket activation), which the
35
+ * `socket-access` check reads. Unset, that check is skipped: a gateway that binds its own path
36
+ * owns the socket by construction (development).
37
+ */
38
+ listenFd?: number;
39
+ }
40
+ /** The worker unit's checks, in the plan's order. */
41
+ export declare function workerSelfChecks(environment?: SelfCheckEnvironment): Promise<SelfCheckResult[]>;
42
+ /** The gateway unit's checks. */
43
+ export declare function gatewaySelfChecks(environment?: SelfCheckEnvironment): Promise<SelfCheckResult[]>;
44
+ /** Throw one error naming every failed check, or return. */
45
+ export declare function assertSelfChecks(results: readonly SelfCheckResult[]): void;
46
+ /**
47
+ * A TCP connect to IMDS fails with no route (`ENETUNREACH`, or `EAFNOSUPPORT` under
48
+ * `RestrictAddressFamilies=AF_UNIX`) or times out. Any other outcome, a refusal included, means a
49
+ * route exists.
50
+ * @internal Exported for tests.
51
+ */
52
+ export declare function imdsUnreachable(environment?: SelfCheckEnvironment): Promise<SelfCheckResult>;
53
+ /**
54
+ * `/proc/self/net/dev` lists only `lo`: `PrivateNetwork=` took effect. Not `/sys/class/net`, which
55
+ * `PrivateNetwork=` does not remount, so it shows the host's interfaces even inside the cut.
56
+ * @internal Exported for tests.
57
+ */
58
+ export declare function onlyLoopback(environment?: SelfCheckEnvironment): Promise<SelfCheckResult>;
59
+ /**
60
+ * Every process `/proc` lists is this uid's: `ProtectProc=invisible` took effect.
61
+ * @internal Exported for tests.
62
+ */
63
+ export declare function procShowsOnlyOwnProcesses(environment?: SelfCheckEnvironment): Promise<SelfCheckResult>;
64
+ /**
65
+ * `/home` is empty: `ProtectHome=tmpfs`'s always-observable effect. `/run/user`, which it also
66
+ * hides, is empty anyway whenever nobody is logged in.
67
+ * @internal Exported for tests.
68
+ */
69
+ export declare function homeIsEmpty(environment?: SelfCheckEnvironment): Promise<SelfCheckResult>;
70
+ /**
71
+ * No GitHub or Clerk secret, nor the ARN or JSON field naming one, is in the worker's environment:
72
+ * they belong to the gateway's env file. Nor is a conventional credential variable (a GitHub token,
73
+ * the AWS SDK's static credentials). Names only are reported.
74
+ * @internal Exported for tests.
75
+ */
76
+ export declare function noSecretsInEnv(environment?: SelfCheckEnvironment): SelfCheckResult;
77
+ /**
78
+ * The gateway cannot list `/mnt/efs`: `InaccessiblePaths=/mnt/efs` took effect.
79
+ * @internal Exported for tests.
80
+ */
81
+ export declare function efsUnreadable(environment?: SelfCheckEnvironment): Promise<SelfCheckResult>;
82
+ /**
83
+ * `kernel.core_pattern` does not begin with `|`: a piped pattern would hand a crashing gateway
84
+ * git's memory (the credential file's contents) to a helper.
85
+ * @internal Exported for tests.
86
+ */
87
+ export declare function corePatternNotPiped(environment?: SelfCheckEnvironment): Promise<SelfCheckResult>;
88
+ /**
89
+ * `/proc/self/limits` shows a zero core limit, soft and hard: `LimitCORE=0` took effect.
90
+ * @internal Exported for tests.
91
+ */
92
+ export declare function coreLimitZero(environment?: SelfCheckEnvironment): Promise<SelfCheckResult>;
93
+ /**
94
+ * The socket systemd passed is one only another user (and root) can connect to: its node is not
95
+ * this process's uid, and grants no group or other access. Whoever owns it is the worker, so a
96
+ * gateway running as that user (a unit without `User=canopy-github`) would hand the worker its
97
+ * own environment and memory. A socket's own descriptor reports the creating process (systemd,
98
+ * root), not the node's owner, so the node is found by the socket's inode in
99
+ * `/proc/self/net/unix`.
100
+ * @internal Exported for tests.
101
+ */
102
+ export declare function socketAccessIsOthers(environment?: SelfCheckEnvironment): Promise<SelfCheckResult>;
103
+ /**
104
+ * One TCP connect, as the IMDS check makes it.
105
+ * @internal Exported for tests.
106
+ */
107
+ export declare function tcpConnect(host: string, port: number, timeoutMs: number): Promise<ConnectOutcome>;