@aphexcms/cms-core 9.8.1 → 9.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (259) hide show
  1. package/dist/api/organizations.d.ts +2 -1
  2. package/dist/api/organizations.d.ts.map +1 -1
  3. package/dist/api/organizations.js +2 -1
  4. package/dist/api/schemas/documents.d.ts +1 -0
  5. package/dist/api/schemas/documents.d.ts.map +1 -1
  6. package/dist/api/schemas/documents.js +1 -0
  7. package/dist/api/schemas/jobs.d.ts +15 -0
  8. package/dist/api/schemas/jobs.d.ts.map +1 -0
  9. package/dist/api/schemas/jobs.js +17 -0
  10. package/dist/api/schemas/user.d.ts.map +1 -1
  11. package/dist/api/schemas/user.js +6 -2
  12. package/dist/auth/account-deletion.d.ts +35 -0
  13. package/dist/auth/account-deletion.d.ts.map +1 -0
  14. package/dist/auth/account-deletion.js +65 -0
  15. package/dist/auth/bootstrap.d.ts +104 -0
  16. package/dist/auth/bootstrap.d.ts.map +1 -0
  17. package/dist/auth/bootstrap.js +164 -0
  18. package/dist/auth/instance-state.d.ts +13 -0
  19. package/dist/auth/instance-state.d.ts.map +1 -0
  20. package/dist/auth/instance-state.js +30 -0
  21. package/dist/auth/invitation-status.d.ts +25 -0
  22. package/dist/auth/invitation-status.d.ts.map +1 -0
  23. package/dist/auth/invitation-status.js +41 -0
  24. package/dist/auth/sign-up-policy.d.ts +62 -0
  25. package/dist/auth/sign-up-policy.d.ts.map +1 -0
  26. package/dist/auth/sign-up-policy.js +120 -0
  27. package/dist/components/AdminApp.svelte +83 -6
  28. package/dist/components/AdminApp.svelte.d.ts.map +1 -1
  29. package/dist/components/admin/ActivityView.svelte +263 -13
  30. package/dist/components/admin/ActivityView.svelte.d.ts +10 -1
  31. package/dist/components/admin/ActivityView.svelte.d.ts.map +1 -1
  32. package/dist/db/interfaces/document.d.ts +9 -0
  33. package/dist/db/interfaces/document.d.ts.map +1 -1
  34. package/dist/db/interfaces/events.d.ts +29 -2
  35. package/dist/db/interfaces/events.d.ts.map +1 -1
  36. package/dist/db/interfaces/index.d.ts +1 -0
  37. package/dist/db/interfaces/index.d.ts.map +1 -1
  38. package/dist/db/interfaces/index.js +1 -0
  39. package/dist/db/interfaces/instance.d.ts +29 -0
  40. package/dist/db/interfaces/instance.d.ts.map +1 -1
  41. package/dist/db/interfaces/instance.js +8 -1
  42. package/dist/events/built-in-consumers.d.ts +20 -0
  43. package/dist/events/built-in-consumers.d.ts.map +1 -0
  44. package/dist/events/built-in-consumers.js +49 -0
  45. package/dist/events/catalog.d.ts +16 -0
  46. package/dist/events/catalog.d.ts.map +1 -1
  47. package/dist/events/catalog.js +17 -0
  48. package/dist/events/consumer.d.ts +14 -0
  49. package/dist/events/consumer.d.ts.map +1 -1
  50. package/dist/events/consumer.js +1 -0
  51. package/dist/events/emit.d.ts +23 -0
  52. package/dist/events/emit.d.ts.map +1 -1
  53. package/dist/events/emit.js +32 -1
  54. package/dist/events/index.d.ts +2 -0
  55. package/dist/events/index.d.ts.map +1 -1
  56. package/dist/events/index.js +2 -0
  57. package/dist/field-validation/utils.d.ts +27 -4
  58. package/dist/field-validation/utils.d.ts.map +1 -1
  59. package/dist/field-validation/utils.js +78 -10
  60. package/dist/index.d.ts +1 -0
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +3 -0
  63. package/dist/jobs/run-batch.d.ts +7 -1
  64. package/dist/jobs/run-batch.d.ts.map +1 -1
  65. package/dist/jobs/run-batch.js +3 -1
  66. package/dist/lib/api/organizations.d.ts +2 -1
  67. package/dist/lib/api/organizations.d.ts.map +1 -1
  68. package/dist/lib/api/organizations.js +2 -1
  69. package/dist/lib/api/organizations.js.map +1 -1
  70. package/dist/lib/api/schemas/api-keys.d.ts +4 -4
  71. package/dist/lib/api/schemas/documents.d.ts +1 -0
  72. package/dist/lib/api/schemas/documents.d.ts.map +1 -1
  73. package/dist/lib/api/schemas/documents.js +1 -0
  74. package/dist/lib/api/schemas/documents.js.map +1 -1
  75. package/dist/lib/api/schemas/jobs.d.ts +15 -0
  76. package/dist/lib/api/schemas/jobs.d.ts.map +1 -0
  77. package/dist/lib/api/schemas/jobs.js +18 -0
  78. package/dist/lib/api/schemas/jobs.js.map +1 -0
  79. package/dist/lib/api/schemas/user.d.ts.map +1 -1
  80. package/dist/lib/api/schemas/user.js +6 -2
  81. package/dist/lib/api/schemas/user.js.map +1 -1
  82. package/dist/lib/auth/account-deletion.d.ts +35 -0
  83. package/dist/lib/auth/account-deletion.d.ts.map +1 -0
  84. package/dist/lib/auth/account-deletion.js +66 -0
  85. package/dist/lib/auth/account-deletion.js.map +1 -0
  86. package/dist/lib/auth/bootstrap.d.ts +104 -0
  87. package/dist/lib/auth/bootstrap.d.ts.map +1 -0
  88. package/dist/lib/auth/bootstrap.js +165 -0
  89. package/dist/lib/auth/bootstrap.js.map +1 -0
  90. package/dist/lib/auth/instance-state.d.ts +13 -0
  91. package/dist/lib/auth/instance-state.d.ts.map +1 -0
  92. package/dist/lib/auth/instance-state.js +31 -0
  93. package/dist/lib/auth/instance-state.js.map +1 -0
  94. package/dist/lib/auth/invitation-status.d.ts +25 -0
  95. package/dist/lib/auth/invitation-status.d.ts.map +1 -0
  96. package/dist/lib/auth/invitation-status.js +42 -0
  97. package/dist/lib/auth/invitation-status.js.map +1 -0
  98. package/dist/lib/auth/sign-up-policy.d.ts +62 -0
  99. package/dist/lib/auth/sign-up-policy.d.ts.map +1 -0
  100. package/dist/lib/auth/sign-up-policy.js +121 -0
  101. package/dist/lib/auth/sign-up-policy.js.map +1 -0
  102. package/dist/lib/db/interfaces/document.d.ts +9 -0
  103. package/dist/lib/db/interfaces/document.d.ts.map +1 -1
  104. package/dist/lib/db/interfaces/events.d.ts +29 -2
  105. package/dist/lib/db/interfaces/events.d.ts.map +1 -1
  106. package/dist/lib/db/interfaces/index.d.ts +1 -0
  107. package/dist/lib/db/interfaces/index.d.ts.map +1 -1
  108. package/dist/lib/db/interfaces/index.js +1 -0
  109. package/dist/lib/db/interfaces/index.js.map +1 -1
  110. package/dist/lib/db/interfaces/instance.d.ts +29 -0
  111. package/dist/lib/db/interfaces/instance.d.ts.map +1 -1
  112. package/dist/lib/db/interfaces/instance.js +8 -1
  113. package/dist/lib/db/interfaces/instance.js.map +1 -1
  114. package/dist/lib/events/built-in-consumers.d.ts +20 -0
  115. package/dist/lib/events/built-in-consumers.d.ts.map +1 -0
  116. package/dist/lib/events/built-in-consumers.js +50 -0
  117. package/dist/lib/events/built-in-consumers.js.map +1 -0
  118. package/dist/lib/events/catalog.d.ts +16 -0
  119. package/dist/lib/events/catalog.d.ts.map +1 -1
  120. package/dist/lib/events/catalog.js +17 -0
  121. package/dist/lib/events/catalog.js.map +1 -1
  122. package/dist/lib/events/consumer.d.ts +14 -0
  123. package/dist/lib/events/consumer.d.ts.map +1 -1
  124. package/dist/lib/events/consumer.js +1 -0
  125. package/dist/lib/events/consumer.js.map +1 -1
  126. package/dist/lib/events/emit.d.ts +23 -0
  127. package/dist/lib/events/emit.d.ts.map +1 -1
  128. package/dist/lib/events/emit.js +32 -1
  129. package/dist/lib/events/emit.js.map +1 -1
  130. package/dist/lib/events/index.d.ts +2 -0
  131. package/dist/lib/events/index.d.ts.map +1 -1
  132. package/dist/lib/events/index.js +2 -0
  133. package/dist/lib/events/index.js.map +1 -1
  134. package/dist/lib/field-validation/utils.d.ts +27 -4
  135. package/dist/lib/field-validation/utils.d.ts.map +1 -1
  136. package/dist/lib/field-validation/utils.js +78 -10
  137. package/dist/lib/field-validation/utils.js.map +1 -1
  138. package/dist/lib/index.d.ts +1 -0
  139. package/dist/lib/index.d.ts.map +1 -1
  140. package/dist/lib/index.js +3 -0
  141. package/dist/lib/index.js.map +1 -1
  142. package/dist/lib/jobs/run-batch.d.ts +7 -1
  143. package/dist/lib/jobs/run-batch.d.ts.map +1 -1
  144. package/dist/lib/jobs/run-batch.js +3 -1
  145. package/dist/lib/jobs/run-batch.js.map +1 -1
  146. package/dist/lib/local-api/collection-api.d.ts +48 -1
  147. package/dist/lib/local-api/collection-api.d.ts.map +1 -1
  148. package/dist/lib/local-api/collection-api.js +110 -11
  149. package/dist/lib/local-api/collection-api.js.map +1 -1
  150. package/dist/lib/local-api/index.d.ts +1 -1
  151. package/dist/lib/local-api/index.d.ts.map +1 -1
  152. package/dist/lib/local-api/index.js +1 -1
  153. package/dist/lib/local-api/index.js.map +1 -1
  154. package/dist/lib/plugins/resolver.d.ts.map +1 -1
  155. package/dist/lib/plugins/resolver.js +7 -1
  156. package/dist/lib/plugins/resolver.js.map +1 -1
  157. package/dist/lib/schema-utils/utils.d.ts +31 -1
  158. package/dist/lib/schema-utils/utils.d.ts.map +1 -1
  159. package/dist/lib/schema-utils/utils.js +68 -0
  160. package/dist/lib/schema-utils/utils.js.map +1 -1
  161. package/dist/lib/server/api/rate-limit.d.ts +38 -0
  162. package/dist/lib/server/api/rate-limit.d.ts.map +1 -0
  163. package/dist/lib/server/api/rate-limit.js +71 -0
  164. package/dist/lib/server/api/rate-limit.js.map +1 -0
  165. package/dist/lib/server/api/routes/documents-by-id.d.ts.map +1 -1
  166. package/dist/lib/server/api/routes/documents-by-id.js +10 -1
  167. package/dist/lib/server/api/routes/documents-by-id.js.map +1 -1
  168. package/dist/lib/server/api/routes/documents-publish.d.ts.map +1 -1
  169. package/dist/lib/server/api/routes/documents-publish.js +10 -0
  170. package/dist/lib/server/api/routes/documents-publish.js.map +1 -1
  171. package/dist/lib/server/api/routes/documents.d.ts.map +1 -1
  172. package/dist/lib/server/api/routes/documents.js +11 -0
  173. package/dist/lib/server/api/routes/documents.js.map +1 -1
  174. package/dist/lib/server/api/routes/jobs.d.ts.map +1 -1
  175. package/dist/lib/server/api/routes/jobs.js +174 -20
  176. package/dist/lib/server/api/routes/jobs.js.map +1 -1
  177. package/dist/lib/server/api/routes/organizations-by-id.d.ts.map +1 -1
  178. package/dist/lib/server/api/routes/organizations-by-id.js +61 -3
  179. package/dist/lib/server/api/routes/organizations-by-id.js.map +1 -1
  180. package/dist/lib/server/api/routes/organizations-invitations.d.ts.map +1 -1
  181. package/dist/lib/server/api/routes/organizations-invitations.js +31 -2
  182. package/dist/lib/server/api/routes/organizations-invitations.js.map +1 -1
  183. package/dist/lib/server/api/routes/resolve-organization-names.d.ts +17 -0
  184. package/dist/lib/server/api/routes/resolve-organization-names.d.ts.map +1 -0
  185. package/dist/lib/server/api/routes/resolve-organization-names.js +26 -0
  186. package/dist/lib/server/api/routes/resolve-organization-names.js.map +1 -0
  187. package/dist/lib/server/api/routes/user.d.ts +0 -8
  188. package/dist/lib/server/api/routes/user.d.ts.map +1 -1
  189. package/dist/lib/server/api/routes/user.js +144 -0
  190. package/dist/lib/server/api/routes/user.js.map +1 -1
  191. package/dist/lib/server/index.d.ts +6 -1
  192. package/dist/lib/server/index.d.ts.map +1 -1
  193. package/dist/lib/server/index.js +9 -1
  194. package/dist/lib/server/index.js.map +1 -1
  195. package/dist/lib/type-gen.d.ts.map +1 -1
  196. package/dist/lib/type-gen.js +11 -5
  197. package/dist/lib/type-gen.js.map +1 -1
  198. package/dist/lib/types/capabilities.d.ts +14 -0
  199. package/dist/lib/types/capabilities.d.ts.map +1 -1
  200. package/dist/lib/types/capabilities.js +37 -18
  201. package/dist/lib/types/capabilities.js.map +1 -1
  202. package/dist/lib/types/events.d.ts +27 -4
  203. package/dist/lib/types/events.d.ts.map +1 -1
  204. package/dist/lib/types/filters.d.ts +31 -0
  205. package/dist/lib/types/filters.d.ts.map +1 -1
  206. package/dist/lib/types/schemas.d.ts +14 -0
  207. package/dist/lib/types/schemas.d.ts.map +1 -1
  208. package/dist/lib/utils/fetch-remote-file.d.ts.map +1 -1
  209. package/dist/lib/utils/fetch-remote-file.js +32 -3
  210. package/dist/lib/utils/fetch-remote-file.js.map +1 -1
  211. package/dist/local-api/collection-api.d.ts +48 -1
  212. package/dist/local-api/collection-api.d.ts.map +1 -1
  213. package/dist/local-api/collection-api.js +110 -11
  214. package/dist/local-api/index.d.ts +1 -1
  215. package/dist/local-api/index.d.ts.map +1 -1
  216. package/dist/local-api/index.js +1 -1
  217. package/dist/plugins/resolver.d.ts.map +1 -1
  218. package/dist/plugins/resolver.js +7 -1
  219. package/dist/schema-utils/utils.d.ts +31 -1
  220. package/dist/schema-utils/utils.d.ts.map +1 -1
  221. package/dist/schema-utils/utils.js +68 -0
  222. package/dist/server/api/rate-limit.d.ts +38 -0
  223. package/dist/server/api/rate-limit.d.ts.map +1 -0
  224. package/dist/server/api/rate-limit.js +70 -0
  225. package/dist/server/api/routes/documents-by-id.d.ts.map +1 -1
  226. package/dist/server/api/routes/documents-by-id.js +10 -1
  227. package/dist/server/api/routes/documents-publish.d.ts.map +1 -1
  228. package/dist/server/api/routes/documents-publish.js +10 -0
  229. package/dist/server/api/routes/documents.d.ts.map +1 -1
  230. package/dist/server/api/routes/documents.js +11 -0
  231. package/dist/server/api/routes/jobs.d.ts.map +1 -1
  232. package/dist/server/api/routes/jobs.js +174 -20
  233. package/dist/server/api/routes/organizations-by-id.d.ts.map +1 -1
  234. package/dist/server/api/routes/organizations-by-id.js +61 -3
  235. package/dist/server/api/routes/organizations-invitations.d.ts.map +1 -1
  236. package/dist/server/api/routes/organizations-invitations.js +31 -2
  237. package/dist/server/api/routes/resolve-organization-names.d.ts +17 -0
  238. package/dist/server/api/routes/resolve-organization-names.d.ts.map +1 -0
  239. package/dist/server/api/routes/resolve-organization-names.js +25 -0
  240. package/dist/server/api/routes/user.d.ts +0 -8
  241. package/dist/server/api/routes/user.d.ts.map +1 -1
  242. package/dist/server/api/routes/user.js +144 -0
  243. package/dist/server/index.d.ts +6 -1
  244. package/dist/server/index.d.ts.map +1 -1
  245. package/dist/server/index.js +9 -1
  246. package/dist/type-gen.d.ts.map +1 -1
  247. package/dist/type-gen.js +11 -5
  248. package/dist/types/capabilities.d.ts +14 -0
  249. package/dist/types/capabilities.d.ts.map +1 -1
  250. package/dist/types/capabilities.js +37 -18
  251. package/dist/types/events.d.ts +27 -4
  252. package/dist/types/events.d.ts.map +1 -1
  253. package/dist/types/filters.d.ts +31 -0
  254. package/dist/types/filters.d.ts.map +1 -1
  255. package/dist/types/schemas.d.ts +14 -0
  256. package/dist/types/schemas.d.ts.map +1 -1
  257. package/dist/utils/fetch-remote-file.d.ts.map +1 -1
  258. package/dist/utils/fetch-remote-file.js +32 -3
  259. package/package.json +6 -6
@@ -7,25 +7,58 @@
7
7
  import { onMount } from 'svelte';
8
8
  import { Button } from '@aphexcms/ui/shadcn/button';
9
9
  import { Badge } from '@aphexcms/ui/shadcn/badge';
10
- import { RefreshCw, CalendarClock, Radio, Sparkles, Undo2, ChevronDown } from '@lucide/svelte';
10
+ import {
11
+ RefreshCw,
12
+ CalendarClock,
13
+ Radio,
14
+ Sparkles,
15
+ Undo2,
16
+ ChevronDown,
17
+ RotateCcw,
18
+ Ban,
19
+ TriangleAlert
20
+ } from '@lucide/svelte';
11
21
  import { toast } from 'svelte-sonner';
12
22
  import { apiClient } from '../../api/client';
13
- import type { Job, DomainEvent, JobStatus } from '../../types/events';
23
+ import type { Job, DomainEvent, JobStatus, OutboxHealth } from '../../types/events';
14
24
  import type {
15
25
  AgentChangeSet,
16
26
  AgentChangeSetWithOperations,
17
27
  AgentChangeSetStatus
18
28
  } from '../../types/agent-change-sets';
19
29
 
30
+ type Props = {
31
+ /**
32
+ * May the viewer retry/cancel jobs? `org.settings` — see `requireJobControl` in the
33
+ * route. Gating here only hides buttons; the server is what enforces it.
34
+ */
35
+ canControlJobs?: boolean;
36
+ /** Super admins can widen jobs/events past their active organization (`?scope=all`). */
37
+ isSuperAdmin?: boolean;
38
+ };
39
+ let { canControlJobs = false, isSuperAdmin = false }: Props = $props();
40
+
20
41
  // The server resolves `createdBy` (a raw user id) to a display name — see
21
42
  // `resolve-created-by.ts`, shared by GET /api/events and GET /api/agent/change-sets.
22
43
  type WithCreatedByName<T> = T & { createdByName: string | null };
44
+ // …and `organizationId` to an org name, but only in the instance-wide view, where rows
45
+ // from several tenants share a table. Absent (not null) in the org-scoped view.
46
+ type WithOrganizationName<T> = T & { organizationName?: string | null };
23
47
 
24
48
  type Tab = 'jobs' | 'events' | 'agent';
25
49
  let tab = $state<Tab>('jobs');
26
50
 
27
- let jobs = $state<Job[]>([]);
28
- let events = $state<WithCreatedByName<DomainEvent>[]>([]);
51
+ /**
52
+ * Whose history is on screen. `all` is a super-admin-only widening: the queue is
53
+ * instance-wide (one worker claims across every org), so a failure in a tenant you
54
+ * aren't currently switched into is otherwise invisible.
55
+ */
56
+ let scope = $state<'organization' | 'all'>('organization');
57
+
58
+ let jobs = $state<WithOrganizationName<Job>[]>([]);
59
+ let events = $state<WithOrganizationName<WithCreatedByName<DomainEvent>>[]>([]);
60
+ let health = $state<OutboxHealth | null>(null);
61
+ let actingJobId = $state<string | null>(null);
29
62
  let changeSets = $state<WithCreatedByName<AgentChangeSetWithOperations>[]>([]);
30
63
  let loading = $state(false);
31
64
  let error = $state<string | null>(null);
@@ -89,24 +122,86 @@
89
122
  return Number.isNaN(date.getTime()) ? '—' : date.toLocaleString();
90
123
  }
91
124
 
125
+ /** "3m", "2h", "4d" — coarse on purpose; this is a staleness signal, not a stopwatch. */
126
+ function age(d: string | Date): string {
127
+ const ms = Date.now() - new Date(d).getTime();
128
+ if (ms < 60_000) return `${Math.max(0, Math.round(ms / 1000))}s`;
129
+ if (ms < 3_600_000) return `${Math.round(ms / 60_000)}m`;
130
+ if (ms < 86_400_000) return `${Math.round(ms / 3_600_000)}h`;
131
+ return `${Math.round(ms / 86_400_000)}d`;
132
+ }
133
+
134
+ // A backlog older than this means the relay has stopped, not that it's busy. The
135
+ // self-hosted worker polls every 5s by default and platform cron runs at most once a
136
+ // minute, so a minute is the shortest gap that can't be explained by normal cadence.
137
+ const RELAY_STALE_MS = 60_000;
138
+
139
+ /**
140
+ * The backlog, pre-chewed into exactly what the banner renders — or null when there's
141
+ * nothing to say. Deriving the whole object (rather than a `stalled` flag beside the raw
142
+ * `health`) is what lets the markup read `oldestAge` without re-proving that
143
+ * `oldestPendingAt` is non-null: `pending > 0` implies a row exists, but only this
144
+ * narrowing tells the compiler so.
145
+ */
146
+ const relayBacklog = $derived.by(() => {
147
+ const oldest = health?.oldestPendingAt;
148
+ if (!health || health.pending === 0 || !oldest) return null;
149
+ return {
150
+ pending: health.pending,
151
+ oldestAge: age(oldest),
152
+ stalled: Date.now() - new Date(oldest).getTime() > RELAY_STALE_MS
153
+ };
154
+ });
155
+
156
+ /**
157
+ * A job whose lease has run out but which is still marked `leased` — the worker holding
158
+ * it died mid-run. It isn't lost (the next `claimDueJobs` reclaims expired leases), but
159
+ * it looks identical to healthy in-flight work without this.
160
+ */
161
+ function leaseExpired(job: Job): boolean {
162
+ return (
163
+ job.status === 'leased' && !!job.leaseExpiresAt && new Date(job.leaseExpiresAt) < new Date()
164
+ );
165
+ }
166
+
167
+ /** Requeue puts a dead letter back with a fresh attempt budget; cancel retires it. */
168
+ const canRetry = (job: Job) => job.status === 'failed' || job.status === 'cancelled';
169
+ const canCancel = (job: Job) => job.status === 'pending' || job.status === 'failed';
170
+
171
+ /** Only send `scope` when it's actually widened — keeps the default request unchanged. */
172
+ function scopeParam(): Record<string, string> {
173
+ return scope === 'all' ? { scope: 'all' } : {};
174
+ }
175
+
92
176
  async function load() {
93
177
  loading = true;
94
178
  error = null;
95
179
  try {
96
180
  if (tab === 'jobs') {
97
- const params: Record<string, string> = { limit: '100' };
181
+ const params: Record<string, string> = { limit: '100', ...scopeParam() };
98
182
  if (statusFilter !== 'all') params.status = statusFilter;
99
- const res = await apiClient.get<Job[]>('/jobs', params);
183
+ // Health rides along with the list: it's the context for everything below it,
184
+ // and a separate refresh button for one number would be its own small trap.
185
+ const [res, healthRes] = await Promise.all([
186
+ apiClient.get<WithOrganizationName<Job>[]>('/jobs', params),
187
+ apiClient.get<OutboxHealth>('/jobs/health', scopeParam())
188
+ ]);
100
189
  if (res.success) {
101
190
  jobs = res.data ?? [];
102
191
  total = res.pagination?.total ?? jobs.length;
103
192
  } else {
104
193
  error = res.error ?? 'Failed to load jobs';
105
194
  }
195
+ // A failed health read shouldn't blank the page — the list is still useful.
196
+ health = healthRes.success ? (healthRes.data ?? null) : null;
106
197
  } else if (tab === 'events') {
107
- const res = await apiClient.get<WithCreatedByName<DomainEvent>[]>('/events', {
108
- limit: '100'
109
- });
198
+ const res = await apiClient.get<WithOrganizationName<WithCreatedByName<DomainEvent>>[]>(
199
+ '/events',
200
+ {
201
+ limit: '100',
202
+ ...scopeParam()
203
+ }
204
+ );
110
205
  if (res.success) {
111
206
  events = res.data ?? [];
112
207
  total = res.pagination?.total ?? events.length;
@@ -138,6 +233,34 @@
138
233
  load();
139
234
  }
140
235
 
236
+ /**
237
+ * Retry and cancel share everything but the verb and the past-tense confirmation, so they
238
+ * share a body. `organizationId` is always sent: in the instance-wide view the job may
239
+ * belong to a tenant the caller isn't switched into, and the server needs to be told which
240
+ * one (it rejects the mismatch unless you're a super admin).
241
+ */
242
+ async function actOnJob(job: Job, action: 'retry' | 'cancel') {
243
+ if (actingJobId) return;
244
+ actingJobId = job.id;
245
+ try {
246
+ const res = await apiClient.post<Job>(`/jobs/${job.id}/${action}`, {
247
+ organizationId: job.organizationId
248
+ });
249
+ if (!res.success) {
250
+ toast.error(res.error ?? `Could not ${action} this job`);
251
+ return;
252
+ }
253
+ toast.success(
254
+ action === 'retry' ? 'Job requeued — it runs on the next tick.' : 'Job cancelled.'
255
+ );
256
+ await load();
257
+ } catch (err) {
258
+ toast.error(err instanceof Error ? err.message : `Could not ${action} this job`);
259
+ } finally {
260
+ actingJobId = null;
261
+ }
262
+ }
263
+
141
264
  async function toggleExpand(changeSet: AgentChangeSet) {
142
265
  if (expandedChangeSetId === changeSet.id) {
143
266
  expandedChangeSetId = null;
@@ -196,6 +319,13 @@
196
319
  if (tab === 'jobs') load();
197
320
  });
198
321
 
322
+ // Scope applies to jobs and events alike, so this one reloads whichever is showing.
323
+ $effect(() => {
324
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions -- reactive dependency: re-run when scope changes
325
+ scope;
326
+ if (tab !== 'agent') load();
327
+ });
328
+
199
329
  onMount(load);
200
330
  </script>
201
331
 
@@ -207,9 +337,32 @@
207
337
  Scheduled jobs, the domain-event log, and the AI assistant's audit trail.
208
338
  </p>
209
339
  </div>
210
- <Button variant="outline" size="sm" onclick={load} disabled={loading} class="gap-1.5">
211
- <RefreshCw class="h-3.5 w-3.5 {loading ? 'animate-spin' : ''}" /> Refresh
212
- </Button>
340
+ <div class="flex items-center gap-2">
341
+ {#if isSuperAdmin && tab !== 'agent'}
342
+ <!-- The queue is instance-wide; this is the only view that can show it that way. -->
343
+ <div class="border-rule flex items-center rounded-md border p-0.5 text-xs">
344
+ <button
345
+ class="rounded px-2 py-1 transition-colors {scope === 'organization'
346
+ ? 'bg-muted text-foreground'
347
+ : 'text-muted-foreground hover:text-foreground'}"
348
+ onclick={() => (scope = 'organization')}
349
+ >
350
+ This workspace
351
+ </button>
352
+ <button
353
+ class="rounded px-2 py-1 transition-colors {scope === 'all'
354
+ ? 'bg-muted text-foreground'
355
+ : 'text-muted-foreground hover:text-foreground'}"
356
+ onclick={() => (scope = 'all')}
357
+ >
358
+ All workspaces
359
+ </button>
360
+ </div>
361
+ {/if}
362
+ <Button variant="outline" size="sm" onclick={load} disabled={loading} class="gap-1.5">
363
+ <RefreshCw class="h-3.5 w-3.5 {loading ? 'animate-spin' : ''}" /> Refresh
364
+ </Button>
365
+ </div>
213
366
  </div>
214
367
 
215
368
  <!-- Tabs -->
@@ -244,6 +397,43 @@
244
397
  </div>
245
398
 
246
399
  {#if tab === 'jobs'}
400
+ {#if relayBacklog}
401
+ <!--
402
+ The relay backlog. A count on its own is meaningless — a busy instance always has
403
+ rows in flight — so the age of the oldest is what decides the tone: past a minute,
404
+ nothing is draining the outbox and every consumer has silently stopped.
405
+ -->
406
+ <div
407
+ class="mb-3 flex items-start gap-2 rounded-md border p-3 text-sm {relayBacklog.stalled
408
+ ? 'border-destructive/40 bg-destructive/10 text-destructive'
409
+ : 'border-rule text-muted-foreground'}"
410
+ >
411
+ {#if relayBacklog.stalled}
412
+ <TriangleAlert class="mt-0.5 h-4 w-4 shrink-0" />
413
+ {:else}
414
+ <Radio class="mt-0.5 h-4 w-4 shrink-0" />
415
+ {/if}
416
+ <div>
417
+ {#if relayBacklog.stalled}
418
+ <p class="font-medium">
419
+ The relay looks stopped — {relayBacklog.pending} event{relayBacklog.pending === 1
420
+ ? ''
421
+ : 's'} waiting, oldest {relayBacklog.oldestAge} old.
422
+ </p>
423
+ <p class="mt-0.5 text-xs">
424
+ Nothing is reacting to events: scheduled publishes, erasure and plugin consumers are
425
+ all paused until a worker calls <code>POST /api/internal/workers/run</code> again.
426
+ </p>
427
+ {:else}
428
+ <p>
429
+ {relayBacklog.pending} event{relayBacklog.pending === 1 ? '' : 's'} waiting to fan out,
430
+ oldest {relayBacklog.oldestAge} old — the relay is keeping up.
431
+ </p>
432
+ {/if}
433
+ </div>
434
+ </div>
435
+ {/if}
436
+
247
437
  <div class="mb-3 flex flex-wrap items-center gap-1.5">
248
438
  {#each statuses as s (s)}
249
439
  <button
@@ -276,19 +466,42 @@
276
466
  <thead class="bg-muted/50 text-muted-foreground text-xs">
277
467
  <tr>
278
468
  <th class="px-3 py-2 text-left font-medium">Type</th>
469
+ {#if scope === 'all'}
470
+ <th class="px-3 py-2 text-left font-medium">Workspace</th>
471
+ {/if}
279
472
  <th class="px-3 py-2 text-left font-medium">Status</th>
280
473
  <th class="px-3 py-2 text-left font-medium">Run at</th>
281
474
  <th class="px-3 py-2 text-left font-medium">Attempts</th>
282
475
  <th class="px-3 py-2 text-left font-medium">Last error</th>
283
476
  <th class="px-3 py-2 text-left font-medium">Created</th>
477
+ {#if canControlJobs}
478
+ <th class="px-3 py-2 text-right font-medium">Actions</th>
479
+ {/if}
284
480
  </tr>
285
481
  </thead>
286
482
  <tbody>
287
483
  {#each jobs as job (job.id)}
288
484
  <tr class="border-rule border-t">
289
485
  <td class="px-3 py-2 font-mono text-xs">{job.type}</td>
290
- <td class="px-3 py-2">
486
+ {#if scope === 'all'}
487
+ <td class="text-muted-foreground px-3 py-2 text-xs">
488
+ {job.organizationName ?? job.organizationId}
489
+ </td>
490
+ {/if}
491
+ <td class="px-3 py-2 whitespace-nowrap">
291
492
  <Badge variant={statusVariant[job.status]} class="capitalize">{job.status}</Badge>
493
+ {#if leaseExpired(job)}
494
+ <!-- Still `leased` past its expiry: the worker that held it died. The next
495
+ claim reclaims it, so this is a diagnosis, not an action item. -->
496
+ <span
497
+ class="text-destructive ml-1.5 text-xs"
498
+ title="Lease expired {age(
499
+ job.leaseExpiresAt ?? new Date()
500
+ )} ago — the worker holding this job stopped. It will be reclaimed on the next tick."
501
+ >
502
+ stalled
503
+ </span>
504
+ {/if}
292
505
  </td>
293
506
  <td class="px-3 py-2 whitespace-nowrap">{fmt(job.runAt)}</td>
294
507
  <td class="px-3 py-2">{job.attempts}/{job.maxAttempts}</td>
@@ -301,6 +514,35 @@
301
514
  <td class="text-muted-foreground px-3 py-2 whitespace-nowrap"
302
515
  >{fmt(job.createdAt)}</td
303
516
  >
517
+ {#if canControlJobs}
518
+ <td class="px-3 py-2 text-right whitespace-nowrap">
519
+ {#if canRetry(job)}
520
+ <Button
521
+ variant="ghost"
522
+ size="sm"
523
+ class="h-7 gap-1 px-2 text-xs"
524
+ disabled={actingJobId !== null}
525
+ onclick={() => actOnJob(job, 'retry')}
526
+ >
527
+ <RotateCcw class="h-3 w-3" /> Retry
528
+ </Button>
529
+ {/if}
530
+ {#if canCancel(job)}
531
+ <Button
532
+ variant="ghost"
533
+ size="sm"
534
+ class="text-muted-foreground h-7 gap-1 px-2 text-xs"
535
+ disabled={actingJobId !== null}
536
+ onclick={() => actOnJob(job, 'cancel')}
537
+ >
538
+ <Ban class="h-3 w-3" /> Cancel
539
+ </Button>
540
+ {/if}
541
+ {#if !canRetry(job) && !canCancel(job)}
542
+ <span class="text-muted-foreground text-xs">—</span>
543
+ {/if}
544
+ </td>
545
+ {/if}
304
546
  </tr>
305
547
  {/each}
306
548
  </tbody>
@@ -317,6 +559,9 @@
317
559
  <thead class="bg-muted/50 text-muted-foreground text-xs">
318
560
  <tr>
319
561
  <th class="px-3 py-2 text-left font-medium">Type</th>
562
+ {#if scope === 'all'}
563
+ <th class="px-3 py-2 text-left font-medium">Workspace</th>
564
+ {/if}
320
565
  <th class="px-3 py-2 text-left font-medium">Payload</th>
321
566
  <th class="px-3 py-2 text-left font-medium">By</th>
322
567
  <th class="px-3 py-2 text-left font-medium">When</th>
@@ -326,6 +571,11 @@
326
571
  {#each events as event (event.id)}
327
572
  <tr class="border-rule border-t">
328
573
  <td class="px-3 py-2 font-mono text-xs">{event.type}</td>
574
+ {#if scope === 'all'}
575
+ <td class="text-muted-foreground px-3 py-2 text-xs">
576
+ {event.organizationName ?? event.organizationId}
577
+ </td>
578
+ {/if}
329
579
  <td
330
580
  class="text-muted-foreground max-w-[320px] truncate px-3 py-2 font-mono text-xs"
331
581
  title={JSON.stringify(event.payload)}
@@ -1,4 +1,13 @@
1
- declare const ActivityView: import("svelte").Component<Record<string, never>, {}, "">;
1
+ type Props = {
2
+ /**
3
+ * May the viewer retry/cancel jobs? `org.settings` — see `requireJobControl` in the
4
+ * route. Gating here only hides buttons; the server is what enforces it.
5
+ */
6
+ canControlJobs?: boolean;
7
+ /** Super admins can widen jobs/events past their active organization (`?scope=all`). */
8
+ isSuperAdmin?: boolean;
9
+ };
10
+ declare const ActivityView: import("svelte").Component<Props, {}, "">;
2
11
  type ActivityView = ReturnType<typeof ActivityView>;
3
12
  export default ActivityView;
4
13
  //# sourceMappingURL=ActivityView.svelte.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ActivityView.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/components/admin/ActivityView.svelte.ts"],"names":[],"mappings":"AAoZA,QAAA,MAAM,YAAY,2DAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"ActivityView.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/components/admin/ActivityView.svelte.ts"],"names":[],"mappings":"AAgCC,KAAK,KAAK,GAAG;IACZ;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,wFAAwF;IACxF,YAAY,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AA8kBH,QAAA,MAAM,YAAY,2CAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
@@ -52,6 +52,15 @@ export interface DocumentAdapter {
52
52
  */
53
53
  updateDocDraft(organizationId: string, id: string, data: any, updatedBy?: string, expectedRevision?: number): Promise<Document | null>;
54
54
  deleteDocById(organizationId: string, id: string): Promise<boolean>;
55
+ /**
56
+ * Recompute the document's precomputed full-text search index (`search_text`
57
+ * column, and on SQLite the FTS5 shadow table row) from caller-supplied text.
58
+ * Called by `CollectionAPI` as a best-effort step after `createDocument`/
59
+ * `updateDocDraft` succeeds — same shape as reference-index sync: not folded
60
+ * into the write transaction, self-healing on the next edit if it's missed.
61
+ * Optional so adapters without full-text search aren't broken.
62
+ */
63
+ updateSearchText?(organizationId: string, id: string, searchText: string): Promise<void>;
55
64
  publishDoc(organizationId: string, id: string, expectedRevision?: number): Promise<Document | null>;
56
65
  unpublishDoc(organizationId: string, id: string, expectedRevision?: number): Promise<Document | null>;
57
66
  countDocsByType(organizationId: string, type: string): Promise<number>;
@@ -1 +1 @@
1
- {"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../../../src/lib/db/interfaces/document.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAChF,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAE1E,MAAM,WAAW,eAAe;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;CACjC;AAED,MAAM,WAAW,kBAAkB;IAClC,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,GAAG,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mEAAmE;IACnE,EAAE,CAAC,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,kBAAkB;IAClC,SAAS,CAAC,EAAE,GAAG,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;6DACyD;IACzD,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,qBAAa,qBAAsB,SAAQ,KAAK;aAG9B,UAAU,EAAE,MAAM;aAClB,gBAAgB,EAAE,MAAM;aACxB,eAAe,EAAE,MAAM;gBAHvC,OAAO,EAAE,MAAM,EACC,UAAU,EAAE,MAAM,EAClB,gBAAgB,EAAE,MAAM,EACxB,eAAe,EAAE,MAAM;CAKxC;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAE/B,cAAc,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC5D;;;;;OAKG;IACH,cAAc,CACb,cAAc,EAAE,MAAM,EACtB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,GAAG,EACT,SAAS,CAAC,EAAE,MAAM,EAClB,gBAAgB,CAAC,EAAE,MAAM,GACvB,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC;IAC5B,aAAa,CAAC,cAAc,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAGpE,UAAU,CACT,cAAc,EAAE,MAAM,EACtB,EAAE,EAAE,MAAM,EACV,gBAAgB,CAAC,EAAE,MAAM,GACvB,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC;IAC5B,YAAY,CACX,cAAc,EAAE,MAAM,EACtB,EAAE,EAAE,MAAM,EACV,gBAAgB,CAAC,EAAE,MAAM,GACvB,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC;IAG5B,eAAe,CAAC,cAAc,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACvE,kBAAkB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAG5E;;;;;;OAMG;IACH,mBAAmB,CAClB,cAAc,EAAE,MAAM,EACtB,cAAc,EAAE,MAAM,EACtB,OAAO,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC;IAEjC;;;;;;OAMG;IACH,mBAAmB,CAClB,cAAc,EAAE,MAAM,EACtB,EAAE,EAAE,MAAM,EACV,OAAO,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAC5B,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC;IAE5B;;;;;;OAMG;IACH,cAAc,CAAC,cAAc,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAE/F;;;;;;OAMG;IACH,6BAA6B,CAAC,CAC7B,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,MAAM,EACf,UAAU,CAAC,EAAE,MAAM,EAAE,GACnB,OAAO,CAAC,KAAK,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC,CAAC;IAE9F;;;;;;OAMG;IACH,gCAAgC,CAAC,CAChC,cAAc,EAAE,MAAM,EACtB,QAAQ,EAAE,MAAM,EAAE,EAClB,UAAU,CAAC,EAAE,MAAM,EAAE,GACnB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAEnC;;;OAGG;IACH,2BAA2B,CAAC,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAGvF,qBAAqB,CAAC,CAAC,IAAI,EAAE;QAC5B,UAAU,EAAE,MAAM,CAAC;QACnB,cAAc,EAAE,MAAM,CAAC;QACvB,SAAS,EAAE,OAAO,GAAG,SAAS,CAAC;QAC/B,IAAI,EAAE,GAAG,CAAC;QACV,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC1B,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;IAEpC,oBAAoB,CAAC,CACpB,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAC3C,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAEhC,kBAAkB,CAAC,CAClB,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,MAAM,GACnB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;IAEnC,sBAAsB,CAAC,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACjF"}
1
+ {"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../../../src/lib/db/interfaces/document.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import type { DomainEvent, AppendEventInput, Job, ScheduleJobInput, ClaimJobsOptions, ListEventsOptions, ListJobsOptions, OutboxRow, ListUnprocessedOutboxOptions, Page } from '../../types/events.js';
1
+ import type { DomainEvent, AppendEventInput, Job, ScheduleJobInput, ClaimJobsOptions, ListEventsOptions, ListJobsOptions, OutboxRow, ListUnprocessedOutboxOptions, OutboxHealth, Page } from '../../types/events.js';
2
2
  /**
3
3
  * The durable spine: an append-only domain-event log plus a DB-backed job queue.
4
4
  * All rows are organization-scoped. (The event-log read API — `listEvents` — lands
@@ -25,6 +25,13 @@ export interface EventJobAdapter {
25
25
  listUnprocessedOutbox(options: ListUnprocessedOutboxOptions): Promise<OutboxRow[]>;
26
26
  /** Stamp `processedAt` once an outbox row has been fanned out to every subscriber. */
27
27
  markOutboxProcessed(organizationId: string, id: string): Promise<void>;
28
+ /**
29
+ * Size and age of the unprocessed backlog — the "is the relay running?" signal.
30
+ * Omit `organizationId` for the instance-wide figure. See {@link OutboxHealth}.
31
+ */
32
+ outboxHealth(options: {
33
+ organizationId?: string;
34
+ }): Promise<OutboxHealth>;
28
35
  /** Schedule a job. Idempotent when `idempotencyKey` is set: a duplicate key returns the existing job. */
29
36
  scheduleJob(input: ScheduleJobInput): Promise<Job>;
30
37
  /**
@@ -49,7 +56,27 @@ export interface EventJobAdapter {
49
56
  }): Promise<void>;
50
57
  /** Cancel a job (e.g. a scheduled publish the user called off): status → `cancelled`, clear the lease. */
51
58
  cancelJob(organizationId: string, id: string): Promise<void>;
52
- /** List jobs for the org, newest first (read-only history / observability). */
59
+ /** Read a single job by id (org-scoped). Null when it doesn't exist in that org. */
60
+ getJob(organizationId: string, id: string): Promise<Job | null>;
61
+ /**
62
+ * Put a finished job back on the queue by hand — the operator's undo for a dead letter.
63
+ *
64
+ * Distinct from `retryJob`, which is the *runner's* backoff transition and deliberately
65
+ * leaves `attempts` alone. A dead-lettered job sits at `attempts === maxAttempts`, so
66
+ * reusing `retryJob` would hand it back to the runner only for the next claim to exhaust
67
+ * it again immediately. This resets the attempt counter and clears `lastError`, giving
68
+ * the job a fresh budget once whatever broke has been fixed.
69
+ *
70
+ * Guarded to `failed` and `cancelled` in SQL: requeueing a `pending` job is pointless and
71
+ * requeueing a `leased` one would race the worker currently holding it, whose settle would
72
+ * then overwrite the status. Returns the updated job, or null when the id doesn't exist in
73
+ * the org or wasn't in a requeueable state — so callers can tell "gone" from "not allowed"
74
+ * with a follow-up `getJob`.
75
+ */
76
+ requeueJob(organizationId: string, id: string, options: {
77
+ runAt: Date;
78
+ }): Promise<Job | null>;
79
+ /** List jobs, newest first (read-only history / observability). Omit `organizationId` for instance-wide. */
53
80
  listJobs(options: ListJobsOptions): Promise<Page<Job>>;
54
81
  }
55
82
  //# sourceMappingURL=events.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../../src/lib/db/interfaces/events.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACX,WAAW,EACX,gBAAgB,EAChB,GAAG,EACH,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,SAAS,EACT,4BAA4B,EAC5B,IAAI,EACJ,MAAM,oBAAoB,CAAC;AAE5B;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAE/B;;;;;;OAMG;IACH,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAC3D,8CAA8C;IAC9C,QAAQ,CAAC,cAAc,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAC1E,wFAAwF;IACxF,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;IAGnE;;;;OAIG;IACH,qBAAqB,CAAC,OAAO,EAAE,4BAA4B,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;IACnF,sFAAsF;IACtF,mBAAmB,CAAC,cAAc,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAGvE,yGAAyG;IACzG,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IACnD;;;OAGG;IACH,YAAY,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IACxD,oCAAoC;IACpC,WAAW,CAAC,cAAc,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D;;;;OAIG;IACH,QAAQ,CACP,cAAc,EAAE,MAAM,EACtB,EAAE,EAAE,MAAM,EACV,OAAO,EAAE;QAAE,KAAK,EAAE,IAAI,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GACrC,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,uFAAuF;IACvF,OAAO,CAAC,cAAc,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvF,0GAA0G;IAC1G,SAAS,CAAC,cAAc,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7D,+EAA+E;IAC/E,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;CACvD"}
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../../src/lib/db/interfaces/events.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACX,WAAW,EACX,gBAAgB,EAChB,GAAG,EACH,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,SAAS,EACT,4BAA4B,EAC5B,YAAY,EACZ,IAAI,EACJ,MAAM,oBAAoB,CAAC;AAE5B;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAE/B;;;;;;OAMG;IACH,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAC3D,8CAA8C;IAC9C,QAAQ,CAAC,cAAc,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAC1E,wFAAwF;IACxF,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;IAGnE;;;;OAIG;IACH,qBAAqB,CAAC,OAAO,EAAE,4BAA4B,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;IACnF,sFAAsF;IACtF,mBAAmB,CAAC,cAAc,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvE;;;OAGG;IACH,YAAY,CAAC,OAAO,EAAE;QAAE,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IAG1E,yGAAyG;IACzG,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IACnD;;;OAGG;IACH,YAAY,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IACxD,oCAAoC;IACpC,WAAW,CAAC,cAAc,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D;;;;OAIG;IACH,QAAQ,CACP,cAAc,EAAE,MAAM,EACtB,EAAE,EAAE,MAAM,EACV,OAAO,EAAE;QAAE,KAAK,EAAE,IAAI,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GACrC,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,uFAAuF;IACvF,OAAO,CAAC,cAAc,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvF,0GAA0G;IAC1G,SAAS,CAAC,cAAc,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7D,oFAAoF;IACpF,MAAM,CAAC,cAAc,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC;IAChE;;;;;;;;;;;;;;OAcG;IACH,UAAU,CAAC,cAAc,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,KAAK,EAAE,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC;IAC9F,4GAA4G;IAC5G,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;CACvD"}
@@ -17,6 +17,7 @@ export type { UserProfileAdapter, NewUserProfileData } from './user.js';
17
17
  export type { SchemaAdapter } from './schema.js';
18
18
  export type { OrganizationAdapter } from './organization.js';
19
19
  export type { InstanceAdapter, InstanceSettings } from './instance.js';
20
+ export { BOOTSTRAP_CLAIM_ID } from './instance.js';
20
21
  export type { RolesAdapter } from './role.js';
21
22
  export type { ReferenceAdapter, BackReferenceRow, BackReferenceLookup } from './reference.js';
22
23
  export type { PluginSettingsAdapter, PluginSettingsRow } from './plugin-settings.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/db/interfaces/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/db/interfaces/index.ts"],"names":[],"mappings":"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"}
@@ -1 +1,2 @@
1
1
  export { RevisionConflictError } from './document.js';
2
+ export { BOOTSTRAP_CLAIM_ID } from './instance.js';
@@ -1,7 +1,36 @@
1
1
  import type { InstanceSettings } from '../../types/instance.js';
2
2
  export type { InstanceSettings };
3
+ /**
4
+ * Row id holding the one-time bootstrap claim.
5
+ *
6
+ * Deliberately not `'default'` — `get`/`updateInstanceSettings` are scoped to
7
+ * that row, so this one is invisible to them and can't be cleared by an ordinary
8
+ * settings write.
9
+ */
10
+ export declare const BOOTSTRAP_CLAIM_ID = "aphex:bootstrap-claim";
3
11
  export interface InstanceAdapter {
4
12
  getInstanceSettings(): Promise<InstanceSettings>;
5
13
  updateInstanceSettings(settings: Partial<InstanceSettings>): Promise<InstanceSettings>;
14
+ /**
15
+ * Atomically claim the one-time bootstrap promotion. `true` means this caller
16
+ * won and may grant an instance role; `false` means somebody else already did.
17
+ *
18
+ * Exists because "is the instance empty?" and "insert the first profile" were
19
+ * two statements with a gap between them: two sign-ups arriving together could
20
+ * both observe an empty instance and both be promoted to super admin. Every
21
+ * bootstrap policy gates on `isFirstUser`, so at most one promotion is
22
+ * intended in all of them — this makes that atomic rather than likely.
23
+ *
24
+ * Implemented as an insert of a dedicated row that conflicts with itself, so
25
+ * the database decides the winner in a single statement. That matters more
26
+ * than it sounds: the obvious fix — wrapping the check and the insert in one
27
+ * transaction — holds SQLite's single write lock across the auth provider's
28
+ * own concurrent user and session inserts and fails sign-up with SQLITE_BUSY.
29
+ *
30
+ * Optional: an adapter that doesn't implement it keeps the old non-atomic
31
+ * behaviour (with a warning) rather than refusing to ever promote anyone,
32
+ * which would leave a fresh install with no way to get its first administrator.
33
+ */
34
+ tryClaimBootstrap?(): Promise<boolean>;
6
35
  }
7
36
  //# sourceMappingURL=instance.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../../../src/lib/db/interfaces/instance.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,YAAY,EAAE,gBAAgB,EAAE,CAAC;AAEjC,MAAM,WAAW,eAAe;IAC/B,mBAAmB,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACjD,sBAAsB,CAAC,QAAQ,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;CACvF"}
1
+ {"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../../../src/lib/db/interfaces/instance.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,YAAY,EAAE,gBAAgB,EAAE,CAAC;AAEjC;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,0BAA0B,CAAC;AAE1D,MAAM,WAAW,eAAe;IAC/B,mBAAmB,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACjD,sBAAsB,CAAC,QAAQ,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAEvF;;;;;;;;;;;;;;;;;;;OAmBG;IACH,iBAAiB,CAAC,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CACvC"}
@@ -1 +1,8 @@
1
- export {};
1
+ /**
2
+ * Row id holding the one-time bootstrap claim.
3
+ *
4
+ * Deliberately not `'default'` — `get`/`updateInstanceSettings` are scoped to
5
+ * that row, so this one is invisible to them and can't be cleared by an ordinary
6
+ * settings write.
7
+ */
8
+ export const BOOTSTRAP_CLAIM_ID = 'aphex:bootstrap-claim';
@@ -0,0 +1,20 @@
1
+ import type { EventConsumerPart } from '../plugins/types.js';
2
+ /**
3
+ * Erase a deleted user's avatar — the file, not just the row.
4
+ *
5
+ * Account deletion removes the user record, and with it the only pointer to the avatar
6
+ * asset. Without this the image survives in object storage indefinitely, reachable by
7
+ * anyone holding its URL and invisible to the media browser (avatars are marked
8
+ * `system`), so nothing would ever surface it for cleanup. That is precisely the
9
+ * "personal data with no way left to find or erase it" case a right-to-erasure request
10
+ * has to be able to answer.
11
+ *
12
+ * Idempotent, as every consumer must be: a missing asset is a completed erasure, so a
13
+ * redelivery finds nothing and succeeds. It throws only when the delete itself fails, so
14
+ * a transient storage outage retries with backoff instead of silently giving up — the
15
+ * one outcome that would leave the data behind while reporting success.
16
+ */
17
+ export declare const eraseUserAvatarConsumer: EventConsumerPart;
18
+ /** Every consumer core ships. Seeded into the part resolver ahead of plugin parts. */
19
+ export declare const BUILT_IN_EVENT_CONSUMERS: readonly EventConsumerPart[];
20
+ //# sourceMappingURL=built-in-consumers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"built-in-consumers.d.ts","sourceRoot":"","sources":["../../src/lib/events/built-in-consumers.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAQ1D;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,uBAAuB,EAAE,iBAoCrC,CAAC;AAEF,sFAAsF;AACtF,eAAO,MAAM,wBAAwB,EAAE,SAAS,iBAAiB,EAA8B,CAAC"}