canopycms 0.0.59 → 0.0.61-int.74

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 (286) hide show
  1. package/dist/api/__test__/mock-client.d.ts +29 -0
  2. package/dist/api/__test__/mock-client.d.ts.map +1 -1
  3. package/dist/api/__test__/mock-client.js +55 -4
  4. package/dist/api/__test__/mock-client.js.map +1 -1
  5. package/dist/api/admin-branch-health.d.ts +65 -0
  6. package/dist/api/admin-branch-health.d.ts.map +1 -0
  7. package/dist/api/admin-branch-health.js +448 -0
  8. package/dist/api/admin-branch-health.js.map +1 -0
  9. package/dist/api/admin.d.ts +136 -0
  10. package/dist/api/admin.d.ts.map +1 -0
  11. package/dist/api/admin.js +338 -0
  12. package/dist/api/admin.js.map +1 -0
  13. package/dist/api/assets.d.ts.map +1 -1
  14. package/dist/api/assets.js +4 -0
  15. package/dist/api/assets.js.map +1 -1
  16. package/dist/api/branch-merge.d.ts +6 -0
  17. package/dist/api/branch-merge.d.ts.map +1 -1
  18. package/dist/api/branch-merge.js +15 -9
  19. package/dist/api/branch-merge.js.map +1 -1
  20. package/dist/api/branch-status.d.ts.map +1 -1
  21. package/dist/api/branch-status.js +15 -5
  22. package/dist/api/branch-status.js.map +1 -1
  23. package/dist/api/branch-withdraw.d.ts +6 -0
  24. package/dist/api/branch-withdraw.d.ts.map +1 -1
  25. package/dist/api/branch-withdraw.js +34 -7
  26. package/dist/api/branch-withdraw.js.map +1 -1
  27. package/dist/api/branch.d.ts +11 -1
  28. package/dist/api/branch.d.ts.map +1 -1
  29. package/dist/api/branch.js +70 -4
  30. package/dist/api/branch.js.map +1 -1
  31. package/dist/api/client.d.ts +34 -0
  32. package/dist/api/client.d.ts.map +1 -1
  33. package/dist/api/client.js +47 -0
  34. package/dist/api/client.js.map +1 -1
  35. package/dist/api/comments.d.ts.map +1 -1
  36. package/dist/api/comments.js +4 -0
  37. package/dist/api/comments.js.map +1 -1
  38. package/dist/api/content.d.ts.map +1 -1
  39. package/dist/api/content.js +4 -4
  40. package/dist/api/content.js.map +1 -1
  41. package/dist/api/entries.d.ts.map +1 -1
  42. package/dist/api/entries.js +23 -4
  43. package/dist/api/entries.js.map +1 -1
  44. package/dist/api/github-sync.d.ts.map +1 -1
  45. package/dist/api/github-sync.js +14 -3
  46. package/dist/api/github-sync.js.map +1 -1
  47. package/dist/api/groups.d.ts +3 -0
  48. package/dist/api/groups.d.ts.map +1 -1
  49. package/dist/api/groups.js +89 -68
  50. package/dist/api/groups.js.map +1 -1
  51. package/dist/api/guards.d.ts +7 -1
  52. package/dist/api/guards.d.ts.map +1 -1
  53. package/dist/api/guards.js +54 -0
  54. package/dist/api/guards.js.map +1 -1
  55. package/dist/api/index.d.ts +1 -0
  56. package/dist/api/index.d.ts.map +1 -1
  57. package/dist/api/index.js +0 -1
  58. package/dist/api/index.js.map +1 -1
  59. package/dist/api/permissions.d.ts +1 -0
  60. package/dist/api/permissions.d.ts.map +1 -1
  61. package/dist/api/permissions.js +23 -20
  62. package/dist/api/permissions.js.map +1 -1
  63. package/dist/api/schema.d.ts.map +1 -1
  64. package/dist/api/schema.js +36 -22
  65. package/dist/api/schema.js.map +1 -1
  66. package/dist/authorization/branch.d.ts +14 -1
  67. package/dist/authorization/branch.d.ts.map +1 -1
  68. package/dist/authorization/branch.js +5 -3
  69. package/dist/authorization/branch.js.map +1 -1
  70. package/dist/authorization/groups/index.d.ts +1 -1
  71. package/dist/authorization/groups/index.d.ts.map +1 -1
  72. package/dist/authorization/groups/index.js +1 -1
  73. package/dist/authorization/groups/index.js.map +1 -1
  74. package/dist/authorization/groups/loader.d.ts +25 -4
  75. package/dist/authorization/groups/loader.d.ts.map +1 -1
  76. package/dist/authorization/groups/loader.js +39 -20
  77. package/dist/authorization/groups/loader.js.map +1 -1
  78. package/dist/authorization/groups/schema.d.ts +8 -7
  79. package/dist/authorization/groups/schema.d.ts.map +1 -1
  80. package/dist/authorization/groups/schema.js +8 -4
  81. package/dist/authorization/groups/schema.js.map +1 -1
  82. package/dist/authorization/index.d.ts +5 -3
  83. package/dist/authorization/index.d.ts.map +1 -1
  84. package/dist/authorization/index.js +8 -3
  85. package/dist/authorization/index.js.map +1 -1
  86. package/dist/authorization/path.d.ts +8 -0
  87. package/dist/authorization/path.d.ts.map +1 -1
  88. package/dist/authorization/path.js +16 -1
  89. package/dist/authorization/path.js.map +1 -1
  90. package/dist/authorization/permissions/index.d.ts +1 -1
  91. package/dist/authorization/permissions/index.d.ts.map +1 -1
  92. package/dist/authorization/permissions/index.js +1 -1
  93. package/dist/authorization/permissions/index.js.map +1 -1
  94. package/dist/authorization/permissions/loader.d.ts +18 -4
  95. package/dist/authorization/permissions/loader.d.ts.map +1 -1
  96. package/dist/authorization/permissions/loader.js +35 -24
  97. package/dist/authorization/permissions/loader.js.map +1 -1
  98. package/dist/authorization/permissions/schema.d.ts +8 -7
  99. package/dist/authorization/permissions/schema.d.ts.map +1 -1
  100. package/dist/authorization/permissions/schema.js +8 -4
  101. package/dist/authorization/permissions/schema.js.map +1 -1
  102. package/dist/authorization/protected-branch.d.ts +39 -0
  103. package/dist/authorization/protected-branch.d.ts.map +1 -0
  104. package/dist/authorization/protected-branch.js +43 -0
  105. package/dist/authorization/protected-branch.js.map +1 -0
  106. package/dist/authorization/settings-file-store.d.ts +127 -0
  107. package/dist/authorization/settings-file-store.d.ts.map +1 -0
  108. package/dist/authorization/settings-file-store.js +156 -0
  109. package/dist/authorization/settings-file-store.js.map +1 -0
  110. package/dist/branch-health.d.ts +78 -0
  111. package/dist/branch-health.d.ts.map +1 -0
  112. package/dist/branch-health.js +151 -0
  113. package/dist/branch-health.js.map +1 -0
  114. package/dist/branch-metadata.d.ts +31 -0
  115. package/dist/branch-metadata.d.ts.map +1 -1
  116. package/dist/branch-metadata.js +43 -1
  117. package/dist/branch-metadata.js.map +1 -1
  118. package/dist/branch-registry.d.ts.map +1 -1
  119. package/dist/branch-registry.js +13 -1
  120. package/dist/branch-registry.js.map +1 -1
  121. package/dist/cli/cli.js +398 -200
  122. package/dist/cli/generate-ai-content.js +72 -13
  123. package/dist/cli/init.js +84 -2
  124. package/dist/cli/migrate.d.ts.map +1 -1
  125. package/dist/cli/migrate.js +39 -11
  126. package/dist/cli/migrate.js.map +1 -1
  127. package/dist/cli/sync.d.ts.map +1 -1
  128. package/dist/cli/sync.js +4 -1
  129. package/dist/cli/sync.js.map +1 -1
  130. package/dist/config/index.d.ts +1 -1
  131. package/dist/config/index.d.ts.map +1 -1
  132. package/dist/config/index.js.map +1 -1
  133. package/dist/config/schemas/config.d.ts +55 -5
  134. package/dist/config/schemas/config.d.ts.map +1 -1
  135. package/dist/config/schemas/config.js +18 -1
  136. package/dist/config/schemas/config.js.map +1 -1
  137. package/dist/config/types.d.ts +11 -1
  138. package/dist/config/types.d.ts.map +1 -1
  139. package/dist/content-store.d.ts +16 -0
  140. package/dist/content-store.d.ts.map +1 -1
  141. package/dist/content-store.js +22 -6
  142. package/dist/content-store.js.map +1 -1
  143. package/dist/context.d.ts +3 -0
  144. package/dist/context.d.ts.map +1 -1
  145. package/dist/context.js +25 -3
  146. package/dist/context.js.map +1 -1
  147. package/dist/editor/BranchManager.d.ts +11 -0
  148. package/dist/editor/BranchManager.d.ts.map +1 -1
  149. package/dist/editor/BranchManager.js +34 -10
  150. package/dist/editor/BranchManager.js.map +1 -1
  151. package/dist/editor/CommentsPanel.d.ts.map +1 -1
  152. package/dist/editor/CommentsPanel.js +4 -12
  153. package/dist/editor/CommentsPanel.js.map +1 -1
  154. package/dist/editor/Editor.d.ts.map +1 -1
  155. package/dist/editor/Editor.js +46 -10
  156. package/dist/editor/Editor.js.map +1 -1
  157. package/dist/editor/EntryNavigator.d.ts +7 -0
  158. package/dist/editor/EntryNavigator.d.ts.map +1 -1
  159. package/dist/editor/EntryNavigator.js +17 -5
  160. package/dist/editor/EntryNavigator.js.map +1 -1
  161. package/dist/editor/FormRenderer.d.ts.map +1 -1
  162. package/dist/editor/FormRenderer.js +4 -0
  163. package/dist/editor/FormRenderer.js.map +1 -1
  164. package/dist/editor/admin/SystemHealthPanel.d.ts +6 -0
  165. package/dist/editor/admin/SystemHealthPanel.d.ts.map +1 -0
  166. package/dist/editor/admin/SystemHealthPanel.js +234 -0
  167. package/dist/editor/admin/SystemHealthPanel.js.map +1 -0
  168. package/dist/editor/admin/useSystemHealth.d.ts +40 -0
  169. package/dist/editor/admin/useSystemHealth.d.ts.map +1 -0
  170. package/dist/editor/admin/useSystemHealth.js +205 -0
  171. package/dist/editor/admin/useSystemHealth.js.map +1 -0
  172. package/dist/editor/comments/FieldWrapper.d.ts.map +1 -1
  173. package/dist/editor/comments/FieldWrapper.js +1 -1
  174. package/dist/editor/comments/FieldWrapper.js.map +1 -1
  175. package/dist/editor/comments/InlineCommentThread.d.ts.map +1 -1
  176. package/dist/editor/comments/InlineCommentThread.js +2 -21
  177. package/dist/editor/comments/InlineCommentThread.js.map +1 -1
  178. package/dist/editor/comments/ThreadCarousel.d.ts +2 -0
  179. package/dist/editor/comments/ThreadCarousel.d.ts.map +1 -1
  180. package/dist/editor/comments/ThreadCarousel.js +5 -2
  181. package/dist/editor/comments/ThreadCarousel.js.map +1 -1
  182. package/dist/editor/components/EditorHeader.d.ts +11 -0
  183. package/dist/editor/components/EditorHeader.d.ts.map +1 -1
  184. package/dist/editor/components/EditorHeader.js +75 -41
  185. package/dist/editor/components/EditorHeader.js.map +1 -1
  186. package/dist/editor/components/EditorSidebar.d.ts +7 -1
  187. package/dist/editor/components/EditorSidebar.d.ts.map +1 -1
  188. package/dist/editor/components/EditorSidebar.js +3 -3
  189. package/dist/editor/components/EditorSidebar.js.map +1 -1
  190. package/dist/editor/fields/ImageField.d.ts.map +1 -1
  191. package/dist/editor/fields/ImageField.js +4 -1
  192. package/dist/editor/fields/ImageField.js.map +1 -1
  193. package/dist/editor/fields/StringListField.d.ts +20 -0
  194. package/dist/editor/fields/StringListField.d.ts.map +1 -0
  195. package/dist/editor/fields/StringListField.js +25 -0
  196. package/dist/editor/fields/StringListField.js.map +1 -0
  197. package/dist/editor/hooks/useBranchActions.d.ts.map +1 -1
  198. package/dist/editor/hooks/useBranchActions.js +8 -2
  199. package/dist/editor/hooks/useBranchActions.js.map +1 -1
  200. package/dist/editor/hooks/useBranchManager.d.ts +14 -3
  201. package/dist/editor/hooks/useBranchManager.d.ts.map +1 -1
  202. package/dist/editor/hooks/useBranchManager.js +18 -1
  203. package/dist/editor/hooks/useBranchManager.js.map +1 -1
  204. package/dist/editor/hooks/useDraftManager.d.ts +7 -0
  205. package/dist/editor/hooks/useDraftManager.d.ts.map +1 -1
  206. package/dist/editor/hooks/useDraftManager.js +57 -3
  207. package/dist/editor/hooks/useDraftManager.js.map +1 -1
  208. package/dist/editor/hooks/useEntryManager.d.ts.map +1 -1
  209. package/dist/editor/hooks/useEntryManager.js +25 -7
  210. package/dist/editor/hooks/useEntryManager.js.map +1 -1
  211. package/dist/editor/hooks/useGroupManager.d.ts.map +1 -1
  212. package/dist/editor/hooks/useGroupManager.js +13 -2
  213. package/dist/editor/hooks/useGroupManager.js.map +1 -1
  214. package/dist/editor/hooks/usePermissionManager.d.ts.map +1 -1
  215. package/dist/editor/hooks/usePermissionManager.js +13 -2
  216. package/dist/editor/hooks/usePermissionManager.js.map +1 -1
  217. package/dist/editor/media/AssetCard.d.ts.map +1 -1
  218. package/dist/editor/media/AssetCard.js +23 -2
  219. package/dist/editor/media/AssetCard.js.map +1 -1
  220. package/dist/editor/relative-time.d.ts +8 -0
  221. package/dist/editor/relative-time.d.ts.map +1 -0
  222. package/dist/editor/relative-time.js +26 -0
  223. package/dist/editor/relative-time.js.map +1 -0
  224. package/dist/git-manager.d.ts.map +1 -1
  225. package/dist/git-manager.js +16 -2
  226. package/dist/git-manager.js.map +1 -1
  227. package/dist/http/handler.d.ts.map +1 -1
  228. package/dist/http/handler.js +20 -9
  229. package/dist/http/handler.js.map +1 -1
  230. package/dist/http/router.d.ts.map +1 -1
  231. package/dist/http/router.js +2 -0
  232. package/dist/http/router.js.map +1 -1
  233. package/dist/paths/branch-name.d.ts +20 -0
  234. package/dist/paths/branch-name.d.ts.map +1 -0
  235. package/dist/paths/branch-name.js +24 -0
  236. package/dist/paths/branch-name.js.map +1 -0
  237. package/dist/paths/branch.d.ts +2 -8
  238. package/dist/paths/branch.d.ts.map +1 -1
  239. package/dist/paths/branch.js +6 -12
  240. package/dist/paths/branch.js.map +1 -1
  241. package/dist/paths/index.d.ts +2 -1
  242. package/dist/paths/index.d.ts.map +1 -1
  243. package/dist/paths/index.js +4 -1
  244. package/dist/paths/index.js.map +1 -1
  245. package/dist/schema/schema-store.d.ts +98 -0
  246. package/dist/schema/schema-store.d.ts.map +1 -1
  247. package/dist/schema/schema-store.js +187 -29
  248. package/dist/schema/schema-store.js.map +1 -1
  249. package/dist/services.js +9 -1
  250. package/dist/services.js.map +1 -1
  251. package/dist/task-queue/index.d.ts +2 -2
  252. package/dist/task-queue/index.d.ts.map +1 -1
  253. package/dist/task-queue/index.js +1 -1
  254. package/dist/task-queue/index.js.map +1 -1
  255. package/dist/task-queue/task-queue.d.ts +37 -1
  256. package/dist/task-queue/task-queue.d.ts.map +1 -1
  257. package/dist/task-queue/task-queue.js +104 -0
  258. package/dist/task-queue/task-queue.js.map +1 -1
  259. package/dist/task-queue/types.d.ts +11 -0
  260. package/dist/task-queue/types.d.ts.map +1 -1
  261. package/dist/types.d.ts +54 -0
  262. package/dist/types.d.ts.map +1 -1
  263. package/dist/utils/occ-json-write.d.ts.map +1 -1
  264. package/dist/utils/occ-json-write.js +7 -0
  265. package/dist/utils/occ-json-write.js.map +1 -1
  266. package/dist/utils/provisioning-lock.d.ts +19 -0
  267. package/dist/utils/provisioning-lock.d.ts.map +1 -1
  268. package/dist/utils/provisioning-lock.js +26 -0
  269. package/dist/utils/provisioning-lock.js.map +1 -1
  270. package/dist/validation/reference-validator.d.ts +17 -1
  271. package/dist/validation/reference-validator.d.ts.map +1 -1
  272. package/dist/validation/reference-validator.js +33 -10
  273. package/dist/validation/reference-validator.js.map +1 -1
  274. package/dist/worker/cms-worker.d.ts +74 -0
  275. package/dist/worker/cms-worker.d.ts.map +1 -1
  276. package/dist/worker/cms-worker.js +542 -29
  277. package/dist/worker/cms-worker.js.map +1 -1
  278. package/dist/worker/task-queue.d.ts +3 -3
  279. package/dist/worker/task-queue.d.ts.map +1 -1
  280. package/dist/worker/task-queue.js +2 -2
  281. package/dist/worker/task-queue.js.map +1 -1
  282. package/dist/worker/worker-status.d.ts +48 -0
  283. package/dist/worker/worker-status.d.ts.map +1 -0
  284. package/dist/worker/worker-status.js +53 -0
  285. package/dist/worker/worker-status.js.map +1 -0
  286. package/package.json +1 -1
package/dist/cli/cli.js CHANGED
@@ -362,6 +362,36 @@ async function retryTask(taskDir, taskId, error, logger = nullLogger) {
362
362
  });
363
363
  logger.debug("Retrying task", { id: taskId, retryCount, backoffMs });
364
364
  }
365
+ async function requeueFailedTask(taskDir, taskId, logger = nullLogger) {
366
+ const failedPath = path5.join(taskDir, "failed", `${taskId}.json`);
367
+ let content;
368
+ try {
369
+ content = await fs4.readFile(failedPath, "utf-8");
370
+ } catch (err) {
371
+ if (isNotFoundError2(err))
372
+ return { error: "not-found" };
373
+ throw err;
374
+ }
375
+ const original = parseTaskJson(content);
376
+ if (!original)
377
+ return { error: "unparseable" };
378
+ const newTaskId = crypto.randomUUID();
379
+ const newTask = {
380
+ id: newTaskId,
381
+ action: original.action,
382
+ payload: { ...original.payload, requeuedFrom: taskId },
383
+ status: "pending",
384
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
385
+ retryCount: 0,
386
+ maxRetries: original.maxRetries
387
+ };
388
+ const pendingPath = path5.join(taskDir, "pending", `${newTaskId}.json`);
389
+ await atomicWriteFile(pendingPath, JSON.stringify(newTask, null, 2));
390
+ await fs4.unlink(failedPath).catch(() => {
391
+ });
392
+ logger.debug("Requeued failed task", { originalId: taskId, newTaskId });
393
+ return { newTaskId };
394
+ }
365
395
  async function recoverOrphanedTasks(taskDir, maxAgeMs = 5 * 6e4, logger = nullLogger) {
366
396
  const processingDir = path5.join(taskDir, "processing");
367
397
  const pendingDir = path5.join(taskDir, "pending");
@@ -491,6 +521,46 @@ async function getQueueStats(taskDir) {
491
521
  }
492
522
  return stats;
493
523
  }
524
+ async function listCorruptTaskFiles(taskDir, limit = 50, logger = nullLogger) {
525
+ const corruptDir = path5.join(taskDir, "corrupt");
526
+ let files;
527
+ try {
528
+ files = await fs4.readdir(corruptDir);
529
+ } catch (err) {
530
+ if (isNotFoundError2(err))
531
+ return [];
532
+ throw err;
533
+ }
534
+ const entries = [];
535
+ for (const fileName of files.filter((f) => f.endsWith(".json"))) {
536
+ const filePath = path5.join(corruptDir, fileName);
537
+ try {
538
+ const stat = await fs4.stat(filePath);
539
+ entries.push({
540
+ fileName,
541
+ size: stat.size,
542
+ mtime: stat.mtime.toISOString(),
543
+ rawSnippet: await readSnippet(filePath, 500)
544
+ });
545
+ } catch (err) {
546
+ if (isNotFoundError2(err))
547
+ continue;
548
+ logger.debug("Failed to read corrupt task file", { fileName });
549
+ }
550
+ }
551
+ entries.sort((a, b) => new Date(b.mtime).getTime() - new Date(a.mtime).getTime());
552
+ return entries.slice(0, limit);
553
+ }
554
+ async function readSnippet(filePath, maxBytes) {
555
+ const handle = await fs4.open(filePath, "r");
556
+ try {
557
+ const buffer = Buffer.alloc(maxBytes);
558
+ const { bytesRead } = await handle.read(buffer, 0, maxBytes, 0);
559
+ return buffer.subarray(0, bytesRead).toString("utf-8");
560
+ } finally {
561
+ await handle.close();
562
+ }
563
+ }
494
564
  function parseTaskJson(content) {
495
565
  try {
496
566
  const parsed = JSON.parse(content);
@@ -794,7 +864,7 @@ CanopyCMS worker run-once (mode: ${mode}, auth: ${authMode})
794
864
  }
795
865
  console.log("\nDone");
796
866
  }
797
- var __defProp2, __getOwnPropNames2, __esm2, __export2, primitiveFieldTypes, fieldTypes, init_types, fieldBaseSchema, selectOptionSchema, referenceOptionSchema, primitiveFieldSchema, ASPECT_RATIO_RE, imageFieldSchema, selectFieldSchema, referenceFieldSchema, fieldHolder, blockSchema, blockFieldSchema, objectFieldSchema, inlineGroupFieldSchema, customFieldSchema, knownFieldSchema, fieldSchema, init_field, relativePathSchema, entryTypeSchema, collectionSchema, init_collection, mediaSchema, init_media, defaultBranchAccessSchema, defaultPathAccessSchema, defaultBaseBranchSchema, defaultRemoteNameSchema, defaultRemoteUrlSchema, gitBotAuthorNameSchema, gitBotAuthorEmailSchema, githubTokenEnvVarSchema, operatingModeSchema, deployedAsSchema, contentRootSchema, sourceRootSchema, deploymentNameSchema, devContentSyncSchema, devConfigSchema, editorConfigSchema, CanopyConfigSchema, DEFAULT_PROD_WORKSPACE, init_config, permissionTargetSchema, pathPermissionSchema, init_permissions, init_normalize, init_types2, init_flatten, init_validation, init_helpers, init_config2, templates_exports, __dirname, TEMPLATES_DIR, init_templates, task_queue_config_exports, init_task_queue_config, LOG_LEVELS, DebugLogger, testLogger, init_debug, init_atomic_write, DEFAULT_MAX_RETRIES, nullLogger, init_task_queue, init_task_queue2, task_queue_exports, debugLogger, cmsTaskQueueLogger, init_task_queue3, ProdClientSafeStrategy, DevClientSafeStrategy, ProdStrategy, DevStrategy, strategyCache;
867
+ var __defProp2, __getOwnPropNames2, __esm2, __export2, primitiveFieldTypes, fieldTypes, init_types, fieldBaseSchema, selectOptionSchema, referenceOptionSchema, primitiveFieldSchema, ASPECT_RATIO_RE, imageFieldSchema, selectFieldSchema, referenceFieldSchema, fieldHolder, blockSchema, blockFieldSchema, objectFieldSchema, inlineGroupFieldSchema, customFieldSchema, knownFieldSchema, fieldSchema, init_field, relativePathSchema, entryTypeSchema, collectionSchema, init_collection, mediaSchema, init_media, defaultBranchAccessSchema, defaultPathAccessLevelSchema, defaultPathAccessLevelsSchema, defaultPathAccessSchema, defaultBaseBranchSchema, defaultRemoteNameSchema, defaultRemoteUrlSchema, gitBotAuthorNameSchema, gitBotAuthorEmailSchema, githubTokenEnvVarSchema, operatingModeSchema, deployedAsSchema, contentRootSchema, sourceRootSchema, deploymentNameSchema, devContentSyncSchema, devConfigSchema, editorConfigSchema, CanopyConfigSchema, DEFAULT_PROD_WORKSPACE, init_config, permissionTargetSchema, pathPermissionSchema, init_permissions, init_normalize, init_types2, init_flatten, init_validation, init_helpers, init_config2, templates_exports, __dirname, TEMPLATES_DIR, init_templates, task_queue_config_exports, init_task_queue_config, LOG_LEVELS, DebugLogger, testLogger, init_debug, init_atomic_write, DEFAULT_MAX_RETRIES, nullLogger, init_task_queue, init_task_queue2, task_queue_exports, debugLogger, cmsTaskQueueLogger, init_task_queue3, ProdClientSafeStrategy, DevClientSafeStrategy, ProdStrategy, DevStrategy, strategyCache;
798
868
  var init_init = __esm({
799
869
  "dist/cli/init.js"() {
800
870
  "use strict";
@@ -980,7 +1050,13 @@ var init_init = __esm({
980
1050
  init_collection();
981
1051
  init_media();
982
1052
  defaultBranchAccessSchema = z4.enum(["allow", "deny"]).default("deny");
983
- defaultPathAccessSchema = z4.enum(["allow", "deny"]).default("deny");
1053
+ defaultPathAccessLevelSchema = z4.enum(["allow", "deny"]);
1054
+ defaultPathAccessLevelsSchema = z4.object({
1055
+ read: defaultPathAccessLevelSchema.optional(),
1056
+ edit: defaultPathAccessLevelSchema.optional(),
1057
+ review: defaultPathAccessLevelSchema.optional()
1058
+ }).strict();
1059
+ defaultPathAccessSchema = z4.union([defaultPathAccessLevelSchema, defaultPathAccessLevelsSchema]).default("deny");
984
1060
  defaultBaseBranchSchema = z4.string().default("main");
985
1061
  defaultRemoteNameSchema = z4.string().default("origin");
986
1062
  defaultRemoteUrlSchema = z4.string().min(1);
@@ -1013,6 +1089,10 @@ var init_init = __esm({
1013
1089
  // have .default('deny'), so the field is optional on input but always resolves to
1014
1090
  // 'allow'/'deny' (never undefined) on output. An outer .optional() would short-circuit
1015
1091
  // before the inner default runs, defeating the fail-closed default (SCH-M1).
1092
+ // defaultPathAccessSchema's union keeps the same rule: the .default('deny') sits on the
1093
+ // OUTER union, not inside defaultPathAccessLevelsSchema, so an omitted top-level field
1094
+ // still resolves to 'deny' and an omitted level inside the object form stays undefined
1095
+ // (resolved to 'deny' at read time by resolveDefaultPathAccess).
1016
1096
  defaultBranchAccess: defaultBranchAccessSchema,
1017
1097
  defaultPathAccess: defaultPathAccessSchema,
1018
1098
  // .optional() deliberately defeats defaultBaseBranchSchema's .default('main'):
@@ -1261,8 +1341,10 @@ var init_init = __esm({
1261
1341
  getQueueStats: () => getQueueStats,
1262
1342
  getTask: () => getTask,
1263
1343
  getTaskResult: () => getTask,
1344
+ listCorruptTaskFiles: () => listCorruptTaskFiles,
1264
1345
  listTasks: () => listTasks,
1265
1346
  recoverOrphanedTasks: () => recoverOrphanedTasks,
1347
+ requeueFailedTask: () => requeueFailedTask,
1266
1348
  retryTask: () => retryTask
1267
1349
  });
1268
1350
  init_task_queue3 = __esm2({
@@ -3046,6 +3128,8 @@ async function withOccFileLock(filePath, fn) {
3046
3128
  return await fn();
3047
3129
  } finally {
3048
3130
  await release().catch((err) => {
3131
+ if (isNodeError3(err) && err.code === "ENOENT")
3132
+ return;
3049
3133
  log4.warn("lock", `Failed to release lock for ${filePath}`, { error: getErrorMessage3(err) });
3050
3134
  });
3051
3135
  }
@@ -3560,7 +3644,7 @@ async function generateAIContentCLI(options) {
3560
3644
  console.log(` Output: ${result.outputDir}
3561
3645
  `);
3562
3646
  }
3563
- var __defProp3, __getOwnPropNames3, __esm3, __export3, init_normalize2, ROOT_COLLECTION_ID, init_types3, normalizePathValue, flattenSchema, init_flatten2, ProdClientSafeStrategy2, DevClientSafeStrategy2, clientStrategyCache, init_client_safe_strategy, primitiveFieldTypes2, fieldTypes2, init_types22, fieldBaseSchema2, selectOptionSchema2, referenceOptionSchema2, primitiveFieldSchema2, ASPECT_RATIO_RE2, imageFieldSchema2, selectFieldSchema2, referenceFieldSchema2, fieldHolder2, blockSchema2, blockFieldSchema2, objectFieldSchema2, inlineGroupFieldSchema2, customFieldSchema2, knownFieldSchema2, fieldSchema2, init_field2, relativePathSchema2, entryTypeSchema2, collectionSchema2, init_collection2, mediaSchema2, init_media2, defaultBranchAccessSchema2, defaultPathAccessSchema2, defaultBaseBranchSchema2, defaultRemoteNameSchema2, defaultRemoteUrlSchema2, gitBotAuthorNameSchema2, gitBotAuthorEmailSchema2, githubTokenEnvVarSchema2, operatingModeSchema2, deployedAsSchema2, contentRootSchema2, sourceRootSchema2, deploymentNameSchema2, devContentSyncSchema2, devConfigSchema2, editorConfigSchema2, CanopyConfigSchema2, DEFAULT_PROD_WORKSPACE2, init_config3, permissionTargetSchema2, pathPermissionSchema2, init_permissions2, init_validation2, init_helpers2, init_config22, ProdStrategy2, DevStrategy2, strategyCache2, init_client_unsafe_strategy, operating_mode_exports, init_operating_mode, locks, BASE58_PATTERN, CONTENT_ID_PATTERN, PHYSICAL_SEGMENT_PATTERN, isValidId, EMPTY_LOGICAL_PATH, ContentIdIndex, registry, finalization, LOG_LEVELS2, DebugLogger2, testLogger2, log2, META_DIR, entryTypeMetaSchema, collectionMetaSchema, rootCollectionMetaSchema, isDeployedStatic, isBuildMode, STATIC_DEPLOY_USER, SCHEMA_CACHE_VERSION, MTIME_CHECK_DEBOUNCE_MS, SCHEMA_GENERATION_RESOURCE, BranchSchemaCache, RESOURCE, getFormatExtension, isDataOnlyFormat, asRecord, BranchPathError, resolveContentBranchesRoot, ContentStoreError, ContentConflictError, DEFAULT_INDEX_FRESHNESS_INTERVAL_MS, FORCED_REFRESH_MIN_INTERVAL_MS, STALE_LOOKUP, ContentStore, ATTR_CONTENT, BLOCK_PREFIX, BLOCK_SUFFIX, BLOCK_RESTORE_RE, INLINE_PREFIX, INLINE_SUFFIX, INLINE_RESTORE_RE, FLAT_CELL_TYPES, log22, BASE58_CHAR, ENTRY_LINK_PATTERN, log3, REGISTRY_FILE, REGISTRY_STALE_FILE, REGISTRY_TEMP_FILE, REGISTRY_VERSION, RESOURCE2, GET_MISS_REFRESH_MIN_INTERVAL_MS, BranchRegistry, log4, OccWriteConflictError, BRANCH_META_DIR, BRANCH_META_FILE, CURRENT_SCHEMA_VERSION, BranchMetadataConflictError, BranchMetadataFileManager, getBranchMetadataFileManager, loadBranchContext, NETWORK_SCHEME_PATTERN, TRANSPORT_HELPER_PATTERN, LEADING_OPTION_PATTERN, WINDOWS_DRIVE_PATTERN, COLON_BEFORE_SLASH_PATTERN, log5, GIT_ENV_PASSTHROUGH, remoteInitLocks, GitConflictError, GitManager, log6, workspaceInitLocks, BranchWorkspaceManager, log7, normalizePath, readEntryData, parseTypedFilename, listCollectionEntries, MAX_INPUT_PIXELS, KNOWN_FIELD_TYPES, STRING_FIELD_TYPES, selectOptionValues, jiti;
3647
+ var __defProp3, __getOwnPropNames3, __esm3, __export3, init_normalize2, ROOT_COLLECTION_ID, init_types3, normalizePathValue, flattenSchema, init_flatten2, ProdClientSafeStrategy2, DevClientSafeStrategy2, clientStrategyCache, init_client_safe_strategy, primitiveFieldTypes2, fieldTypes2, init_types22, fieldBaseSchema2, selectOptionSchema2, referenceOptionSchema2, primitiveFieldSchema2, ASPECT_RATIO_RE2, imageFieldSchema2, selectFieldSchema2, referenceFieldSchema2, fieldHolder2, blockSchema2, blockFieldSchema2, objectFieldSchema2, inlineGroupFieldSchema2, customFieldSchema2, knownFieldSchema2, fieldSchema2, init_field2, relativePathSchema2, entryTypeSchema2, collectionSchema2, init_collection2, mediaSchema2, init_media2, defaultBranchAccessSchema2, defaultPathAccessLevelSchema2, defaultPathAccessLevelsSchema2, defaultPathAccessSchema2, defaultBaseBranchSchema2, defaultRemoteNameSchema2, defaultRemoteUrlSchema2, gitBotAuthorNameSchema2, gitBotAuthorEmailSchema2, githubTokenEnvVarSchema2, operatingModeSchema2, deployedAsSchema2, contentRootSchema2, sourceRootSchema2, deploymentNameSchema2, devContentSyncSchema2, devConfigSchema2, editorConfigSchema2, CanopyConfigSchema2, DEFAULT_PROD_WORKSPACE2, init_config3, permissionTargetSchema2, pathPermissionSchema2, init_permissions2, init_validation2, init_helpers2, init_config22, ProdStrategy2, DevStrategy2, strategyCache2, init_client_unsafe_strategy, operating_mode_exports, init_operating_mode, locks, BASE58_PATTERN, CONTENT_ID_PATTERN, PHYSICAL_SEGMENT_PATTERN, isValidId, EMPTY_LOGICAL_PATH, ContentIdIndex, registry, finalization, LOG_LEVELS2, DebugLogger2, testLogger2, log2, META_DIR, entryTypeMetaSchema, collectionMetaSchema, rootCollectionMetaSchema, isDeployedStatic, isBuildMode, STATIC_DEPLOY_USER, SCHEMA_CACHE_VERSION, MTIME_CHECK_DEBOUNCE_MS, SCHEMA_GENERATION_RESOURCE, BranchSchemaCache, RESOURCE, getFormatExtension, isDataOnlyFormat, asRecord, BranchPathError, resolveContentBranchesRoot, ContentStoreError, ContentConflictError, DEFAULT_INDEX_FRESHNESS_INTERVAL_MS, FORCED_REFRESH_MIN_INTERVAL_MS, STALE_LOOKUP, ContentStore, ATTR_CONTENT, BLOCK_PREFIX, BLOCK_SUFFIX, BLOCK_RESTORE_RE, INLINE_PREFIX, INLINE_SUFFIX, INLINE_RESTORE_RE, FLAT_CELL_TYPES, log22, BASE58_CHAR, ENTRY_LINK_PATTERN, log3, REGISTRY_FILE, REGISTRY_STALE_FILE, REGISTRY_TEMP_FILE, REGISTRY_VERSION, RESOURCE2, GET_MISS_REFRESH_MIN_INTERVAL_MS, BranchRegistry, log4, OccWriteConflictError, BRANCH_META_DIR, BRANCH_META_FILE, CURRENT_SCHEMA_VERSION, BranchMetadataConflictError, BranchMetadataCorruptError, BranchMetadataFileManager, getBranchMetadataFileManager, loadBranchContext, NETWORK_SCHEME_PATTERN, TRANSPORT_HELPER_PATTERN, LEADING_OPTION_PATTERN, WINDOWS_DRIVE_PATTERN, COLON_BEFORE_SLASH_PATTERN, log5, GIT_ENV_PASSTHROUGH, remoteInitLocks, GitConflictError, GitManager, log6, workspaceInitLocks, BranchWorkspaceManager, log7, normalizePath, readEntryData, parseTypedFilename, listCollectionEntries, MAX_INPUT_PIXELS, KNOWN_FIELD_TYPES, STRING_FIELD_TYPES, selectOptionValues, jiti;
3564
3648
  var init_generate_ai_content = __esm({
3565
3649
  "dist/cli/generate-ai-content.js"() {
3566
3650
  "use strict";
@@ -3924,7 +4008,13 @@ var init_generate_ai_content = __esm({
3924
4008
  init_collection2();
3925
4009
  init_media2();
3926
4010
  defaultBranchAccessSchema2 = z52.enum(["allow", "deny"]).default("deny");
3927
- defaultPathAccessSchema2 = z52.enum(["allow", "deny"]).default("deny");
4011
+ defaultPathAccessLevelSchema2 = z52.enum(["allow", "deny"]);
4012
+ defaultPathAccessLevelsSchema2 = z52.object({
4013
+ read: defaultPathAccessLevelSchema2.optional(),
4014
+ edit: defaultPathAccessLevelSchema2.optional(),
4015
+ review: defaultPathAccessLevelSchema2.optional()
4016
+ }).strict();
4017
+ defaultPathAccessSchema2 = z52.union([defaultPathAccessLevelSchema2, defaultPathAccessLevelsSchema2]).default("deny");
3928
4018
  defaultBaseBranchSchema2 = z52.string().default("main");
3929
4019
  defaultRemoteNameSchema2 = z52.string().default("origin");
3930
4020
  defaultRemoteUrlSchema2 = z52.string().min(1);
@@ -3957,6 +4047,10 @@ var init_generate_ai_content = __esm({
3957
4047
  // have .default('deny'), so the field is optional on input but always resolves to
3958
4048
  // 'allow'/'deny' (never undefined) on output. An outer .optional() would short-circuit
3959
4049
  // before the inner default runs, defeating the fail-closed default (SCH-M1).
4050
+ // defaultPathAccessSchema's union keeps the same rule: the .default('deny') sits on the
4051
+ // OUTER union, not inside defaultPathAccessLevelsSchema, so an omitted top-level field
4052
+ // still resolves to 'deny' and an omitted level inside the object form stays undefined
4053
+ // (resolved to 'deny' at read time by resolveDefaultPathAccess).
3960
4054
  defaultBranchAccess: defaultBranchAccessSchema2,
3961
4055
  defaultPathAccess: defaultPathAccessSchema2,
3962
4056
  // .optional() deliberately defeats defaultBaseBranchSchema's .default('main'):
@@ -5521,8 +5615,22 @@ var init_generate_ai_content = __esm({
5521
5615
  * Returns array of entry metadata (relativePath, collection, slug).
5522
5616
  * Returns empty array if the collection doesn't exist.
5523
5617
  */
5524
- async getCollectionEntryPaths(collectionPath) {
5525
- const idIndex = await this.idIndex();
5618
+ /**
5619
+ * Resolve a schema collection referenced by name or logical path.
5620
+ *
5621
+ * Accepts either a full logical path ("content/authors") or a bare
5622
+ * collection name ("authors") — the contract reference fields use for
5623
+ * `collections: [...]` (see README). This is the single normalization
5624
+ * point shared by reference-option loading and reference validation so
5625
+ * the dropdown and the write boundary can never disagree.
5626
+ *
5627
+ * CAVEAT: the bare-name fallback matches on the LAST path segment and
5628
+ * returns the first hit in schema order — with two collections sharing a
5629
+ * leaf name (e.g. content/blog/posts and content/news/posts), a bare
5630
+ * 'posts' is ambiguous. Use the full logical path in schemas that nest
5631
+ * same-named collections.
5632
+ */
5633
+ resolveCollectionItem(collectionPath) {
5526
5634
  const normalized = normalizeFilesystemPath(collectionPath);
5527
5635
  let item = this.schemaIndex.get(normalized);
5528
5636
  if (!item) {
@@ -5536,10 +5644,14 @@ var init_generate_ai_content = __esm({
5536
5644
  }
5537
5645
  }
5538
5646
  }
5539
- if (!item || item.type !== "collection") {
5647
+ return item && item.type === "collection" ? item : void 0;
5648
+ }
5649
+ async getCollectionEntryPaths(collectionPath) {
5650
+ const idIndex = await this.idIndex();
5651
+ const collection = this.resolveCollectionItem(collectionPath);
5652
+ if (!collection) {
5540
5653
  return [];
5541
5654
  }
5542
- const collection = item;
5543
5655
  const treeEntries = idIndex.getEntriesInCollectionTree(collection.logicalPath);
5544
5656
  const entries = [];
5545
5657
  for (const location of treeEntries) {
@@ -5817,7 +5929,13 @@ var init_generate_ai_content = __esm({
5817
5929
  continue;
5818
5930
  }
5819
5931
  const branchRoot = path10.join(this.root, entry.name);
5820
- const meta = await BranchMetadataFileManager.loadOnly(branchRoot);
5932
+ let meta;
5933
+ try {
5934
+ meta = await BranchMetadataFileManager.loadOnly(branchRoot);
5935
+ } catch (err) {
5936
+ console.warn(`CanopyCMS: Skipping branch directory '${entry.name}' during registry scan: ${getErrorMessage3(err)}`);
5937
+ continue;
5938
+ }
5821
5939
  if (meta) {
5822
5940
  branches.push({
5823
5941
  branch: meta.branch,
@@ -5851,6 +5969,14 @@ var init_generate_ai_content = __esm({
5851
5969
  this.name = "BranchMetadataConflictError";
5852
5970
  }
5853
5971
  };
5972
+ BranchMetadataCorruptError = class extends Error {
5973
+ constructor(branchRoot, cause) {
5974
+ super(`Corrupt branch metadata in '${branchRoot}': ${cause}`);
5975
+ this.name = "BranchMetadataCorruptError";
5976
+ this.branchRoot = branchRoot;
5977
+ this.parseCause = cause;
5978
+ }
5979
+ };
5854
5980
  BranchMetadataFileManager = class _BranchMetadataFileManager {
5855
5981
  constructor(branchRoot, baseRoot, options) {
5856
5982
  this.branchRoot = path12.resolve(branchRoot);
@@ -5863,7 +5989,8 @@ var init_generate_ai_content = __esm({
5863
5989
  * Use this for read-only access (e.g., in registry scanning or loadBranchContext).
5864
5990
  */
5865
5991
  static async loadOnly(branchRoot) {
5866
- const filePath = path12.join(path12.resolve(branchRoot), BRANCH_META_DIR, BRANCH_META_FILE);
5992
+ const resolvedRoot = path12.resolve(branchRoot);
5993
+ const filePath = path12.join(resolvedRoot, BRANCH_META_DIR, BRANCH_META_FILE);
5867
5994
  try {
5868
5995
  const raw = await fs12.readFile(filePath, "utf8");
5869
5996
  return JSON.parse(raw);
@@ -5871,6 +5998,9 @@ var init_generate_ai_content = __esm({
5871
5998
  if (isNotFoundError4(err)) {
5872
5999
  return null;
5873
6000
  }
6001
+ if (err instanceof SyntaxError) {
6002
+ throw new BranchMetadataCorruptError(resolvedRoot, err.message);
6003
+ }
5874
6004
  throw err;
5875
6005
  }
5876
6006
  }
@@ -5980,7 +6110,11 @@ var init_generate_ai_content = __esm({
5980
6110
  createdBy: existing?.branch.createdBy ?? incoming.branch?.createdBy ?? defaults.createdBy,
5981
6111
  createdAt: existing?.branch.createdAt ?? defaults.createdAt,
5982
6112
  // Fork point is recorded once at creation; later saves must not move it
5983
- baseBranch: existing?.branch.baseBranch ?? incoming.branch?.baseBranch
6113
+ baseBranch: existing?.branch.baseBranch ?? incoming.branch?.baseBranch,
6114
+ // Always stamped fresh — the spreads above would otherwise let
6115
+ // the existing (creation-time) value win forever, freezing the
6116
+ // timestamp the editor's Branches panel sorts and displays by
6117
+ updatedAt: now
5984
6118
  }
5985
6119
  };
5986
6120
  const written = await this.write(merged, version);
@@ -6450,6 +6584,7 @@ var init_generate_ai_content = __esm({
6450
6584
  }
6451
6585
  if (options.branchType === "orphan") {
6452
6586
  await git.createOrphanSettingsBranch(options.branchName, {});
6587
+ await git.ensureGitExclude("*.lock");
6453
6588
  } else {
6454
6589
  await git.checkoutBranch(options.branchName);
6455
6590
  if (options.gitExcludePattern) {
@@ -6531,8 +6666,9 @@ var init_generate_ai_content = __esm({
6531
6666
  }
6532
6667
  async pullBaseInner() {
6533
6668
  await this.git.fetch(this.remote, this.baseBranch);
6669
+ const fetchedTip = (await this.git.revparse(["FETCH_HEAD"])).trim();
6534
6670
  try {
6535
- await this.git.merge([`${this.remote}/${this.baseBranch}`]);
6671
+ await this.git.merge([fetchedTip]);
6536
6672
  } catch (err) {
6537
6673
  try {
6538
6674
  const status = await this.git.status();
@@ -6587,8 +6723,9 @@ var init_generate_ai_content = __esm({
6587
6723
  }
6588
6724
  async rebaseOntoBaseInner() {
6589
6725
  await this.git.fetch(this.remote, this.baseBranch);
6726
+ const fetchedTip = (await this.git.revparse(["FETCH_HEAD"])).trim();
6590
6727
  try {
6591
- await this.git.rebase([`${this.remote}/${this.baseBranch}`]);
6728
+ await this.git.rebase([fetchedTip]);
6592
6729
  } catch (err) {
6593
6730
  try {
6594
6731
  const status = await this.git.status();
@@ -7875,14 +8012,20 @@ var init_media3 = __esm({
7875
8012
 
7876
8013
  // dist/config/schemas/config.js
7877
8014
  import { z as z12 } from "zod";
7878
- var defaultBranchAccessSchema3, defaultPathAccessSchema3, defaultBaseBranchSchema3, defaultRemoteNameSchema3, defaultRemoteUrlSchema3, gitBotAuthorNameSchema3, gitBotAuthorEmailSchema3, githubTokenEnvVarSchema3, operatingModeSchema3, deployedAsSchema3, contentRootSchema3, sourceRootSchema3, deploymentNameSchema3, devContentSyncSchema3, devConfigSchema3, editorConfigSchema3, CanopyConfigSchema3, DEFAULT_PROD_WORKSPACE3;
8015
+ var defaultBranchAccessSchema3, defaultPathAccessLevelSchema3, defaultPathAccessLevelsSchema3, defaultPathAccessSchema3, defaultBaseBranchSchema3, defaultRemoteNameSchema3, defaultRemoteUrlSchema3, gitBotAuthorNameSchema3, gitBotAuthorEmailSchema3, githubTokenEnvVarSchema3, operatingModeSchema3, deployedAsSchema3, contentRootSchema3, sourceRootSchema3, deploymentNameSchema3, devContentSyncSchema3, devConfigSchema3, editorConfigSchema3, CanopyConfigSchema3, DEFAULT_PROD_WORKSPACE3;
7879
8016
  var init_config4 = __esm({
7880
8017
  "dist/config/schemas/config.js"() {
7881
8018
  "use strict";
7882
8019
  init_collection3();
7883
8020
  init_media3();
7884
8021
  defaultBranchAccessSchema3 = z12.enum(["allow", "deny"]).default("deny");
7885
- defaultPathAccessSchema3 = z12.enum(["allow", "deny"]).default("deny");
8022
+ defaultPathAccessLevelSchema3 = z12.enum(["allow", "deny"]);
8023
+ defaultPathAccessLevelsSchema3 = z12.object({
8024
+ read: defaultPathAccessLevelSchema3.optional(),
8025
+ edit: defaultPathAccessLevelSchema3.optional(),
8026
+ review: defaultPathAccessLevelSchema3.optional()
8027
+ }).strict();
8028
+ defaultPathAccessSchema3 = z12.union([defaultPathAccessLevelSchema3, defaultPathAccessLevelsSchema3]).default("deny");
7886
8029
  defaultBaseBranchSchema3 = z12.string().default("main");
7887
8030
  defaultRemoteNameSchema3 = z12.string().default("origin");
7888
8031
  defaultRemoteUrlSchema3 = z12.string().min(1);
@@ -7915,6 +8058,10 @@ var init_config4 = __esm({
7915
8058
  // have .default('deny'), so the field is optional on input but always resolves to
7916
8059
  // 'allow'/'deny' (never undefined) on output. An outer .optional() would short-circuit
7917
8060
  // before the inner default runs, defeating the fail-closed default (SCH-M1).
8061
+ // defaultPathAccessSchema's union keeps the same rule: the .default('deny') sits on the
8062
+ // OUTER union, not inside defaultPathAccessLevelsSchema, so an omitted top-level field
8063
+ // still resolves to 'deny' and an omitted level inside the object form stays undefined
8064
+ // (resolved to 'deny' at read time by resolveDefaultPathAccess).
7918
8065
  defaultBranchAccess: defaultBranchAccessSchema3,
7919
8066
  defaultPathAccess: defaultPathAccessSchema3,
7920
8067
  // .optional() deliberately defeats defaultBaseBranchSchema's .default('main'):
@@ -8175,7 +8322,7 @@ async function selectBranch(options, branchesDir) {
8175
8322
  let branches = [];
8176
8323
  if (await filePathExists(branchesDir)) {
8177
8324
  const entries = await fs23.readdir(branchesDir, { withFileTypes: true });
8178
- branches = entries.filter((e) => e.isDirectory()).map((e) => e.name);
8325
+ branches = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name);
8179
8326
  }
8180
8327
  let branchName = options.branch;
8181
8328
  if (!branchName) {
@@ -8554,11 +8701,19 @@ var init_resolve = __esm({
8554
8701
  }
8555
8702
  });
8556
8703
 
8704
+ // dist/paths/branch-name.js
8705
+ var init_branch_name = __esm({
8706
+ "dist/paths/branch-name.js"() {
8707
+ "use strict";
8708
+ }
8709
+ });
8710
+
8557
8711
  // dist/paths/branch.js
8558
8712
  var init_branch = __esm({
8559
8713
  "dist/paths/branch.js"() {
8560
8714
  "use strict";
8561
8715
  init_operating_mode2();
8716
+ init_branch_name();
8562
8717
  }
8563
8718
  });
8564
8719
 
@@ -8571,6 +8726,7 @@ var init_paths = __esm({
8571
8726
  init_validation3();
8572
8727
  init_resolve();
8573
8728
  init_branch();
8729
+ init_branch_name();
8574
8730
  }
8575
8731
  });
8576
8732
 
@@ -8583,10 +8739,170 @@ var init_schema = __esm({
8583
8739
  }
8584
8740
  });
8585
8741
 
8586
- // dist/branch-registry.js
8742
+ // dist/utils/async-mutex.js
8743
+ async function withLock2(key, fn) {
8744
+ const prev = locks2.get(key) ?? Promise.resolve();
8745
+ let resolve2;
8746
+ const next = new Promise((r) => {
8747
+ resolve2 = r;
8748
+ });
8749
+ locks2.set(key, next);
8750
+ await prev;
8751
+ try {
8752
+ return await fn();
8753
+ } finally {
8754
+ resolve2();
8755
+ if (locks2.get(key) === next)
8756
+ locks2.delete(key);
8757
+ }
8758
+ }
8759
+ var locks2;
8760
+ var init_async_mutex = __esm({
8761
+ "dist/utils/async-mutex.js"() {
8762
+ "use strict";
8763
+ locks2 = /* @__PURE__ */ new Map();
8764
+ }
8765
+ });
8766
+
8767
+ // dist/utils/occ-json-write.js
8587
8768
  import fs24 from "node:fs/promises";
8588
8769
  import path27 from "node:path";
8589
- var log11, REGISTRY_FILE2, REGISTRY_STALE_FILE2, REGISTRY_TEMP_FILE2, REGISTRY_VERSION2, RESOURCE4, GET_MISS_REFRESH_MIN_INTERVAL_MS2, BranchRegistry2;
8770
+ import { randomUUID as randomUUID4 } from "node:crypto";
8771
+ import lockfile4 from "proper-lockfile";
8772
+ async function writeOccJsonFile2(filePath, payload, options) {
8773
+ const { expectedVersion, settleMs = 50, trailingNewline = false } = options;
8774
+ const newVersion = expectedVersion === null ? 1 : expectedVersion + 1;
8775
+ const writeId = randomUUID4();
8776
+ const data = { ...payload, version: newVersion, writeId };
8777
+ const content = JSON.stringify(data, null, 2) + (trailingNewline ? "\n" : "");
8778
+ const dir = path27.dirname(filePath);
8779
+ await fs24.mkdir(dir, { recursive: true });
8780
+ const tempPath = `${filePath}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
8781
+ await fs24.writeFile(tempPath, content, "utf-8");
8782
+ if (expectedVersion === null) {
8783
+ try {
8784
+ await fs24.link(tempPath, filePath);
8785
+ } catch (err) {
8786
+ await fs24.unlink(tempPath).catch(() => {
8787
+ });
8788
+ if (isFileExistsError(err)) {
8789
+ throw new OccWriteConflictError2();
8790
+ }
8791
+ throw err;
8792
+ }
8793
+ await fs24.unlink(tempPath).catch(() => {
8794
+ });
8795
+ return { version: newVersion, writeId };
8796
+ }
8797
+ try {
8798
+ let currentVersion = null;
8799
+ try {
8800
+ const current = JSON.parse(await fs24.readFile(filePath, "utf-8"));
8801
+ currentVersion = current.version ?? 0;
8802
+ } catch {
8803
+ currentVersion = null;
8804
+ }
8805
+ if (currentVersion !== expectedVersion) {
8806
+ throw new OccWriteConflictError2();
8807
+ }
8808
+ await fs24.rename(tempPath, filePath);
8809
+ if (settleMs > 0) {
8810
+ await new Promise((resolve2) => setTimeout(resolve2, settleMs));
8811
+ }
8812
+ const afterWrite = JSON.parse(await fs24.readFile(filePath, "utf-8"));
8813
+ if (afterWrite.writeId !== writeId) {
8814
+ throw new OccWriteConflictError2();
8815
+ }
8816
+ } catch (err) {
8817
+ await fs24.unlink(tempPath).catch(() => {
8818
+ });
8819
+ throw err;
8820
+ }
8821
+ return { version: newVersion, writeId };
8822
+ }
8823
+ async function withOccRetry2(operation, options) {
8824
+ const maxAttempts = options?.maxAttempts ?? 10;
8825
+ const isRetryable = options?.isRetryable;
8826
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
8827
+ try {
8828
+ return await operation();
8829
+ } catch (err) {
8830
+ const retryable = err instanceof OccWriteConflictError2 || (isRetryable?.(err) ?? false);
8831
+ if (retryable && attempt < maxAttempts) {
8832
+ const baseDelay = Math.min(10 * Math.pow(2, attempt - 1), 100);
8833
+ const jitter = Math.random() * baseDelay;
8834
+ await new Promise((resolve2) => setTimeout(resolve2, baseDelay + jitter));
8835
+ continue;
8836
+ }
8837
+ throw err;
8838
+ }
8839
+ }
8840
+ throw new Error("Unreachable");
8841
+ }
8842
+ async function withOccFileLock2(filePath, fn) {
8843
+ const dir = path27.dirname(filePath);
8844
+ await fs24.mkdir(dir, { recursive: true });
8845
+ const MAX_ATTEMPTS = 14;
8846
+ let release = null;
8847
+ for (let attempt = 1; release === null; attempt++) {
8848
+ try {
8849
+ release = await lockfile4.lock(filePath, {
8850
+ lockfilePath: `${filePath}.lock`,
8851
+ realpath: false,
8852
+ stale: 1e4,
8853
+ retries: 0,
8854
+ // A compromised lock (the lock dir vanished or refresh failed
8855
+ // mid-hold, e.g. the branch directory containing it was deleted)
8856
+ // must not crash the process, which is proper-lockfile's default.
8857
+ // Our critical sections are short and idempotent-on-conflict (OCC
8858
+ // verify inside); log and let the section finish.
8859
+ onCompromised: (err) => {
8860
+ log11.warn("lock", `Lock compromised mid-hold for ${filePath}`, {
8861
+ error: getErrorMessage(err)
8862
+ });
8863
+ }
8864
+ });
8865
+ } catch (err) {
8866
+ const code = isNodeError(err) ? err.code : void 0;
8867
+ const contended = code === "ELOCKED";
8868
+ if (!contended || attempt >= MAX_ATTEMPTS) {
8869
+ throw new OccWriteConflictError2(`Could not acquire file lock: ${getErrorMessage(err)}`);
8870
+ }
8871
+ const baseDelay = Math.min(50 * Math.pow(1.6, attempt - 1), 2e3);
8872
+ const jitter = Math.random() * baseDelay;
8873
+ await new Promise((resolve2) => setTimeout(resolve2, baseDelay + jitter));
8874
+ }
8875
+ }
8876
+ try {
8877
+ return await fn();
8878
+ } finally {
8879
+ await release().catch((err) => {
8880
+ if (isNodeError(err) && err.code === "ENOENT")
8881
+ return;
8882
+ log11.warn("lock", `Failed to release lock for ${filePath}`, { error: getErrorMessage(err) });
8883
+ });
8884
+ }
8885
+ }
8886
+ var log11, OccWriteConflictError2;
8887
+ var init_occ_json_write = __esm({
8888
+ "dist/utils/occ-json-write.js"() {
8889
+ "use strict";
8890
+ init_error();
8891
+ init_debug2();
8892
+ log11 = createDebugLogger3({ prefix: "OccJsonWrite" });
8893
+ OccWriteConflictError2 = class extends Error {
8894
+ constructor(message = "Concurrent modification detected") {
8895
+ super(message);
8896
+ this.name = "OccWriteConflictError";
8897
+ }
8898
+ };
8899
+ }
8900
+ });
8901
+
8902
+ // dist/branch-registry.js
8903
+ import fs25 from "node:fs/promises";
8904
+ import path28 from "node:path";
8905
+ var log12, REGISTRY_FILE2, REGISTRY_STALE_FILE2, REGISTRY_TEMP_FILE2, REGISTRY_VERSION2, RESOURCE4, GET_MISS_REFRESH_MIN_INTERVAL_MS2, BranchRegistry2;
8590
8906
  var init_branch_registry = __esm({
8591
8907
  "dist/branch-registry.js"() {
8592
8908
  "use strict";
@@ -8594,7 +8910,7 @@ var init_branch_registry = __esm({
8594
8910
  init_error();
8595
8911
  init_debug2();
8596
8912
  init_resource_generation();
8597
- log11 = createDebugLogger3({ prefix: "BranchRegistry" });
8913
+ log12 = createDebugLogger3({ prefix: "BranchRegistry" });
8598
8914
  REGISTRY_FILE2 = "branches.json";
8599
8915
  REGISTRY_STALE_FILE2 = "branches.stale.json";
8600
8916
  REGISTRY_TEMP_FILE2 = "branches.tmp.json";
@@ -8605,10 +8921,10 @@ var init_branch_registry = __esm({
8605
8921
  constructor(root) {
8606
8922
  this.regenInFlight = null;
8607
8923
  this.lastForcedRefreshMs = 0;
8608
- this.root = path27.resolve(root);
8609
- this.registryPath = path27.join(this.root, REGISTRY_FILE2);
8610
- this.stalePath = path27.join(this.root, REGISTRY_STALE_FILE2);
8611
- this.tempPath = path27.join(this.root, REGISTRY_TEMP_FILE2);
8924
+ this.root = path28.resolve(root);
8925
+ this.registryPath = path28.join(this.root, REGISTRY_FILE2);
8926
+ this.stalePath = path28.join(this.root, REGISTRY_STALE_FILE2);
8927
+ this.tempPath = path28.join(this.root, REGISTRY_TEMP_FILE2);
8612
8928
  }
8613
8929
  /**
8614
8930
  * Returns all branches. Uses the cached snapshot if its embedded generation
@@ -8617,7 +8933,7 @@ var init_branch_registry = __esm({
8617
8933
  async list() {
8618
8934
  let parsed;
8619
8935
  try {
8620
- const raw = await fs24.readFile(this.registryPath, "utf8");
8936
+ const raw = await fs25.readFile(this.registryPath, "utf8");
8621
8937
  parsed = JSON.parse(raw);
8622
8938
  } catch (err) {
8623
8939
  if (isNotFoundError(err)) {
@@ -8680,7 +8996,7 @@ var init_branch_registry = __esm({
8680
8996
  });
8681
8997
  await this.regenerate();
8682
8998
  } catch (err) {
8683
- log11.warn("invalidate", "Eager regeneration after invalidate() failed", {
8999
+ log12.warn("invalidate", "Eager regeneration after invalidate() failed", {
8684
9000
  error: getErrorMessage(err)
8685
9001
  });
8686
9002
  }
@@ -8706,7 +9022,7 @@ var init_branch_registry = __esm({
8706
9022
  async performRegenerate() {
8707
9023
  const read = await readResourceGeneration2(this.root, RESOURCE4);
8708
9024
  const branches = await this.scanBranchDirectories();
8709
- await fs24.unlink(this.stalePath).catch(() => {
9025
+ await fs25.unlink(this.stalePath).catch(() => {
8710
9026
  });
8711
9027
  if (!read.ok) {
8712
9028
  return branches;
@@ -8717,12 +9033,12 @@ var init_branch_registry = __esm({
8717
9033
  generation: read.token
8718
9034
  };
8719
9035
  const uniqueTempPath = `${this.tempPath}.${Date.now()}.${Math.random().toString(36).slice(2)}`;
8720
- await fs24.mkdir(this.root, { recursive: true });
8721
- await fs24.writeFile(uniqueTempPath, JSON.stringify(snapshot, null, 2) + "\n", "utf8");
9036
+ await fs25.mkdir(this.root, { recursive: true });
9037
+ await fs25.writeFile(uniqueTempPath, JSON.stringify(snapshot, null, 2) + "\n", "utf8");
8722
9038
  try {
8723
- await fs24.rename(uniqueTempPath, this.registryPath);
9039
+ await fs25.rename(uniqueTempPath, this.registryPath);
8724
9040
  } catch (err) {
8725
- await fs24.unlink(uniqueTempPath).catch(() => {
9041
+ await fs25.unlink(uniqueTempPath).catch(() => {
8726
9042
  });
8727
9043
  throw err;
8728
9044
  }
@@ -8736,13 +9052,19 @@ var init_branch_registry = __esm({
8736
9052
  async scanBranchDirectories() {
8737
9053
  const branches = [];
8738
9054
  try {
8739
- const entries = await fs24.readdir(this.root, { withFileTypes: true });
9055
+ const entries = await fs25.readdir(this.root, { withFileTypes: true });
8740
9056
  for (const entry of entries) {
8741
9057
  if (!entry.isDirectory() || entry.name.startsWith(".")) {
8742
9058
  continue;
8743
9059
  }
8744
- const branchRoot = path27.join(this.root, entry.name);
8745
- const meta = await BranchMetadataFileManager2.loadOnly(branchRoot);
9060
+ const branchRoot = path28.join(this.root, entry.name);
9061
+ let meta;
9062
+ try {
9063
+ meta = await BranchMetadataFileManager2.loadOnly(branchRoot);
9064
+ } catch (err) {
9065
+ console.warn(`CanopyCMS: Skipping branch directory '${entry.name}' during registry scan: ${getErrorMessage(err)}`);
9066
+ continue;
9067
+ }
8746
9068
  if (meta) {
8747
9069
  branches.push({
8748
9070
  branch: meta.branch,
@@ -8763,168 +9085,10 @@ var init_branch_registry = __esm({
8763
9085
  }
8764
9086
  });
8765
9087
 
8766
- // dist/utils/async-mutex.js
8767
- async function withLock2(key, fn) {
8768
- const prev = locks2.get(key) ?? Promise.resolve();
8769
- let resolve2;
8770
- const next = new Promise((r) => {
8771
- resolve2 = r;
8772
- });
8773
- locks2.set(key, next);
8774
- await prev;
8775
- try {
8776
- return await fn();
8777
- } finally {
8778
- resolve2();
8779
- if (locks2.get(key) === next)
8780
- locks2.delete(key);
8781
- }
8782
- }
8783
- var locks2;
8784
- var init_async_mutex = __esm({
8785
- "dist/utils/async-mutex.js"() {
8786
- "use strict";
8787
- locks2 = /* @__PURE__ */ new Map();
8788
- }
8789
- });
8790
-
8791
- // dist/utils/occ-json-write.js
8792
- import fs25 from "node:fs/promises";
8793
- import path28 from "node:path";
8794
- import { randomUUID as randomUUID4 } from "node:crypto";
8795
- import lockfile4 from "proper-lockfile";
8796
- async function writeOccJsonFile2(filePath, payload, options) {
8797
- const { expectedVersion, settleMs = 50, trailingNewline = false } = options;
8798
- const newVersion = expectedVersion === null ? 1 : expectedVersion + 1;
8799
- const writeId = randomUUID4();
8800
- const data = { ...payload, version: newVersion, writeId };
8801
- const content = JSON.stringify(data, null, 2) + (trailingNewline ? "\n" : "");
8802
- const dir = path28.dirname(filePath);
8803
- await fs25.mkdir(dir, { recursive: true });
8804
- const tempPath = `${filePath}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
8805
- await fs25.writeFile(tempPath, content, "utf-8");
8806
- if (expectedVersion === null) {
8807
- try {
8808
- await fs25.link(tempPath, filePath);
8809
- } catch (err) {
8810
- await fs25.unlink(tempPath).catch(() => {
8811
- });
8812
- if (isFileExistsError(err)) {
8813
- throw new OccWriteConflictError2();
8814
- }
8815
- throw err;
8816
- }
8817
- await fs25.unlink(tempPath).catch(() => {
8818
- });
8819
- return { version: newVersion, writeId };
8820
- }
8821
- try {
8822
- let currentVersion = null;
8823
- try {
8824
- const current = JSON.parse(await fs25.readFile(filePath, "utf-8"));
8825
- currentVersion = current.version ?? 0;
8826
- } catch {
8827
- currentVersion = null;
8828
- }
8829
- if (currentVersion !== expectedVersion) {
8830
- throw new OccWriteConflictError2();
8831
- }
8832
- await fs25.rename(tempPath, filePath);
8833
- if (settleMs > 0) {
8834
- await new Promise((resolve2) => setTimeout(resolve2, settleMs));
8835
- }
8836
- const afterWrite = JSON.parse(await fs25.readFile(filePath, "utf-8"));
8837
- if (afterWrite.writeId !== writeId) {
8838
- throw new OccWriteConflictError2();
8839
- }
8840
- } catch (err) {
8841
- await fs25.unlink(tempPath).catch(() => {
8842
- });
8843
- throw err;
8844
- }
8845
- return { version: newVersion, writeId };
8846
- }
8847
- async function withOccRetry2(operation, options) {
8848
- const maxAttempts = options?.maxAttempts ?? 10;
8849
- const isRetryable = options?.isRetryable;
8850
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
8851
- try {
8852
- return await operation();
8853
- } catch (err) {
8854
- const retryable = err instanceof OccWriteConflictError2 || (isRetryable?.(err) ?? false);
8855
- if (retryable && attempt < maxAttempts) {
8856
- const baseDelay = Math.min(10 * Math.pow(2, attempt - 1), 100);
8857
- const jitter = Math.random() * baseDelay;
8858
- await new Promise((resolve2) => setTimeout(resolve2, baseDelay + jitter));
8859
- continue;
8860
- }
8861
- throw err;
8862
- }
8863
- }
8864
- throw new Error("Unreachable");
8865
- }
8866
- async function withOccFileLock2(filePath, fn) {
8867
- const dir = path28.dirname(filePath);
8868
- await fs25.mkdir(dir, { recursive: true });
8869
- const MAX_ATTEMPTS = 14;
8870
- let release = null;
8871
- for (let attempt = 1; release === null; attempt++) {
8872
- try {
8873
- release = await lockfile4.lock(filePath, {
8874
- lockfilePath: `${filePath}.lock`,
8875
- realpath: false,
8876
- stale: 1e4,
8877
- retries: 0,
8878
- // A compromised lock (the lock dir vanished or refresh failed
8879
- // mid-hold, e.g. the branch directory containing it was deleted)
8880
- // must not crash the process, which is proper-lockfile's default.
8881
- // Our critical sections are short and idempotent-on-conflict (OCC
8882
- // verify inside); log and let the section finish.
8883
- onCompromised: (err) => {
8884
- log12.warn("lock", `Lock compromised mid-hold for ${filePath}`, {
8885
- error: getErrorMessage(err)
8886
- });
8887
- }
8888
- });
8889
- } catch (err) {
8890
- const code = isNodeError(err) ? err.code : void 0;
8891
- const contended = code === "ELOCKED";
8892
- if (!contended || attempt >= MAX_ATTEMPTS) {
8893
- throw new OccWriteConflictError2(`Could not acquire file lock: ${getErrorMessage(err)}`);
8894
- }
8895
- const baseDelay = Math.min(50 * Math.pow(1.6, attempt - 1), 2e3);
8896
- const jitter = Math.random() * baseDelay;
8897
- await new Promise((resolve2) => setTimeout(resolve2, baseDelay + jitter));
8898
- }
8899
- }
8900
- try {
8901
- return await fn();
8902
- } finally {
8903
- await release().catch((err) => {
8904
- log12.warn("lock", `Failed to release lock for ${filePath}`, { error: getErrorMessage(err) });
8905
- });
8906
- }
8907
- }
8908
- var log12, OccWriteConflictError2;
8909
- var init_occ_json_write = __esm({
8910
- "dist/utils/occ-json-write.js"() {
8911
- "use strict";
8912
- init_error();
8913
- init_debug2();
8914
- log12 = createDebugLogger3({ prefix: "OccJsonWrite" });
8915
- OccWriteConflictError2 = class extends Error {
8916
- constructor(message = "Concurrent modification detected") {
8917
- super(message);
8918
- this.name = "OccWriteConflictError";
8919
- }
8920
- };
8921
- }
8922
- });
8923
-
8924
9088
  // dist/branch-metadata.js
8925
9089
  import fs26 from "node:fs/promises";
8926
9090
  import path29 from "node:path";
8927
- var BRANCH_META_DIR2, BRANCH_META_FILE2, CURRENT_SCHEMA_VERSION2, BranchMetadataConflictError2, BranchMetadataFileManager2;
9091
+ var BRANCH_META_DIR2, BRANCH_META_FILE2, CURRENT_SCHEMA_VERSION2, BranchMetadataConflictError2, BranchMetadataCorruptError2, BranchMetadataFileManager2;
8928
9092
  var init_branch_metadata = __esm({
8929
9093
  "dist/branch-metadata.js"() {
8930
9094
  "use strict";
@@ -8942,6 +9106,14 @@ var init_branch_metadata = __esm({
8942
9106
  this.name = "BranchMetadataConflictError";
8943
9107
  }
8944
9108
  };
9109
+ BranchMetadataCorruptError2 = class extends Error {
9110
+ constructor(branchRoot, cause) {
9111
+ super(`Corrupt branch metadata in '${branchRoot}': ${cause}`);
9112
+ this.name = "BranchMetadataCorruptError";
9113
+ this.branchRoot = branchRoot;
9114
+ this.parseCause = cause;
9115
+ }
9116
+ };
8945
9117
  BranchMetadataFileManager2 = class _BranchMetadataFileManager2 {
8946
9118
  constructor(branchRoot, baseRoot, options) {
8947
9119
  this.branchRoot = path29.resolve(branchRoot);
@@ -8954,7 +9126,8 @@ var init_branch_metadata = __esm({
8954
9126
  * Use this for read-only access (e.g., in registry scanning or loadBranchContext).
8955
9127
  */
8956
9128
  static async loadOnly(branchRoot) {
8957
- const filePath = path29.join(path29.resolve(branchRoot), BRANCH_META_DIR2, BRANCH_META_FILE2);
9129
+ const resolvedRoot = path29.resolve(branchRoot);
9130
+ const filePath = path29.join(resolvedRoot, BRANCH_META_DIR2, BRANCH_META_FILE2);
8958
9131
  try {
8959
9132
  const raw = await fs26.readFile(filePath, "utf8");
8960
9133
  return JSON.parse(raw);
@@ -8962,6 +9135,9 @@ var init_branch_metadata = __esm({
8962
9135
  if (isNotFoundError(err)) {
8963
9136
  return null;
8964
9137
  }
9138
+ if (err instanceof SyntaxError) {
9139
+ throw new BranchMetadataCorruptError2(resolvedRoot, err.message);
9140
+ }
8965
9141
  throw err;
8966
9142
  }
8967
9143
  }
@@ -9071,7 +9247,11 @@ var init_branch_metadata = __esm({
9071
9247
  createdBy: existing?.branch.createdBy ?? incoming.branch?.createdBy ?? defaults.createdBy,
9072
9248
  createdAt: existing?.branch.createdAt ?? defaults.createdAt,
9073
9249
  // Fork point is recorded once at creation; later saves must not move it
9074
- baseBranch: existing?.branch.baseBranch ?? incoming.branch?.baseBranch
9250
+ baseBranch: existing?.branch.baseBranch ?? incoming.branch?.baseBranch,
9251
+ // Always stamped fresh — the spreads above would otherwise let
9252
+ // the existing (creation-time) value win forever, freezing the
9253
+ // timestamp the editor's Branches panel sorts and displays by
9254
+ updatedAt: now
9075
9255
  }
9076
9256
  };
9077
9257
  const written = await this.write(merged, version);
@@ -9256,15 +9436,31 @@ async function migrate(options) {
9256
9436
  return { opCount: 0 };
9257
9437
  }
9258
9438
  }
9259
- for (const op of ops) {
9260
- if (op.kind === "rename") {
9261
- await fs27.rename(op.from, op.to);
9262
- } else {
9263
- await fs27.writeFile(op.filePath, op.content, "utf-8");
9439
+ const isBranchClone = Boolean(await BranchMetadataFileManager2.loadOnly(projectDir));
9440
+ const applyOps = async () => {
9441
+ for (const op of ops) {
9442
+ if (op.kind === "rename") {
9443
+ await fs27.rename(op.from, op.to);
9444
+ } else {
9445
+ await fs27.writeFile(op.filePath, op.content, "utf-8");
9446
+ }
9264
9447
  }
9448
+ };
9449
+ if (isBranchClone) {
9450
+ const lockPath = path30.join(projectDir, ".canopy-meta", "schema");
9451
+ try {
9452
+ await withLock2(lockPath, () => withOccFileLock2(lockPath, applyOps));
9453
+ } catch (err) {
9454
+ if (err instanceof OccWriteConflictError2) {
9455
+ throw new MigrateError("Another process is modifying the schema, try again.");
9456
+ }
9457
+ throw err;
9458
+ }
9459
+ } else {
9460
+ await applyOps();
9265
9461
  }
9266
9462
  p3.log.success(`Applied ${ops.length} operation(s).`);
9267
- if (await BranchMetadataFileManager2.loadOnly(projectDir)) {
9463
+ if (isBranchClone) {
9268
9464
  await invalidateBranchContentCaches2(projectDir);
9269
9465
  }
9270
9466
  try {
@@ -9288,6 +9484,8 @@ var init_migrate = __esm({
9288
9484
  init_fs();
9289
9485
  init_schema();
9290
9486
  init_error();
9487
+ init_async_mutex();
9488
+ init_occ_json_write();
9291
9489
  init_branch_metadata();
9292
9490
  init_content_index_generation();
9293
9491
  MIGRATE_FORMATS = ["md", "mdx", "json", "yaml"];