canopycms 0.0.60 → 0.0.61-int.75

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 (218) 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 +51 -0
  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 +13 -3
  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 +15 -3
  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.js +1 -1
  42. package/dist/api/entries.js.map +1 -1
  43. package/dist/api/github-sync.d.ts.map +1 -1
  44. package/dist/api/github-sync.js +14 -3
  45. package/dist/api/github-sync.js.map +1 -1
  46. package/dist/api/guards.d.ts +7 -1
  47. package/dist/api/guards.d.ts.map +1 -1
  48. package/dist/api/guards.js +54 -0
  49. package/dist/api/guards.js.map +1 -1
  50. package/dist/api/index.d.ts +1 -0
  51. package/dist/api/index.d.ts.map +1 -1
  52. package/dist/api/index.js +0 -1
  53. package/dist/api/index.js.map +1 -1
  54. package/dist/api/schema.js +7 -7
  55. package/dist/api/schema.js.map +1 -1
  56. package/dist/authorization/branch.d.ts +14 -1
  57. package/dist/authorization/branch.d.ts.map +1 -1
  58. package/dist/authorization/branch.js +5 -3
  59. package/dist/authorization/branch.js.map +1 -1
  60. package/dist/authorization/index.d.ts +1 -0
  61. package/dist/authorization/index.d.ts.map +1 -1
  62. package/dist/authorization/index.js +2 -0
  63. package/dist/authorization/index.js.map +1 -1
  64. package/dist/authorization/protected-branch.d.ts +39 -0
  65. package/dist/authorization/protected-branch.d.ts.map +1 -0
  66. package/dist/authorization/protected-branch.js +43 -0
  67. package/dist/authorization/protected-branch.js.map +1 -0
  68. package/dist/branch-health.d.ts +78 -0
  69. package/dist/branch-health.d.ts.map +1 -0
  70. package/dist/branch-health.js +151 -0
  71. package/dist/branch-health.js.map +1 -0
  72. package/dist/branch-metadata.d.ts +20 -0
  73. package/dist/branch-metadata.d.ts.map +1 -1
  74. package/dist/branch-metadata.js +21 -1
  75. package/dist/branch-metadata.js.map +1 -1
  76. package/dist/branch-registry.d.ts.map +1 -1
  77. package/dist/branch-registry.js +13 -1
  78. package/dist/branch-registry.js.map +1 -1
  79. package/dist/cli/cli.js +154 -13
  80. package/dist/cli/generate-ai-content.js +54 -10
  81. package/dist/cli/init.js +72 -0
  82. package/dist/cli/sync.d.ts.map +1 -1
  83. package/dist/cli/sync.js +4 -1
  84. package/dist/cli/sync.js.map +1 -1
  85. package/dist/content-store.d.ts +16 -0
  86. package/dist/content-store.d.ts.map +1 -1
  87. package/dist/content-store.js +22 -6
  88. package/dist/content-store.js.map +1 -1
  89. package/dist/editor/BranchManager.d.ts +9 -1
  90. package/dist/editor/BranchManager.d.ts.map +1 -1
  91. package/dist/editor/BranchManager.js +20 -9
  92. package/dist/editor/BranchManager.js.map +1 -1
  93. package/dist/editor/CommentsPanel.d.ts.map +1 -1
  94. package/dist/editor/CommentsPanel.js +4 -12
  95. package/dist/editor/CommentsPanel.js.map +1 -1
  96. package/dist/editor/Editor.d.ts.map +1 -1
  97. package/dist/editor/Editor.js +46 -10
  98. package/dist/editor/Editor.js.map +1 -1
  99. package/dist/editor/EntryNavigator.d.ts +7 -0
  100. package/dist/editor/EntryNavigator.d.ts.map +1 -1
  101. package/dist/editor/EntryNavigator.js +17 -5
  102. package/dist/editor/EntryNavigator.js.map +1 -1
  103. package/dist/editor/FormRenderer.d.ts.map +1 -1
  104. package/dist/editor/FormRenderer.js +4 -0
  105. package/dist/editor/FormRenderer.js.map +1 -1
  106. package/dist/editor/admin/SystemHealthPanel.d.ts +6 -0
  107. package/dist/editor/admin/SystemHealthPanel.d.ts.map +1 -0
  108. package/dist/editor/admin/SystemHealthPanel.js +234 -0
  109. package/dist/editor/admin/SystemHealthPanel.js.map +1 -0
  110. package/dist/editor/admin/useSystemHealth.d.ts +40 -0
  111. package/dist/editor/admin/useSystemHealth.d.ts.map +1 -0
  112. package/dist/editor/admin/useSystemHealth.js +205 -0
  113. package/dist/editor/admin/useSystemHealth.js.map +1 -0
  114. package/dist/editor/comments/FieldWrapper.d.ts.map +1 -1
  115. package/dist/editor/comments/FieldWrapper.js +1 -1
  116. package/dist/editor/comments/FieldWrapper.js.map +1 -1
  117. package/dist/editor/comments/InlineCommentThread.d.ts.map +1 -1
  118. package/dist/editor/comments/InlineCommentThread.js +2 -21
  119. package/dist/editor/comments/InlineCommentThread.js.map +1 -1
  120. package/dist/editor/comments/ThreadCarousel.d.ts +2 -0
  121. package/dist/editor/comments/ThreadCarousel.d.ts.map +1 -1
  122. package/dist/editor/comments/ThreadCarousel.js +5 -2
  123. package/dist/editor/comments/ThreadCarousel.js.map +1 -1
  124. package/dist/editor/components/EditorHeader.d.ts +11 -0
  125. package/dist/editor/components/EditorHeader.d.ts.map +1 -1
  126. package/dist/editor/components/EditorHeader.js +75 -41
  127. package/dist/editor/components/EditorHeader.js.map +1 -1
  128. package/dist/editor/components/EditorSidebar.d.ts +7 -1
  129. package/dist/editor/components/EditorSidebar.d.ts.map +1 -1
  130. package/dist/editor/components/EditorSidebar.js +3 -3
  131. package/dist/editor/components/EditorSidebar.js.map +1 -1
  132. package/dist/editor/fields/ImageField.d.ts.map +1 -1
  133. package/dist/editor/fields/ImageField.js +4 -1
  134. package/dist/editor/fields/ImageField.js.map +1 -1
  135. package/dist/editor/fields/StringListField.d.ts +20 -0
  136. package/dist/editor/fields/StringListField.d.ts.map +1 -0
  137. package/dist/editor/fields/StringListField.js +25 -0
  138. package/dist/editor/fields/StringListField.js.map +1 -0
  139. package/dist/editor/hooks/useBranchActions.d.ts.map +1 -1
  140. package/dist/editor/hooks/useBranchActions.js +8 -2
  141. package/dist/editor/hooks/useBranchActions.js.map +1 -1
  142. package/dist/editor/hooks/useBranchManager.d.ts +12 -3
  143. package/dist/editor/hooks/useBranchManager.d.ts.map +1 -1
  144. package/dist/editor/hooks/useBranchManager.js +16 -1
  145. package/dist/editor/hooks/useBranchManager.js.map +1 -1
  146. package/dist/editor/hooks/useDraftManager.d.ts +7 -0
  147. package/dist/editor/hooks/useDraftManager.d.ts.map +1 -1
  148. package/dist/editor/hooks/useDraftManager.js +57 -3
  149. package/dist/editor/hooks/useDraftManager.js.map +1 -1
  150. package/dist/editor/hooks/useEntryManager.d.ts.map +1 -1
  151. package/dist/editor/hooks/useEntryManager.js +25 -7
  152. package/dist/editor/hooks/useEntryManager.js.map +1 -1
  153. package/dist/editor/media/AssetCard.d.ts.map +1 -1
  154. package/dist/editor/media/AssetCard.js +23 -2
  155. package/dist/editor/media/AssetCard.js.map +1 -1
  156. package/dist/editor/relative-time.d.ts +8 -0
  157. package/dist/editor/relative-time.d.ts.map +1 -0
  158. package/dist/editor/relative-time.js +26 -0
  159. package/dist/editor/relative-time.js.map +1 -0
  160. package/dist/git-manager.d.ts.map +1 -1
  161. package/dist/git-manager.js +10 -2
  162. package/dist/git-manager.js.map +1 -1
  163. package/dist/http/handler.d.ts.map +1 -1
  164. package/dist/http/handler.js +20 -9
  165. package/dist/http/handler.js.map +1 -1
  166. package/dist/http/router.d.ts.map +1 -1
  167. package/dist/http/router.js +2 -0
  168. package/dist/http/router.js.map +1 -1
  169. package/dist/paths/branch-name.d.ts +20 -0
  170. package/dist/paths/branch-name.d.ts.map +1 -0
  171. package/dist/paths/branch-name.js +24 -0
  172. package/dist/paths/branch-name.js.map +1 -0
  173. package/dist/paths/branch.d.ts +2 -8
  174. package/dist/paths/branch.d.ts.map +1 -1
  175. package/dist/paths/branch.js +6 -12
  176. package/dist/paths/branch.js.map +1 -1
  177. package/dist/paths/index.d.ts +2 -1
  178. package/dist/paths/index.d.ts.map +1 -1
  179. package/dist/paths/index.js +4 -1
  180. package/dist/paths/index.js.map +1 -1
  181. package/dist/services.js +9 -1
  182. package/dist/services.js.map +1 -1
  183. package/dist/task-queue/index.d.ts +2 -2
  184. package/dist/task-queue/index.d.ts.map +1 -1
  185. package/dist/task-queue/index.js +1 -1
  186. package/dist/task-queue/index.js.map +1 -1
  187. package/dist/task-queue/task-queue.d.ts +37 -1
  188. package/dist/task-queue/task-queue.d.ts.map +1 -1
  189. package/dist/task-queue/task-queue.js +104 -0
  190. package/dist/task-queue/task-queue.js.map +1 -1
  191. package/dist/task-queue/types.d.ts +11 -0
  192. package/dist/task-queue/types.d.ts.map +1 -1
  193. package/dist/types.d.ts +41 -0
  194. package/dist/types.d.ts.map +1 -1
  195. package/dist/utils/occ-json-write.d.ts.map +1 -1
  196. package/dist/utils/occ-json-write.js +7 -0
  197. package/dist/utils/occ-json-write.js.map +1 -1
  198. package/dist/utils/provisioning-lock.d.ts +19 -0
  199. package/dist/utils/provisioning-lock.d.ts.map +1 -1
  200. package/dist/utils/provisioning-lock.js +26 -0
  201. package/dist/utils/provisioning-lock.js.map +1 -1
  202. package/dist/validation/reference-validator.d.ts +17 -1
  203. package/dist/validation/reference-validator.d.ts.map +1 -1
  204. package/dist/validation/reference-validator.js +33 -10
  205. package/dist/validation/reference-validator.js.map +1 -1
  206. package/dist/worker/cms-worker.d.ts +38 -0
  207. package/dist/worker/cms-worker.d.ts.map +1 -1
  208. package/dist/worker/cms-worker.js +328 -25
  209. package/dist/worker/cms-worker.js.map +1 -1
  210. package/dist/worker/task-queue.d.ts +3 -3
  211. package/dist/worker/task-queue.d.ts.map +1 -1
  212. package/dist/worker/task-queue.js +2 -2
  213. package/dist/worker/task-queue.js.map +1 -1
  214. package/dist/worker/worker-status.d.ts +48 -0
  215. package/dist/worker/worker-status.d.ts.map +1 -0
  216. package/dist/worker/worker-status.js +53 -0
  217. package/dist/worker/worker-status.js.map +1 -0
  218. package/package.json +1 -1
@@ -0,0 +1,448 @@
1
+ /**
2
+ * Admin branch-health observability + recovery (PR-A3): a scan listing every
3
+ * directory under the branches root (healthy/corrupt-metadata/orphan), plus
4
+ * two recovery actions -- purge (reversible trash-rename) and repair-metadata
5
+ * (recreate defaults for a corrupt branch.json). Split out of admin.ts to
6
+ * keep that file under its size budget; ADMIN_ROUTES in admin.ts re-exports
7
+ * these routes so the router and generate-client keep a single import.
8
+ *
9
+ * See docs/concurrency.md's "Who uses what" table for the locking rationale
10
+ * (double-hold on purge, release-then-save ordering on repair).
11
+ */
12
+ import fs from 'node:fs/promises';
13
+ import path from 'node:path';
14
+ import { z } from 'zod';
15
+ import { simpleGit } from 'simple-git';
16
+ import { BranchMetadataFileManager, getBranchMetadataFileManager } from '../branch-metadata.js';
17
+ import { scanBranchHealth } from '../branch-health.js';
18
+ import { getDefaultBranchBase, sanitizeBranchName } from '../paths/index.js';
19
+ import { withOccFileLock } from '../utils/occ-json-write.js';
20
+ import { tryAcquireProvisioningLock } from '../utils/provisioning-lock.js';
21
+ import { getErrorMessage, isNodeError, isNotFoundError } from '../utils/error.js';
22
+ import { defineEndpoint } from './route-builder.js';
23
+ // ============================================================================
24
+ // Constants
25
+ // ============================================================================
26
+ /** [H1] A fresh (< 5 min old) init lock blocks purge -- provisioning may be running. */
27
+ const PROVISIONING_LOCK_FRESH_MS = 5 * 60_000;
28
+ /** An orphan dir younger than this may still be a clone in progress; corrupt dirs are exempt. */
29
+ const ORPHAN_YOUTH_THRESHOLD_MS = 15 * 60_000;
30
+ const BRANCH_META_DIR = '.canopy-meta';
31
+ const BRANCH_META_FILE = 'branch.json';
32
+ // ============================================================================
33
+ // Zod schemas
34
+ // ============================================================================
35
+ // Mirrors deleteTaskHandler's fileName pattern in admin.ts: conservative
36
+ // charset plus explicit traversal/dot-prefix refinements (the regex alone
37
+ // technically excludes '/' already, but the refinements keep intent explicit
38
+ // and self-documenting at the validation layer).
39
+ const dirNameSchema = z
40
+ .string()
41
+ .regex(/^[A-Za-z0-9._-]{1,100}$/)
42
+ .refine((v) => !v.includes('..'), { message: 'dirName must not contain ..' })
43
+ .refine((v) => !v.startsWith('.'), { message: 'dirName must not be dot-prefixed' });
44
+ const branchDirParamsSchema = z.object({ dirName: dirNameSchema });
45
+ // ============================================================================
46
+ // Shared helpers
47
+ // ============================================================================
48
+ /** Compact UTC stamp for trash/archive names: `YYYYMMDDTHHMMSSZ` (no colons -- portability). */
49
+ function formatTrashStamp(date) {
50
+ return date
51
+ .toISOString()
52
+ .replace(/[-:]/g, '')
53
+ .replace(/\.\d{3}Z$/, 'Z');
54
+ }
55
+ /**
56
+ * Re-resolve dirName under baseRoot and enforce containment (belt-and-
57
+ * suspenders on top of the zod regex, matching deleteTaskHandler's pattern
58
+ * in admin.ts).
59
+ */
60
+ function resolveDirWithinBase(baseRoot, dirName) {
61
+ const resolvedBase = path.resolve(baseRoot);
62
+ const baseWithSep = resolvedBase.endsWith(path.sep) ? resolvedBase : resolvedBase + path.sep;
63
+ const dirPath = path.resolve(resolvedBase, dirName);
64
+ if (dirPath !== resolvedBase && !dirPath.startsWith(baseWithSep))
65
+ return null;
66
+ return dirPath;
67
+ }
68
+ /** Control-flow error for repair's precondition checks made INSIDE the lock hold. */
69
+ class RepairPreconditionError extends Error {
70
+ constructor(message, status) {
71
+ super(message);
72
+ this.name = 'RepairPreconditionError';
73
+ this.status = status;
74
+ }
75
+ }
76
+ // ============================================================================
77
+ // Handlers
78
+ // ============================================================================
79
+ const getBranchHealthHandler = async (_gc, ctx, _req) => {
80
+ // Same derivation services.ts uses to construct the BranchRegistry --
81
+ // admin handlers must agree with it or the scan silently looks at the
82
+ // wrong directory.
83
+ const baseRoot = getDefaultBranchBase(ctx.services.config.mode);
84
+ const baseBranchName = ctx.services.config.defaultBaseBranch ?? 'main';
85
+ try {
86
+ const entries = await scanBranchHealth(baseRoot, { baseBranchName });
87
+ return {
88
+ ok: true,
89
+ status: 200,
90
+ data: { entries, generatedAt: new Date().toISOString() },
91
+ };
92
+ }
93
+ catch (err) {
94
+ return { ok: false, status: 500, error: getErrorMessage(err) };
95
+ }
96
+ };
97
+ /**
98
+ * Purge a corrupt-metadata or orphan branch directory by renaming it into a
99
+ * dot-prefixed trash name (reversible -- nothing is deleted here; the worker
100
+ * sweeps trash older than 30 days, see cleanupTrashedBranchDirs in
101
+ * worker/cms-worker.ts).
102
+ *
103
+ * Safety rails (see the PR's design review for the finding IDs):
104
+ * - [always] the base branch directory can never be purged.
105
+ * - Server re-derives live/corrupt/orphan state itself -- never trusts the
106
+ * client's view of the world.
107
+ * - [H1] a fresh provisioning init-lock blocks purge; a stale one does not.
108
+ * - an orphan (not corrupt) dir younger than 15 minutes is presumed to be a
109
+ * clone in progress and is not purgeable yet.
110
+ * - [H2] the purge itself runs under a zero-retry provisioning lock, so a
111
+ * real in-flight provisioner (whose lock is therefore fresh, and would
112
+ * already have 409'd above) can never race the rename; a same-instant
113
+ * contender simply 409s.
114
+ * - [M3] ALSO runs under the branch.json lockfile -- the same lock every
115
+ * metadata save() takes -- closing the window where a concurrent
116
+ * repair-metadata save() resurrects a ghost branch.json mid-purge.
117
+ */
118
+ const purgeBranchDirHandler = async (_gc, ctx, _req, params) => {
119
+ const baseRoot = getDefaultBranchBase(ctx.services.config.mode);
120
+ const baseBranchName = ctx.services.config.defaultBaseBranch ?? 'main';
121
+ const sanitizedBaseBranchName = sanitizeBranchName(baseBranchName);
122
+ if (params.dirName === sanitizedBaseBranchName) {
123
+ return { ok: false, status: 400, error: 'The base branch directory can never be purged' };
124
+ }
125
+ const dirPath = resolveDirWithinBase(baseRoot, params.dirName);
126
+ if (!dirPath) {
127
+ return { ok: false, status: 400, error: 'Invalid directory name' };
128
+ }
129
+ // [MEDIUM-1 rider] The directory must actually exist -- otherwise
130
+ // withOccFileLock's `mkdir -p` below would CREATE it, and purge would
131
+ // "succeed" with a phantom `.trash-*` entry for a directory that was
132
+ // never there. Must run before anything below treats "no branch.json" as
133
+ // orphan classification.
134
+ const dirExists = await fs
135
+ .stat(dirPath)
136
+ .then(() => true)
137
+ .catch(() => false);
138
+ if (!dirExists) {
139
+ return { ok: false, status: 404, error: 'Directory not found' };
140
+ }
141
+ const branchJsonPath = path.join(dirPath, BRANCH_META_DIR, BRANCH_META_FILE);
142
+ // Re-derive state server-side -- never trust the client's classification.
143
+ let meta = null;
144
+ let hadLoadError = false;
145
+ try {
146
+ meta = await BranchMetadataFileManager.loadOnly(dirPath);
147
+ }
148
+ catch {
149
+ hadLoadError = true;
150
+ }
151
+ if (meta) {
152
+ return { ok: false, status: 409, error: 'Directory holds a live branch; use branch delete' };
153
+ }
154
+ // True orphan (no branch.json, no load error) vs. corrupt-metadata
155
+ // (loadOnly threw). Only true orphans are subject to the youth rail below.
156
+ const isTrueOrphan = !hadLoadError;
157
+ // [H1] freshness rail: a fresh init lock means provisioning may genuinely
158
+ // be in progress; a stale one is just crash debris and does not block.
159
+ const lockPath = path.join(baseRoot, `.${params.dirName}.init.lock`);
160
+ const lockStat = await fs.stat(lockPath).catch(() => null);
161
+ if (lockStat && Date.now() - lockStat.mtimeMs < PROVISIONING_LOCK_FRESH_MS) {
162
+ return { ok: false, status: 409, error: 'Provisioning may be in progress' };
163
+ }
164
+ // Youth rail: a brand-new orphan dir may be a clone that just hasn't
165
+ // written branch.json yet. Corrupt-metadata dirs are exempt -- a
166
+ // parseable-then-corrupted file is not a mid-clone signature.
167
+ if (isTrueOrphan) {
168
+ const dirStat = await fs.stat(dirPath).catch(() => null);
169
+ if (dirStat && Date.now() - dirStat.mtimeMs < ORPHAN_YOUTH_THRESHOLD_MS) {
170
+ return {
171
+ ok: false,
172
+ status: 409,
173
+ error: 'Directory too young -- may be a clone in progress',
174
+ };
175
+ }
176
+ }
177
+ // [H2] Zero-retry provisioning lock: fails fast (409) on genuine live
178
+ // contention instead of hanging the request for ~5 minutes.
179
+ let releaseProvisioningLock;
180
+ try {
181
+ releaseProvisioningLock = await tryAcquireProvisioningLock(baseRoot, `.${params.dirName}.init.lock`);
182
+ }
183
+ catch (err) {
184
+ if (isNodeError(err) && err.code === 'ELOCKED') {
185
+ return { ok: false, status: 409, error: 'Provisioning lock contention, try again' };
186
+ }
187
+ return { ok: false, status: 500, error: getErrorMessage(err) };
188
+ }
189
+ try {
190
+ return await withOccFileLock(branchJsonPath, async () => {
191
+ // [M3] Re-verify classification inside the double hold: a concurrent
192
+ // repair-metadata save() could have resurrected branch.json between
193
+ // our pre-lock check and now.
194
+ let recheckMeta = null;
195
+ try {
196
+ recheckMeta = await BranchMetadataFileManager.loadOnly(dirPath);
197
+ }
198
+ catch {
199
+ // Still corrupt (or now corrupt) -- fine, proceed with the purge.
200
+ }
201
+ if (recheckMeta) {
202
+ return { ok: false, status: 409, error: 'Directory holds a live branch; use branch delete' };
203
+ }
204
+ // [C1] The timestamp lives in the NAME, not the dir's mtime: rename()
205
+ // preserves the original mtime, so mtime-based retention would delete
206
+ // a months-stale orphan's trash on the very first cleanup pass.
207
+ const trashName = `.trash-${params.dirName}-${formatTrashStamp(new Date())}`;
208
+ const trashPath = path.join(baseRoot, trashName);
209
+ try {
210
+ await fs.rename(dirPath, trashPath);
211
+ }
212
+ catch (err) {
213
+ // Rename failure: the directory stays exactly where it was --
214
+ // nothing evaporates, and the next scan still lists it.
215
+ return { ok: false, status: 500, error: getErrorMessage(err) };
216
+ }
217
+ await ctx.services.registry?.invalidate();
218
+ return { ok: true, status: 200, data: { trashedAs: trashName } };
219
+ });
220
+ }
221
+ catch (err) {
222
+ return {
223
+ ok: false,
224
+ status: 409,
225
+ error: `Could not lock branch metadata: ${getErrorMessage(err)}`,
226
+ };
227
+ }
228
+ finally {
229
+ if (releaseProvisioningLock) {
230
+ await releaseProvisioningLock().catch(() => { });
231
+ }
232
+ }
233
+ };
234
+ /**
235
+ * Repair a corrupt branch.json by archiving it (forensics preserved as
236
+ * `branch.json.corrupt-{STAMP}`, ignored by future scans since it doesn't
237
+ * match the exact `branch.json` name) and recreating defaults via the
238
+ * normal save() path. Unlike purge, the base branch IS a valid target here
239
+ * -- a corrupt BASE branch.json is exactly the degraded-service scenario
240
+ * this handler exists to fix.
241
+ *
242
+ * [M4] Lock sequence: withOccFileLock is NOT reentrant, and save() takes it
243
+ * internally, so the rename must happen and the lock must be RELEASED
244
+ * (exiting the withOccFileLock callback) before save() runs -- calling
245
+ * save() while still holding the lock would deadlock against itself.
246
+ *
247
+ * [MEDIUM-1] The provisioning lock is held across the ENTIRE archive+save
248
+ * sequence (acquired before withOccFileLock, released only after save()
249
+ * completes), same lock order as purge (provisioning -> branch.json) so the
250
+ * two can never deadlock against each other. Without this, a concurrent
251
+ * purge could trash the directory in the window between this handler
252
+ * releasing withOccFileLock (required before save(), per [M4] above) and
253
+ * save() actually running -- save() would then resurrect a metadata-only
254
+ * ghost of a directory purge just moved to trash.
255
+ */
256
+ const repairBranchDirHandler = async (_gc, ctx, req, params) => {
257
+ const baseRoot = getDefaultBranchBase(ctx.services.config.mode);
258
+ const dirPath = resolveDirWithinBase(baseRoot, params.dirName);
259
+ if (!dirPath) {
260
+ return { ok: false, status: 400, error: 'Invalid directory name' };
261
+ }
262
+ // [MEDIUM-1 rider] Same stat guard as purge: the directory must actually
263
+ // exist, otherwise withOccFileLock's `mkdir -p` below would CREATE it.
264
+ const dirExists = await fs
265
+ .stat(dirPath)
266
+ .then(() => true)
267
+ .catch(() => false);
268
+ if (!dirExists) {
269
+ return { ok: false, status: 404, error: 'Directory not found' };
270
+ }
271
+ const branchJsonPath = path.join(dirPath, BRANCH_META_DIR, BRANCH_META_FILE);
272
+ // Pre-lock precondition check: fail fast on the common cases without
273
+ // paying for a lock acquisition on a doomed request.
274
+ const precheck = await checkStillCorrupt(dirPath);
275
+ if (precheck !== 'corrupt') {
276
+ return precheck === 'healthy'
277
+ ? { ok: false, status: 409, error: 'Metadata is healthy' }
278
+ : { ok: false, status: 409, error: 'No metadata file -- use purge for orphans' };
279
+ }
280
+ // [MEDIUM-1] Zero-retry provisioning lock, mirroring purge's [H2]: fails
281
+ // fast (409) on genuine live contention instead of hanging the request.
282
+ // Held until the `finally` below, AFTER save() completes.
283
+ let releaseProvisioningLock;
284
+ try {
285
+ releaseProvisioningLock = await tryAcquireProvisioningLock(baseRoot, `.${params.dirName}.init.lock`);
286
+ }
287
+ catch (err) {
288
+ if (isNodeError(err) && err.code === 'ELOCKED') {
289
+ return { ok: false, status: 409, error: 'Provisioning lock contention, try again' };
290
+ }
291
+ return { ok: false, status: 500, error: getErrorMessage(err) };
292
+ }
293
+ try {
294
+ let archivedAs;
295
+ try {
296
+ archivedAs = await withOccFileLock(branchJsonPath, async () => {
297
+ // Re-verify still-corrupt under the lock (a concurrent repair could
298
+ // have already fixed this, or the file could have been purged out
299
+ // from under us).
300
+ const recheck = await checkStillCorrupt(dirPath);
301
+ if (recheck === 'healthy') {
302
+ throw new RepairPreconditionError('Metadata is healthy', 409);
303
+ }
304
+ if (recheck === 'missing') {
305
+ throw new RepairPreconditionError('No metadata file -- use purge for orphans', 409);
306
+ }
307
+ const archivedName = `${BRANCH_META_FILE}.corrupt-${formatTrashStamp(new Date())}`;
308
+ await fs.rename(branchJsonPath, path.join(dirPath, BRANCH_META_DIR, archivedName));
309
+ return archivedName;
310
+ });
311
+ }
312
+ catch (err) {
313
+ if (err instanceof RepairPreconditionError) {
314
+ return { ok: false, status: err.status, error: err.message };
315
+ }
316
+ return {
317
+ ok: false,
318
+ status: 409,
319
+ error: `Could not lock branch metadata: ${getErrorMessage(err)}`,
320
+ };
321
+ }
322
+ // [MEDIUM-3] Prefer the clone's actual checked-out branch over the
323
+ // (possibly sanitized) directory name -- see resolveRepairedBranchName.
324
+ const branchName = await resolveRepairedBranchName(dirPath, params.dirName);
325
+ // Lock released above (we're outside the withOccFileLock callback now) --
326
+ // save() takes its own hold on the same lock internally. Its defaults
327
+ // path fabricates the rest of BranchMetadata and invalidates the
328
+ // registry. Still under the provisioning lock acquired above -- see
329
+ // [MEDIUM-1] in the docstring.
330
+ const manager = getBranchMetadataFileManager(dirPath, baseRoot);
331
+ const saved = await manager.save({
332
+ branch: { name: branchName, status: 'editing', createdBy: req.user.userId },
333
+ });
334
+ return { ok: true, status: 200, data: { branch: saved.branch, archivedAs } };
335
+ }
336
+ finally {
337
+ if (releaseProvisioningLock) {
338
+ await releaseProvisioningLock().catch(() => { });
339
+ }
340
+ }
341
+ };
342
+ /**
343
+ * [MEDIUM-3] Best-effort read of the branch actually checked out in the
344
+ * clone at `dirPath`, falling back to `dirName` on any failure (no `.git`,
345
+ * detached HEAD, corrupt repo, etc.). Workspace directory names are
346
+ * sanitized (slashes stripped, see paths/branch.ts's `sanitizeBranchName`),
347
+ * so a branch named `feature/foo` lives in a directory named `feature-foo`
348
+ * -- writing `dirName` as `branch.name` would make later push/PR tasks
349
+ * target the wrong ref.
350
+ */
351
+ async function resolveRepairedBranchName(dirPath, dirName) {
352
+ const hasGitDir = await fs
353
+ .stat(path.join(dirPath, '.git'))
354
+ .then(() => true)
355
+ .catch(() => false);
356
+ if (!hasGitDir)
357
+ return dirName;
358
+ try {
359
+ const current = await simpleGit({ baseDir: dirPath }).revparse(['--abbrev-ref', 'HEAD']);
360
+ const trimmed = current.trim();
361
+ return trimmed || dirName;
362
+ }
363
+ catch {
364
+ return dirName;
365
+ }
366
+ }
367
+ /** Classify branch.json's current state for repair's precondition checks. */
368
+ async function checkStillCorrupt(dirPath) {
369
+ try {
370
+ const meta = await BranchMetadataFileManager.loadOnly(dirPath);
371
+ return meta ? 'healthy' : 'missing';
372
+ }
373
+ catch (err) {
374
+ if (isNotFoundError(err))
375
+ return 'missing';
376
+ return 'corrupt';
377
+ }
378
+ }
379
+ // ============================================================================
380
+ // Route definitions
381
+ // ============================================================================
382
+ /**
383
+ * Branch directory health scan (healthy/corrupt-metadata/orphan)
384
+ * GET /admin/branch-health
385
+ */
386
+ const getBranchHealth = defineEndpoint({
387
+ namespace: 'admin',
388
+ name: 'branchHealth',
389
+ method: 'GET',
390
+ path: '/admin/branch-health',
391
+ responseType: 'BranchHealthResponse',
392
+ response: {},
393
+ defaultMockData: { entries: [], generatedAt: '2024-01-01T00:00:00.000Z' },
394
+ guards: ['admin'],
395
+ handler: getBranchHealthHandler,
396
+ });
397
+ /**
398
+ * Purge a corrupt-metadata or orphan branch directory (reversible trash-rename)
399
+ * POST /admin/branch-dirs/:dirName/purge
400
+ */
401
+ const purgeBranchDir = defineEndpoint({
402
+ namespace: 'admin',
403
+ name: 'purgeBranchDir',
404
+ method: 'POST',
405
+ path: '/admin/branch-dirs/:dirName/purge',
406
+ params: branchDirParamsSchema,
407
+ responseType: 'PurgeBranchDirResponse',
408
+ response: {},
409
+ defaultMockData: { trashedAs: '.trash-example-branch-20240101T000000Z' },
410
+ guards: ['admin'],
411
+ handler: purgeBranchDirHandler,
412
+ });
413
+ /**
414
+ * Repair a corrupt branch.json by archiving it and recreating defaults
415
+ * POST /admin/branch-dirs/:dirName/repair-metadata
416
+ */
417
+ const repairBranchDir = defineEndpoint({
418
+ namespace: 'admin',
419
+ name: 'repairBranchDir',
420
+ method: 'POST',
421
+ path: '/admin/branch-dirs/:dirName/repair-metadata',
422
+ params: branchDirParamsSchema,
423
+ responseType: 'RepairBranchDirResponse',
424
+ response: {},
425
+ defaultMockData: {
426
+ branch: {
427
+ name: 'example-branch',
428
+ status: 'editing',
429
+ access: {},
430
+ createdBy: 'admin',
431
+ createdAt: '2024-01-01T00:00:00.000Z',
432
+ updatedAt: '2024-01-01T00:00:00.000Z',
433
+ },
434
+ archivedAs: 'branch.json.corrupt-20240101T000000Z',
435
+ },
436
+ guards: ['admin'],
437
+ handler: repairBranchDirHandler,
438
+ });
439
+ /**
440
+ * Exported routes, merged into ADMIN_ROUTES in admin.ts for a single
441
+ * router/generate-client import.
442
+ */
443
+ export const ADMIN_BRANCH_HEALTH_ROUTES = {
444
+ branchHealth: getBranchHealth,
445
+ purgeBranchDir,
446
+ repairBranchDir,
447
+ };
448
+ //# sourceMappingURL=admin-branch-health.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-branch-health.js","sourceRoot":"","sources":["../../src/api/admin-branch-health.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Admin observability endpoints: task queue stats/listing and worker
3
+ * liveness (PR-A1), plus task recovery actions — retry a failed task or
4
+ * delete a stuck/corrupt task file (PR-A2) — for an admin-only dashboard.
5
+ * Branch-health scan + purge/repair actions (PR-A3) live in
6
+ * admin-branch-health.ts and are merged into ADMIN_ROUTES below.
7
+ */
8
+ import { z } from 'zod';
9
+ import type { ApiResponse } from './types';
10
+ import type { Task, QueueStats, CorruptTaskFile } from '../worker/task-queue';
11
+ import type { WorkerStatusReport } from '../types';
12
+ import type { OperatingMode } from '../operating-mode';
13
+ export type { BranchHealthResponse, PurgeBranchDirResponse, RepairBranchDirResponse, } from './admin-branch-health';
14
+ export type WorkerLivenessState = 'alive' | 'stale' | 'absent';
15
+ export interface WorkerLiveness {
16
+ /**
17
+ * 'absent' — the lock dir is removed on clean release, so this also covers
18
+ * "never started". 'alive' — heartbeat fresher than the threshold. 'stale'
19
+ * — heartbeat older than the threshold (the holder likely crashed).
20
+ */
21
+ state: WorkerLivenessState;
22
+ lockMtime?: string;
23
+ lockAgeMs?: number;
24
+ }
25
+ export interface AdminStatusData {
26
+ generatedAt: string;
27
+ mode: OperatingMode;
28
+ queue: QueueStats & {
29
+ oldestPendingAgeMs?: number;
30
+ };
31
+ worker: WorkerLiveness;
32
+ workerStatus: WorkerStatusReport | null;
33
+ statusReadError?: string;
34
+ }
35
+ /** Response type for GET /admin/status */
36
+ export type AdminStatusResponse = ApiResponse<AdminStatusData>;
37
+ export interface AdminTasksData {
38
+ tasks?: Task[];
39
+ corruptFiles?: CorruptTaskFile[];
40
+ }
41
+ /** Response type for GET /admin/tasks/:status */
42
+ export type AdminTasksResponse = ApiResponse<AdminTasksData>;
43
+ export interface AdminRetryTaskData {
44
+ newTaskId: string;
45
+ }
46
+ /** Response type for POST /admin/tasks/:taskId/retry */
47
+ export type AdminRetryTaskResponse = ApiResponse<AdminRetryTaskData>;
48
+ export interface AdminDeleteTaskData {
49
+ deleted: true;
50
+ }
51
+ /** Response type for DELETE /admin/tasks/:status/:fileName */
52
+ export type AdminDeleteTaskResponse = ApiResponse<AdminDeleteTaskData>;
53
+ declare const listAdminTasksParamsSchema: z.ZodObject<{
54
+ status: z.ZodEnum<["pending", "processing", "completed", "failed", "corrupt"]>;
55
+ limit: z.ZodOptional<z.ZodNumber>;
56
+ }, "strip", z.ZodTypeAny, {
57
+ status: "pending" | "processing" | "completed" | "failed" | "corrupt";
58
+ limit?: number | undefined;
59
+ }, {
60
+ status: "pending" | "processing" | "completed" | "failed" | "corrupt";
61
+ limit?: number | undefined;
62
+ }>;
63
+ export type ListAdminTasksParams = z.infer<typeof listAdminTasksParamsSchema>;
64
+ declare const retryTaskParamsSchema: z.ZodObject<{
65
+ taskId: z.ZodString;
66
+ }, "strip", z.ZodTypeAny, {
67
+ taskId: string;
68
+ }, {
69
+ taskId: string;
70
+ }>;
71
+ export type RetryTaskParams = z.infer<typeof retryTaskParamsSchema>;
72
+ declare const deleteTaskParamsSchema: z.ZodObject<{
73
+ status: z.ZodEnum<["pending", "failed", "corrupt"]>;
74
+ fileName: z.ZodEffects<z.ZodString, string, string>;
75
+ }, "strip", z.ZodTypeAny, {
76
+ status: "pending" | "failed" | "corrupt";
77
+ fileName: string;
78
+ }, {
79
+ status: "pending" | "failed" | "corrupt";
80
+ fileName: string;
81
+ }>;
82
+ export type DeleteTaskParams = z.infer<typeof deleteTaskParamsSchema>;
83
+ /**
84
+ * Exported routes for router registration.
85
+ *
86
+ * Branch-health scan + purge/repair (PR-A3) live in admin-branch-health.ts
87
+ * (kept out of this file to stay under its size budget) and are merged in
88
+ * here so the router (http/router.ts) and generate-client.ts keep a single
89
+ * `ADMIN_ROUTES` import.
90
+ */
91
+ export declare const ADMIN_ROUTES: {
92
+ readonly branchHealth: import("./route-builder").RouteDefinition<undefined, undefined, import("./admin-branch-health").BranchHealthResponse>;
93
+ readonly purgeBranchDir: import("./route-builder").RouteDefinition<z.ZodObject<{
94
+ dirName: z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>;
95
+ }, "strip", z.ZodTypeAny, {
96
+ dirName: string;
97
+ }, {
98
+ dirName: string;
99
+ }>, undefined, import("./admin-branch-health").PurgeBranchDirResponse>;
100
+ readonly repairBranchDir: import("./route-builder").RouteDefinition<z.ZodObject<{
101
+ dirName: z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>;
102
+ }, "strip", z.ZodTypeAny, {
103
+ dirName: string;
104
+ }, {
105
+ dirName: string;
106
+ }>, undefined, import("./admin-branch-health").RepairBranchDirResponse>;
107
+ readonly status: import("./route-builder").RouteDefinition<undefined, undefined, AdminStatusResponse>;
108
+ readonly listTasks: import("./route-builder").RouteDefinition<z.ZodObject<{
109
+ status: z.ZodEnum<["pending", "processing", "completed", "failed", "corrupt"]>;
110
+ limit: z.ZodOptional<z.ZodNumber>;
111
+ }, "strip", z.ZodTypeAny, {
112
+ status: "pending" | "processing" | "completed" | "failed" | "corrupt";
113
+ limit?: number | undefined;
114
+ }, {
115
+ status: "pending" | "processing" | "completed" | "failed" | "corrupt";
116
+ limit?: number | undefined;
117
+ }>, undefined, AdminTasksResponse>;
118
+ readonly retryTask: import("./route-builder").RouteDefinition<z.ZodObject<{
119
+ taskId: z.ZodString;
120
+ }, "strip", z.ZodTypeAny, {
121
+ taskId: string;
122
+ }, {
123
+ taskId: string;
124
+ }>, undefined, AdminRetryTaskResponse>;
125
+ readonly deleteTask: import("./route-builder").RouteDefinition<z.ZodObject<{
126
+ status: z.ZodEnum<["pending", "failed", "corrupt"]>;
127
+ fileName: z.ZodEffects<z.ZodString, string, string>;
128
+ }, "strip", z.ZodTypeAny, {
129
+ status: "pending" | "failed" | "corrupt";
130
+ fileName: string;
131
+ }, {
132
+ status: "pending" | "failed" | "corrupt";
133
+ fileName: string;
134
+ }>, undefined, AdminDeleteTaskResponse>;
135
+ };
136
+ //# sourceMappingURL=admin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin.d.ts","sourceRoot":"","sources":["../../src/api/admin.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,OAAO,KAAK,EAA0B,WAAW,EAAE,MAAM,SAAS,CAAA;AAClE,OAAO,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAS7E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAA;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAUtD,YAAY,EACV,oBAAoB,EACpB,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,uBAAuB,CAAA;AAoB9B,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,CAAA;AAE9D,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,KAAK,EAAE,mBAAmB,CAAA;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAgFD,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,aAAa,CAAA;IACnB,KAAK,EAAE,UAAU,GAAG;QAAE,kBAAkB,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACnD,MAAM,EAAE,cAAc,CAAA;IACtB,YAAY,EAAE,kBAAkB,GAAG,IAAI,CAAA;IACvC,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB;AAED,0CAA0C;AAC1C,MAAM,MAAM,mBAAmB,GAAG,WAAW,CAAC,eAAe,CAAC,CAAA;AAE9D,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,IAAI,EAAE,CAAA;IACd,YAAY,CAAC,EAAE,eAAe,EAAE,CAAA;CACjC;AAED,iDAAiD;AACjD,MAAM,MAAM,kBAAkB,GAAG,WAAW,CAAC,cAAc,CAAC,CAAA;AAE5D,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,wDAAwD;AACxD,MAAM,MAAM,sBAAsB,GAAG,WAAW,CAAC,kBAAkB,CAAC,CAAA;AAEpE,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,IAAI,CAAA;CACd;AAED,8DAA8D;AAC9D,MAAM,MAAM,uBAAuB,GAAG,WAAW,CAAC,mBAAmB,CAAC,CAAA;AAQtE,QAAA,MAAM,0BAA0B;;;;;;;;;EAK9B,CAAA;AACF,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAA;AAI7E,QAAA,MAAM,qBAAqB;;;;;;EAEzB,CAAA;AACF,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAA;AAOnE,QAAA,MAAM,sBAAsB;;;;;;;;;EAM1B,CAAA;AACF,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAA;AAkOrE;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAMf,CAAA"}