@kernhq/module-inventory 0.2.0 → 0.4.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 (198) hide show
  1. package/README.md +106 -25
  2. package/dist/contract/capabilities.d.ts +4 -4
  3. package/dist/contract/capabilities.d.ts.map +1 -1
  4. package/dist/contract/capabilities.js +51 -9
  5. package/dist/contract/capabilities.js.map +1 -1
  6. package/dist/contract/events.d.ts +43 -0
  7. package/dist/contract/events.d.ts.map +1 -1
  8. package/dist/contract/events.js +40 -0
  9. package/dist/contract/events.js.map +1 -1
  10. package/dist/contract/index.d.ts +1 -0
  11. package/dist/contract/index.d.ts.map +1 -1
  12. package/dist/contract/index.js +1 -0
  13. package/dist/contract/index.js.map +1 -1
  14. package/dist/contract/models.d.ts +345 -0
  15. package/dist/contract/models.d.ts.map +1 -1
  16. package/dist/contract/models.js +245 -0
  17. package/dist/contract/models.js.map +1 -1
  18. package/dist/contract/notifications.d.ts +24 -0
  19. package/dist/contract/notifications.d.ts.map +1 -0
  20. package/dist/contract/notifications.js +72 -0
  21. package/dist/contract/notifications.js.map +1 -0
  22. package/dist/contract/permissions.d.ts +52 -2
  23. package/dist/contract/permissions.d.ts.map +1 -1
  24. package/dist/contract/permissions.js +55 -2
  25. package/dist/contract/permissions.js.map +1 -1
  26. package/dist/contract/router.d.ts +1108 -0
  27. package/dist/contract/router.d.ts.map +1 -1
  28. package/dist/contract/router.js +253 -1
  29. package/dist/contract/router.js.map +1 -1
  30. package/dist/contract/settings.d.ts +2 -0
  31. package/dist/contract/settings.d.ts.map +1 -1
  32. package/dist/contract/settings.js +24 -4
  33. package/dist/contract/settings.js.map +1 -1
  34. package/dist/server/index.d.ts +8 -4
  35. package/dist/server/index.d.ts.map +1 -1
  36. package/dist/server/index.js +289 -5
  37. package/dist/server/index.js.map +1 -1
  38. package/dist/server/jobs.d.ts +48 -0
  39. package/dist/server/jobs.d.ts.map +1 -0
  40. package/dist/server/jobs.js +358 -0
  41. package/dist/server/jobs.js.map +1 -0
  42. package/dist/server/router.d.ts +1392 -0
  43. package/dist/server/router.d.ts.map +1 -1
  44. package/dist/server/router.js +385 -7
  45. package/dist/server/router.js.map +1 -1
  46. package/dist/server/schema.d.ts +134 -1
  47. package/dist/server/schema.d.ts.map +1 -1
  48. package/dist/server/schema.js +169 -4
  49. package/dist/server/schema.js.map +1 -1
  50. package/dist/server/services/assets.d.ts +69 -3
  51. package/dist/server/services/assets.d.ts.map +1 -1
  52. package/dist/server/services/assets.js +199 -31
  53. package/dist/server/services/assets.js.map +1 -1
  54. package/dist/server/services/attachments.d.ts +80 -0
  55. package/dist/server/services/attachments.d.ts.map +1 -0
  56. package/dist/server/services/attachments.js +182 -0
  57. package/dist/server/services/attachments.js.map +1 -0
  58. package/dist/server/services/audience.d.ts +15 -0
  59. package/dist/server/services/audience.d.ts.map +1 -0
  60. package/dist/server/services/audience.js +64 -0
  61. package/dist/server/services/audience.js.map +1 -0
  62. package/dist/server/services/categories.d.ts +157 -0
  63. package/dist/server/services/categories.d.ts.map +1 -0
  64. package/dist/server/services/categories.js +309 -0
  65. package/dist/server/services/categories.js.map +1 -0
  66. package/dist/server/services/cursor.d.ts +68 -0
  67. package/dist/server/services/cursor.d.ts.map +1 -0
  68. package/dist/server/services/cursor.js +39 -0
  69. package/dist/server/services/cursor.js.map +1 -0
  70. package/dist/server/services/custody.d.ts +175 -0
  71. package/dist/server/services/custody.d.ts.map +1 -0
  72. package/dist/server/services/custody.js +367 -0
  73. package/dist/server/services/custody.js.map +1 -0
  74. package/dist/server/services/db-errors.d.ts +7 -0
  75. package/dist/server/services/db-errors.d.ts.map +1 -0
  76. package/dist/server/services/db-errors.js +32 -0
  77. package/dist/server/services/db-errors.js.map +1 -0
  78. package/dist/server/services/index.d.ts +16 -0
  79. package/dist/server/services/index.d.ts.map +1 -1
  80. package/dist/server/services/index.js +25 -1
  81. package/dist/server/services/index.js.map +1 -1
  82. package/dist/server/services/members.d.ts +27 -0
  83. package/dist/server/services/members.d.ts.map +1 -0
  84. package/dist/server/services/members.js +39 -0
  85. package/dist/server/services/members.js.map +1 -0
  86. package/dist/server/services/notify.d.ts +43 -1
  87. package/dist/server/services/notify.d.ts.map +1 -1
  88. package/dist/server/services/notify.js +47 -5
  89. package/dist/server/services/notify.js.map +1 -1
  90. package/dist/server/services/offboarding.d.ts +70 -0
  91. package/dist/server/services/offboarding.d.ts.map +1 -0
  92. package/dist/server/services/offboarding.js +116 -0
  93. package/dist/server/services/offboarding.js.map +1 -0
  94. package/dist/server/services/repairs.d.ts +204 -0
  95. package/dist/server/services/repairs.d.ts.map +1 -0
  96. package/dist/server/services/repairs.js +476 -0
  97. package/dist/server/services/repairs.js.map +1 -0
  98. package/dist/server/services/search.d.ts +85 -0
  99. package/dist/server/services/search.d.ts.map +1 -0
  100. package/dist/server/services/search.js +142 -0
  101. package/dist/server/services/search.js.map +1 -0
  102. package/dist/server/services/stats.d.ts +42 -0
  103. package/dist/server/services/stats.d.ts.map +1 -0
  104. package/dist/server/services/stats.js +80 -0
  105. package/dist/server/services/stats.js.map +1 -0
  106. package/dist/server/services/status.d.ts +102 -0
  107. package/dist/server/services/status.d.ts.map +1 -0
  108. package/dist/server/services/status.js +71 -0
  109. package/dist/server/services/status.js.map +1 -0
  110. package/migrations/0002_custody_and_categories.sql +23 -0
  111. package/migrations/0003_repairs.sql +23 -0
  112. package/migrations/0004_platform_surfaces.sql +51 -0
  113. package/migrations/0005_repair_dates.sql +35 -0
  114. package/migrations/0006_workspace_registry_read.sql +50 -0
  115. package/migrations/0007_history_sequence.sql +83 -0
  116. package/migrations/0008_category_order_unique.sql +71 -0
  117. package/migrations/meta/0002_snapshot.json +1054 -0
  118. package/migrations/meta/0003_snapshot.json +1070 -0
  119. package/migrations/meta/0004_snapshot.json +1130 -0
  120. package/migrations/meta/0005_snapshot.json +1135 -0
  121. package/migrations/meta/_journal.json +49 -0
  122. package/package.json +2 -1
  123. package/src/client/api-instance.ts +27 -2
  124. package/src/client/bidi.test.ts +148 -0
  125. package/src/client/bidi.ts +85 -0
  126. package/src/client/components/AssetDetailPanel.svelte +614 -0
  127. package/src/client/components/AssetFormDialog.svelte +71 -22
  128. package/src/client/components/AssetPhoto.svelte +178 -0
  129. package/src/client/components/AttachmentsSection.svelte +327 -0
  130. package/src/client/components/CustodyDialog.svelte +201 -0
  131. package/src/client/components/RepairDialog.svelte +271 -0
  132. package/src/client/components/RepairsSection.svelte +318 -0
  133. package/src/client/components/Timeline.svelte +347 -0
  134. package/src/client/components/TimelineText.svelte +124 -0
  135. package/src/client/core-api.ts +71 -0
  136. package/src/client/custody.test.ts +31 -0
  137. package/src/client/custody.ts +34 -0
  138. package/src/client/errors.test.ts +395 -0
  139. package/src/client/errors.ts +229 -0
  140. package/src/client/links.test.ts +74 -0
  141. package/src/client/links.ts +44 -0
  142. package/src/client/members.test.ts +132 -0
  143. package/src/client/members.ts +116 -0
  144. package/src/client/messages.test.ts +127 -0
  145. package/src/client/messages.ts +1106 -18
  146. package/src/client/mock.test.ts +465 -1
  147. package/src/client/mock.ts +1105 -79
  148. package/src/client/module.ts +73 -1
  149. package/src/client/pages/AssetsPage.svelte +317 -45
  150. package/src/client/permissions.ts +7 -0
  151. package/src/client/price.test.ts +151 -3
  152. package/src/client/price.ts +155 -11
  153. package/src/client/query.ts +36 -0
  154. package/src/client/reorder.test.ts +100 -0
  155. package/src/client/reorder.ts +79 -0
  156. package/src/client/repairs.test.ts +38 -0
  157. package/src/client/repairs.ts +38 -0
  158. package/src/client/sequence.test.ts +248 -0
  159. package/src/client/sequence.ts +185 -0
  160. package/src/client/settings/CategoriesSettings.svelte +746 -0
  161. package/src/client/settings/GeneralSettings.svelte +0 -0
  162. package/src/client/status.ts +29 -0
  163. package/src/client/timeline.test.ts +175 -0
  164. package/src/client/timeline.ts +206 -0
  165. package/src/client/widgets/OverviewWidget.svelte +127 -26
  166. package/src/client/widgets/RepairsWidget.svelte +124 -0
  167. package/src/contract/capabilities.ts +53 -9
  168. package/src/contract/events.ts +49 -0
  169. package/src/contract/index.ts +1 -0
  170. package/src/contract/models.ts +270 -0
  171. package/src/contract/notifications.ts +73 -0
  172. package/src/contract/permissions.ts +55 -2
  173. package/src/contract/router.ts +284 -1
  174. package/src/contract/settings.ts +24 -4
  175. package/src/module.test.ts +217 -4
  176. package/src/server/index.ts +311 -4
  177. package/src/server/inventory.int.test.ts +4109 -19
  178. package/src/server/jobs.ts +444 -0
  179. package/src/server/migrations.test.ts +253 -2
  180. package/src/server/router.ts +486 -6
  181. package/src/server/schema.ts +171 -3
  182. package/src/server/services/assets.ts +214 -54
  183. package/src/server/services/attachments.ts +215 -0
  184. package/src/server/services/audience.ts +77 -0
  185. package/src/server/services/categories.ts +337 -0
  186. package/src/server/services/cursor.ts +104 -0
  187. package/src/server/services/custody.ts +471 -0
  188. package/src/server/services/db-errors.ts +42 -0
  189. package/src/server/services/index.ts +34 -1
  190. package/src/server/services/members.ts +54 -0
  191. package/src/server/services/notify.ts +52 -7
  192. package/src/server/services/offboarding.ts +150 -0
  193. package/src/server/services/repairs.ts +567 -0
  194. package/src/server/services/search.ts +166 -0
  195. package/src/server/services/stats.ts +88 -0
  196. package/src/server/services/status.test.ts +34 -0
  197. package/src/server/services/status.ts +143 -0
  198. package/src/client/settings/core-api.ts +0 -32
@@ -1,8 +1,10 @@
1
1
  import { KernError, uuidv7 } from '@kernhq/kernel';
2
- import { and, asc, desc, eq, ilike, isNull, or, sql } from 'drizzle-orm';
2
+ import { and, asc, desc, eq, ilike, isNull, lt, or, sql } from 'drizzle-orm';
3
3
  import { alias } from 'drizzle-orm/pg-core';
4
4
  import { InventorySettings } from '../../contract/settings.js';
5
- import { assets, counters } from '../schema.js';
5
+ import { assetHistory, assets, categories, counters } from '../schema.js';
6
+ import { decodeMark, decodeSeqMark, encodeMark, encodeSeqMark } from './cursor.js';
7
+ import { openRepairId } from './status.js';
6
8
  /** The wire shape: drizzle gives Date objects for timestamps, the contract promises ISO strings. */
7
9
  export function toAsset(row) {
8
10
  return {
@@ -29,26 +31,21 @@ export function toAsset(row) {
29
31
  archivedAt: row.archivedAt?.toISOString() ?? null,
30
32
  };
31
33
  }
32
- const encode = (b) => Buffer.from(JSON.stringify(b), 'utf8').toString('base64url');
33
- /** Cheap and total, and it runs before the value can reach a `::uuid` cast. */
34
- const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
35
- function decode(cursor, sort) {
36
- const refuse = () => KernError.badRequest('That page marker is not one this list issued');
37
- let parsed;
38
- try {
39
- parsed = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8'));
40
- }
41
- catch {
42
- throw refuse();
43
- }
44
- if (typeof parsed?.i !== 'string' || !UUID.test(parsed.i))
45
- throw refuse();
46
- // One comparison covers both a sort this list never issues and a sort it issued under a
47
- // *different* request. Either way the bookmark cannot be read against the ordering asked for,
48
- // and pretending otherwise is what made "Load more" loop for ever.
49
- if (parsed.s !== sort)
50
- throw refuse();
51
- return { i: parsed.i, s: sort };
34
+ /**
35
+ * The wire shape of one timeline entry. Drizzle's `jsonb` columns are nullable in the row and never
36
+ * null in the contract — `changes: []` and `data: {}` are what "nothing to say" looks like to a
37
+ * screen, where `null` is one more thing every caller has to remember to handle.
38
+ */
39
+ export function toHistoryEntry(row) {
40
+ return {
41
+ id: row.id,
42
+ assetId: row.assetId,
43
+ actorId: row.actorId,
44
+ action: row.action,
45
+ changes: row.changes ?? [],
46
+ data: row.data ?? {},
47
+ occurredAt: row.occurredAt.toISOString(),
48
+ };
52
49
  }
53
50
  /**
54
51
  * `%`, `_` and `\` are pattern syntax to `ilike`, not text.
@@ -58,6 +55,14 @@ function decode(cursor, sort) {
58
55
  * stripped: somebody typing a per-cent sign means the character.
59
56
  */
60
57
  const contains = (q) => `%${q.replace(/[\\%_]/g, '\\$&')}%`;
58
+ /**
59
+ * The one ordering a timeline has, named rather than left as a literal.
60
+ *
61
+ * The cursor codec binds a bookmark to the sort it was issued under and refuses it under any other,
62
+ * so a timeline needs a name for its sort even while there is only one — and the day a second one
63
+ * exists, every cursor already in a browser tab is refused rather than misread.
64
+ */
65
+ const HISTORY_SORT = 'recent';
61
66
  /** Which column an ordering sorts on, for the list itself and for the bookmarked row alike. */
62
67
  const sortKey = (t, sort) => sort === 'name' ? t.name : sort === 'code' ? t.code : t.id;
63
68
  export class AssetService {
@@ -115,16 +120,29 @@ export class AssetService {
115
120
  filters.push(eq(assets.custodianUserId, input.custodianUserId));
116
121
  if (!input.archived)
117
122
  filters.push(isNull(assets.archivedAt));
118
- // `recent` sorts by id rather than by `created_at`: ids are uuidv7, so they carry the clock,
119
- // and one indexed unique column is a cheaper and unambiguous page boundary than a timestamp
120
- // two rows can share. Ordered to the millisecond and no finer the kernel's `uuidv7()` fills
121
- // its last ten bytes from `randomUUID()` with no counterso two assets created inside one
122
- // millisecond come back in a stable but arbitrary order relative to each other. That is fine
123
- // for a list and fatal for a test that expects creation order; see `inventory.int.test.ts`.
123
+ /**
124
+ * `recent` sorts by id rather than by `created_at`: ids are uuidv7, so they carry the clock, and
125
+ * one indexed unique column is a cheaper and unambiguous page boundary than a timestamp two rows
126
+ * can share. Ordered to the millisecond and no finerthe kernel's `uuidv7()` fills its last
127
+ * ten bytes from `randomUUID()` with no counter so two assets created inside one millisecond
128
+ * come back in a stable but arbitrary order relative to each other.
129
+ *
130
+ * **The timeline deliberately stopped accepting that and this list has not**, so the difference
131
+ * is worth stating rather than leaving as an inconsistency. `asset_history` is a record somebody
132
+ * argues from, and a millisecond there holds a create and its own first entry, so an arbitrary
133
+ * order is a lie about causation — it has a sequence of its own now (`schema.ts`, `seq`). A list
134
+ * is an ordering of things a person is browsing: nothing hangs on which of two assets imported
135
+ * in the same millisecond is shown first, and paging stays gapless either way because the id is
136
+ * unique. If that ever stops being true — a bulk import whose order somebody relies on — the
137
+ * fix is the same sequence, not a timestamp: `created_at` defaults to the *transaction's*
138
+ * `now()`, so one import shares it exactly.
139
+ *
140
+ * It is fatal for a test that expects creation order; see `inventory.int.test.ts`.
141
+ */
124
142
  const column = sortKey(assets, input.sort);
125
143
  const descending = input.sort === 'recent';
126
144
  if (input.cursor) {
127
- const mark = decode(input.cursor, input.sort);
145
+ const mark = decodeMark(input.cursor, input.sort);
128
146
  /**
129
147
  * The sort key comes back out of the bookmarked row rather than out of the cursor, which is
130
148
  * what keeps the cursor small and stops it disagreeing with the row it names. Scoped by
@@ -154,9 +172,81 @@ export class AssetService {
154
172
  .limit(input.limit + 1);
155
173
  const items = rows.slice(0, input.limit);
156
174
  const last = items.at(-1);
157
- const nextCursor = rows.length > input.limit && last ? encode({ i: last.id, s: input.sort }) : null;
175
+ const nextCursor = rows.length > input.limit && last ? encodeMark({ i: last.id, s: input.sort }) : null;
158
176
  return { items: items.map(toAsset), nextCursor };
159
177
  }
178
+ /**
179
+ * The asset's own timeline, newest first, paged on the sequence the entries carry.
180
+ *
181
+ * **The ordering is `seq`, not `occurred_at` and not the id**, and the middle one of those three
182
+ * is the interesting correction.
183
+ *
184
+ * `occurred_at` was never a candidate: it defaults to `now()`, which is the *transaction*
185
+ * timestamp, so `create` writes the asset and its `created` entry with the same value and a page
186
+ * boundary between two entries that share a sort key repeats one and drops the other.
187
+ *
188
+ * The id looked like the answer and was only half of one. A uuidv7 is unique, so paging on it is
189
+ * gapless — but the kernel's `uuidv7()` carries the clock only to the millisecond and fills the
190
+ * rest from `randomUUID()` with no counter, so two entries written inside one millisecond sort by
191
+ * ten random bytes. Stable, so nothing was ever dropped or repeated; and arbitrary, so a timeline
192
+ * could show "Bruno removed the file" above "Bruno added the file". `seq` is a sequence: strictly
193
+ * increasing whatever the clock does, at whatever rate. `schema.ts` argues it at the column.
194
+ *
195
+ * The comparison is a single column because the sort key *is* the bookmark, so there is no
196
+ * bookmarked-row subquery here. The bookmark is still checked as a bounded positive integer and
197
+ * still bound to the sort it was issued under, because that is the codec's job and not this
198
+ * query's.
199
+ */
200
+ async history(tx, workspaceId, assetId, input) {
201
+ // 404 before paging: a timeline for an asset in another workspace must not answer with an
202
+ // empty list, which reads as "nothing has happened to it" rather than "it is not yours".
203
+ await this.get(tx, workspaceId, assetId);
204
+ const filters = [eq(assetHistory.workspaceId, workspaceId), eq(assetHistory.assetId, assetId)];
205
+ if (input.cursor)
206
+ filters.push(lt(assetHistory.seq, decodeSeqMark(input.cursor, HISTORY_SORT).n));
207
+ const rows = await tx
208
+ .select()
209
+ .from(assetHistory)
210
+ .where(and(...filters))
211
+ .orderBy(desc(assetHistory.seq))
212
+ .limit(input.limit + 1);
213
+ const items = rows.slice(0, input.limit);
214
+ const last = items.at(-1);
215
+ const nextCursor = rows.length > input.limit && last ? encodeSeqMark({ n: last.seq, s: HISTORY_SORT }) : null;
216
+ return { items: items.map(toHistoryEntry), nextCursor };
217
+ }
218
+ /**
219
+ * A category id in a request is a claim about a row in this workspace, and it is checked before
220
+ * it is stored.
221
+ *
222
+ * The same defect `photoFileId` had, one field over: `assets.create` and `assets.update` took
223
+ * `categoryId` on trust, so any uuid at all went into the column. Two things follow from that,
224
+ * and neither is theoretical. An id belonging to **another workspace** files an asset under a
225
+ * category this one cannot see, name or unfile — the picker resolves names from its own
226
+ * `categories.list`, so the field renders blank and the filter it sits behind matches nothing
227
+ * anybody can select. An id belonging to **nobody** does the same thing with no other workspace
228
+ * involved. Either way the register holds a reference to a row that is not there, which is
229
+ * exactly the state `categories.archive` exists to prevent from the other end.
230
+ *
231
+ * **Checked inside the caller's transaction, unlike `checkPhoto`.** A file lives in core and has
232
+ * to be asked for over the broker, so that check happens before a connection is taken — the rule
233
+ * `codeFormat` documents. A category is this module's own table one join away, so the honest
234
+ * place for it is the transaction that writes the row: it costs one indexed read, and it cannot
235
+ * be raced by somebody deleting the category, because nothing deletes one.
236
+ *
237
+ * An **archived** category is deliberately allowed. Archiving takes it out of every picker and
238
+ * leaves every asset already filed under it still naming it; refusing it here would mean an edit
239
+ * to an asset's location failed because of a category somebody tidied away last year, and it
240
+ * would make re-filing a batch of assets under an old category impossible for no gain.
241
+ */
242
+ async requireCategory(tx, workspaceId, categoryId) {
243
+ const [row] = await tx
244
+ .select({ id: categories.id })
245
+ .from(categories)
246
+ .where(and(eq(categories.workspaceId, workspaceId), eq(categories.id, categoryId)));
247
+ if (!row)
248
+ throw KernError.badRequest('That category is not one this workspace has.');
249
+ }
160
250
  async get(tx, workspaceId, assetId) {
161
251
  const [row] = await tx
162
252
  .select()
@@ -167,6 +257,8 @@ export class AssetService {
167
257
  return row;
168
258
  }
169
259
  async create(tx, workspaceId, actorId, input, format) {
260
+ if (input.categoryId)
261
+ await this.requireCategory(tx, workspaceId, input.categoryId);
170
262
  const [row] = await tx
171
263
  .insert(assets)
172
264
  .values({
@@ -205,6 +297,11 @@ export class AssetService {
205
297
  'photoFileId',
206
298
  ];
207
299
  async update(tx, workspaceId, actorId, assetId, input) {
300
+ // Before the row is locked, because a refusal here has nothing to do with this asset and
301
+ // holding a row lock across a check that can throw is a lock held for no reason. `undefined`
302
+ // means the patch never mentioned a category and `null` means "unfile it"; neither is an id.
303
+ if (input.categoryId)
304
+ await this.requireCategory(tx, workspaceId, input.categoryId);
208
305
  const [prev] = await tx
209
306
  .select()
210
307
  .from(assets)
@@ -219,6 +316,16 @@ export class AssetService {
219
316
  const value = input[field];
220
317
  patch[field] = value !== undefined ? (value ?? null) : prev[field];
221
318
  }
319
+ /**
320
+ * A new warranty date earns a new notice.
321
+ *
322
+ * `warranty_notified_at` is the marker that stops the sweep saying the same thing every morning
323
+ * for a month; left alone here, extending a warranty by two years would mean nobody is ever told
324
+ * about the new date, because the row is already marked. Cleared only when the date itself
325
+ * moved — an edit to the name must not re-arm a notice somebody has already had.
326
+ */
327
+ if (patch.warrantyUntil !== prev.warrantyUntil)
328
+ patch.warrantyNotifiedAt = null;
222
329
  // Filtered by workspace as well as by id. The `select … for update` three statements up already
223
330
  // carries the predicate, so this is not reachable today — but `core` connects as a superuser
224
331
  // with RLS bypassed, which makes the predicate in the statement the only barrier there is. A
@@ -240,7 +347,68 @@ export class AssetService {
240
347
  await this.notify.history(tx, activity);
241
348
  return { row: row, activity };
242
349
  }
243
- async archive(tx, workspaceId, actorId, assetId, archived) {
350
+ /**
351
+ * @param repairsOn whether the workspace records repairs, read before the transaction opened.
352
+ * The "it is away for repair" refusal below tells somebody to go and use `repairs.complete`, and
353
+ * that procedure answers 404 in a workspace with the capability off — so the refusal would be an
354
+ * instruction to open a door that is not there, and the asset could never leave the register.
355
+ */
356
+ async archive(tx, workspaceId, actorId, assetId, archived, repairsOn) {
357
+ /**
358
+ * **Locked before either refusal is decided, because both of them are about a race.**
359
+ *
360
+ * The two checks below used to read the facts with a plain select, so the state they call
361
+ * impossible was reachable by simply doing the two things at once: an archive and a handover
362
+ * each read "nobody is holding it", both were allowed, and the register was left with an
363
+ * archived asset and an open custody period — the timeline showing a handover that never ended,
364
+ * and "what is Ada still holding?" quietly not counting it, because that question excludes
365
+ * archived rows. A check whose answer another transaction may already have changed is not a
366
+ * check; it is a comment.
367
+ *
368
+ * The lock is the same one `CustodyService.stamp` and `RepairService.restamp` take before they
369
+ * write `status`, which is what makes the ordering complete rather than one-sided: whichever
370
+ * transaction gets it second re-reads what the first committed, so an archive that arrives
371
+ * first makes the handover refuse, and a handover that arrives first makes the archive refuse.
372
+ * `lockAsset` in `status.ts` argues it in full.
373
+ */
374
+ const [locked] = await tx
375
+ .select()
376
+ .from(assets)
377
+ .where(and(eq(assets.workspaceId, workspaceId), eq(assets.id, assetId)))
378
+ .for('update');
379
+ if (!locked)
380
+ throw KernError.notFound('Asset');
381
+ if (archived) {
382
+ /**
383
+ * An item somebody is holding cannot leave the register.
384
+ *
385
+ * Somebody is answerable for the thing; taking it out of the register does not make them not
386
+ * answerable, it only stops anybody being able to find out. Two steps instead of one, and
387
+ * both of them mean something.
388
+ */
389
+ if (locked.custodianUserId)
390
+ throw KernError.conflict('Somebody is still holding this item. Take it back before archiving it.', 'inventory.asset.still_held');
391
+ /**
392
+ * An item that is at a repairer cannot leave the register either, and for the same reason as
393
+ * custody: money is committed and the thing is out of the building, so taking it out of the
394
+ * register does not settle that — it only stops anybody being able to find out.
395
+ *
396
+ * It also keeps two counts that must agree from drifting. `stats.summary` reports
397
+ * `byStatus.under_repair` from the cached status column, over live rows only, and
398
+ * `outForRepair` from the repair rows themselves; an archived asset with an open repair
399
+ * would appear in one and not the other, and neither number would be wrong.
400
+ *
401
+ * **Only while the workspace records repairs.** With the `repairs` capability off, this
402
+ * refusal names a procedure that answers 404 — so it was not a two-step instruction, it was a
403
+ * dead end: the repair could not be completed, so the asset could not be archived, so the
404
+ * item was stuck in the register for as long as the capability stayed off. A refusal that
405
+ * points at a door has to be withdrawn when the door is taken away. `stats.away` counts only
406
+ * live assets for the same reason, so the two counts still cannot disagree.
407
+ */
408
+ const open = repairsOn ? await openRepairId(tx, workspaceId, assetId) : undefined;
409
+ if (open)
410
+ throw KernError.conflict('This item is away for repair. Log the repair as returned before archiving it.', 'inventory.asset.under_repair');
411
+ }
244
412
  const [row] = await tx
245
413
  .update(assets)
246
414
  .set({ archivedAt: archived ? new Date() : null, updatedAt: new Date() })
@@ -1 +1 @@
1
- {"version":3,"file":"assets.js","sourceRoot":"","sources":["../../../src/server/services/assets.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"assets.js","sourceRoot":"","sources":["../../../src/server/services/assets.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,80 @@
1
+ import { type Kernel, type Tx } from '@kernhq/kernel';
2
+ import type { Attachment as AttachmentModel } from '../../contract/models.js';
3
+ import { attachments } from '../schema.js';
4
+ import type { HistoryInput, NotifyService } from './notify.js';
5
+ type Row = typeof attachments.$inferSelect;
6
+ /** What this module copies out of core's file record, and all it copies. */
7
+ export interface FileFacts {
8
+ id: string;
9
+ name: string;
10
+ mimeType: string | null;
11
+ size: number | null;
12
+ }
13
+ /** The wire shape: drizzle gives Date objects for timestamps, the contract promises ISO strings. */
14
+ export declare function toAttachment(row: Row): AttachmentModel;
15
+ /**
16
+ * Files kept against an asset — receipts, warranties, manuals, a repair invoice.
17
+ *
18
+ * **A module does not upload, and this class is what that sentence means in code.** The browser
19
+ * sends the bytes to core's file service and is handed an id; this records that the asset has that
20
+ * file, with the name and size copied at attach time so a list can be drawn without asking core once
21
+ * per row. Nothing here streams, signs, stores or deletes a byte — `remove` detaches, because the
22
+ * same file may be attached elsewhere and a module has no standing to destroy another module's row.
23
+ *
24
+ * The copied name is a snapshot on purpose: renaming a file in core does not rename it here, which
25
+ * is the same trade every module makes when it copies a label rather than joining across a schema
26
+ * boundary. What it buys is a files list that is one query rather than one query plus a call per row.
27
+ */
28
+ export declare class AttachmentService {
29
+ private readonly kernel;
30
+ private readonly notify;
31
+ constructor(kernel: Kernel, notify: NotifyService);
32
+ /**
33
+ * What core knows about these files — **called before the transaction opens, never inside one.**
34
+ *
35
+ * `kernel.call` is a request to another service over the broker, and awaiting one while holding a
36
+ * pooled connection is the failure `AssetService.codeFormat` documents: the pool starves under
37
+ * concurrent writes, and an attach fails outright whenever core is briefly away. So the router
38
+ * gathers the facts first and hands them to `add`, which does nothing but write.
39
+ *
40
+ * Two checks, and the first one is the one that matters: **a file has to belong to this
41
+ * workspace.** The id arrives in the request, so without this a member of one workspace could
42
+ * attach another workspace's file and read its name and size back out of the list — the module
43
+ * boundary does not help here, because core answers this module as a service.
44
+ */
45
+ describe(workspaceId: string, fileIds: readonly string[]): Promise<FileFacts[]>;
46
+ /**
47
+ * Every file on one asset, its repairs' included, oldest first.
48
+ *
49
+ * Not paged and not filtered by repair: one asset's files are bounded by how much paperwork one
50
+ * item collects, and the panel groups them in the browser. That is the one case where filtering
51
+ * client-side is right — the list is entirely loaded — and it is the opposite of what a paged
52
+ * asset list may do.
53
+ */
54
+ list(tx: Tx, workspaceId: string, assetId: string): Promise<AttachmentModel[]>;
55
+ /**
56
+ * Record that this asset has these files.
57
+ *
58
+ * `onConflictDoNothing` on `(asset_id, file_id)`: attaching the same file to the same asset twice
59
+ * is not an error worth refusing — somebody pressed the button twice, or dropped the same receipt
60
+ * in again — and the second attempt simply adds nothing. The rows that *were* inserted come back,
61
+ * so the caller announces exactly what changed.
62
+ *
63
+ * An archived asset is deliberately allowed: finding the receipt for something you retired last
64
+ * month is a reason to file it, not a reason to be refused.
65
+ */
66
+ add(tx: Tx, workspaceId: string, actorId: string | null, assetId: string, repairId: string | null, files: readonly FileFacts[]): Promise<{
67
+ rows: Row[];
68
+ activities: HistoryInput[];
69
+ }>;
70
+ /** Detach one file. Core's copy of it is untouched — see the class docblock. */
71
+ remove(tx: Tx, workspaceId: string, actorId: string | null, attachmentId: string): Promise<{
72
+ row: Row;
73
+ activity: HistoryInput;
74
+ }>;
75
+ private requireAsset;
76
+ /** A repair the file is filed under has to be one of *this* asset's, not merely one that exists. */
77
+ private requireRepair;
78
+ }
79
+ export {};
80
+ //# sourceMappingURL=attachments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attachments.d.ts","sourceRoot":"","sources":["../../../src/server/services/attachments.ts"],"names":[],"mappings":"AACA,OAAO,EAAa,KAAK,MAAM,EAAE,KAAK,EAAE,EAAU,MAAM,gBAAgB,CAAA;AAExE,OAAO,KAAK,EAAE,UAAU,IAAI,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAC7E,OAAO,EAAU,WAAW,EAAW,MAAM,cAAc,CAAA;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAE9D,KAAK,GAAG,GAAG,OAAO,WAAW,CAAC,YAAY,CAAA;AAE1C,4EAA4E;AAC5E,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CACpB;AAED,oGAAoG;AACpG,wBAAgB,YAAY,CAAC,GAAG,EAAE,GAAG,GAAG,eAAe,CAatD;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,iBAAiB;IAE1B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADN,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,aAAa;IAGxC;;;;;;;;;;;;OAYG;IACG,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAmBrF;;;;;;;OAOG;IACG,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IASpF;;;;;;;;;;OAUG;IACG,GAAG,CACP,EAAE,EAAE,EAAE,EACN,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,GAAG,IAAI,EACtB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,KAAK,EAAE,SAAS,SAAS,EAAE,GAC1B,OAAO,CAAC;QAAE,IAAI,EAAE,GAAG,EAAE,CAAC;QAAC,UAAU,EAAE,YAAY,EAAE,CAAA;KAAE,CAAC;IA6CvD,gFAAgF;IAC1E,MAAM,CACV,EAAE,EAAE,EAAE,EACN,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,GAAG,IAAI,EACtB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC;QAAE,GAAG,EAAE,GAAG,CAAC;QAAC,QAAQ,EAAE,YAAY,CAAA;KAAE,CAAC;YA0BlC,YAAY;IAQ1B,oGAAoG;YACtF,aAAa;CAS5B"}
@@ -0,0 +1,182 @@
1
+ import { KernError, uuidv7 } from '@kernhq/kernel';
2
+ import { and, asc, eq } from 'drizzle-orm';
3
+ import { assets, attachments, repairs } from '../schema.js';
4
+ /** The wire shape: drizzle gives Date objects for timestamps, the contract promises ISO strings. */
5
+ export function toAttachment(row) {
6
+ return {
7
+ id: row.id,
8
+ workspaceId: row.workspaceId,
9
+ assetId: row.assetId,
10
+ repairId: row.repairId,
11
+ fileId: row.fileId,
12
+ name: row.name,
13
+ mimeType: row.mimeType,
14
+ size: row.size,
15
+ uploadedBy: row.uploadedBy,
16
+ createdAt: row.createdAt.toISOString(),
17
+ };
18
+ }
19
+ /**
20
+ * Files kept against an asset — receipts, warranties, manuals, a repair invoice.
21
+ *
22
+ * **A module does not upload, and this class is what that sentence means in code.** The browser
23
+ * sends the bytes to core's file service and is handed an id; this records that the asset has that
24
+ * file, with the name and size copied at attach time so a list can be drawn without asking core once
25
+ * per row. Nothing here streams, signs, stores or deletes a byte — `remove` detaches, because the
26
+ * same file may be attached elsewhere and a module has no standing to destroy another module's row.
27
+ *
28
+ * The copied name is a snapshot on purpose: renaming a file in core does not rename it here, which
29
+ * is the same trade every module makes when it copies a label rather than joining across a schema
30
+ * boundary. What it buys is a files list that is one query rather than one query plus a call per row.
31
+ */
32
+ export class AttachmentService {
33
+ kernel;
34
+ notify;
35
+ constructor(kernel, notify) {
36
+ this.kernel = kernel;
37
+ this.notify = notify;
38
+ }
39
+ /**
40
+ * What core knows about these files — **called before the transaction opens, never inside one.**
41
+ *
42
+ * `kernel.call` is a request to another service over the broker, and awaiting one while holding a
43
+ * pooled connection is the failure `AssetService.codeFormat` documents: the pool starves under
44
+ * concurrent writes, and an attach fails outright whenever core is briefly away. So the router
45
+ * gathers the facts first and hands them to `add`, which does nothing but write.
46
+ *
47
+ * Two checks, and the first one is the one that matters: **a file has to belong to this
48
+ * workspace.** The id arrives in the request, so without this a member of one workspace could
49
+ * attach another workspace's file and read its name and size back out of the list — the module
50
+ * boundary does not help here, because core answers this module as a service.
51
+ */
52
+ async describe(workspaceId, fileIds) {
53
+ const unique = [...new Set(fileIds)];
54
+ const found = await Promise.all(unique.map(async (id) => {
55
+ const file = await this.kernel.call('core.files.get', { id });
56
+ if (!file || file.workspaceId !== workspaceId)
57
+ throw KernError.badRequest('That file is not one this workspace can attach.');
58
+ if (file.status !== 'ready')
59
+ throw KernError.badRequest('That file has not finished uploading yet.');
60
+ return {
61
+ id: file.id,
62
+ name: file.name,
63
+ mimeType: file.mimeType || null,
64
+ size: typeof file.size === 'number' ? file.size : null,
65
+ };
66
+ }));
67
+ return found;
68
+ }
69
+ /**
70
+ * Every file on one asset, its repairs' included, oldest first.
71
+ *
72
+ * Not paged and not filtered by repair: one asset's files are bounded by how much paperwork one
73
+ * item collects, and the panel groups them in the browser. That is the one case where filtering
74
+ * client-side is right — the list is entirely loaded — and it is the opposite of what a paged
75
+ * asset list may do.
76
+ */
77
+ async list(tx, workspaceId, assetId) {
78
+ const rows = await tx
79
+ .select()
80
+ .from(attachments)
81
+ .where(and(eq(attachments.workspaceId, workspaceId), eq(attachments.assetId, assetId)))
82
+ .orderBy(asc(attachments.createdAt), asc(attachments.id));
83
+ return rows.map(toAttachment);
84
+ }
85
+ /**
86
+ * Record that this asset has these files.
87
+ *
88
+ * `onConflictDoNothing` on `(asset_id, file_id)`: attaching the same file to the same asset twice
89
+ * is not an error worth refusing — somebody pressed the button twice, or dropped the same receipt
90
+ * in again — and the second attempt simply adds nothing. The rows that *were* inserted come back,
91
+ * so the caller announces exactly what changed.
92
+ *
93
+ * An archived asset is deliberately allowed: finding the receipt for something you retired last
94
+ * month is a reason to file it, not a reason to be refused.
95
+ */
96
+ async add(tx, workspaceId, actorId, assetId, repairId, files) {
97
+ await this.requireAsset(tx, workspaceId, assetId);
98
+ if (repairId)
99
+ await this.requireRepair(tx, workspaceId, assetId, repairId);
100
+ if (!files.length)
101
+ return { rows: [], activities: [] };
102
+ const inserted = await tx
103
+ .insert(attachments)
104
+ .values(files.map((file) => ({
105
+ id: uuidv7(),
106
+ workspaceId,
107
+ assetId,
108
+ repairId,
109
+ fileId: file.id,
110
+ name: file.name,
111
+ mimeType: file.mimeType,
112
+ size: file.size,
113
+ uploadedBy: actorId,
114
+ })))
115
+ .onConflictDoNothing({ target: [attachments.assetId, attachments.fileId] })
116
+ .returning();
117
+ /**
118
+ * One timeline entry per file, rather than one saying "3 files".
119
+ *
120
+ * A timeline is read to find out what happened to a thing, and "attached the purchase receipt"
121
+ * answers that where "attached 3 files" sends the reader looking. Attaching several at once is
122
+ * rare enough that the extra rows cost nothing.
123
+ */
124
+ const activities = inserted.map((row) => ({
125
+ workspaceId,
126
+ assetId,
127
+ actorId,
128
+ action: 'attachment_added',
129
+ data: {
130
+ attachmentId: row.id,
131
+ name: row.name,
132
+ ...(row.repairId ? { repairId: row.repairId } : {}),
133
+ },
134
+ }));
135
+ for (const activity of activities)
136
+ await this.notify.history(tx, activity);
137
+ return { rows: inserted, activities };
138
+ }
139
+ /** Detach one file. Core's copy of it is untouched — see the class docblock. */
140
+ async remove(tx, workspaceId, actorId, attachmentId) {
141
+ const [row] = await tx
142
+ .select()
143
+ .from(attachments)
144
+ .where(and(eq(attachments.workspaceId, workspaceId), eq(attachments.id, attachmentId)));
145
+ if (!row)
146
+ throw KernError.notFound('Attachment');
147
+ // Filtered by workspace as well as by id, like every other write in this module: `core`
148
+ // connects as a superuser with RLS bypassed, so the predicate in the statement is the only
149
+ // barrier there is, and a barrier that holds only because of what an earlier statement happened
150
+ // to do is not one.
151
+ await tx
152
+ .delete(attachments)
153
+ .where(and(eq(attachments.workspaceId, workspaceId), eq(attachments.id, attachmentId)));
154
+ const activity = {
155
+ workspaceId,
156
+ assetId: row.assetId,
157
+ actorId,
158
+ action: 'attachment_removed',
159
+ data: { attachmentId: row.id, name: row.name },
160
+ };
161
+ await this.notify.history(tx, activity);
162
+ return { row, activity };
163
+ }
164
+ async requireAsset(tx, workspaceId, assetId) {
165
+ const [row] = await tx
166
+ .select({ id: assets.id })
167
+ .from(assets)
168
+ .where(and(eq(assets.workspaceId, workspaceId), eq(assets.id, assetId)));
169
+ if (!row)
170
+ throw KernError.notFound('Asset');
171
+ }
172
+ /** A repair the file is filed under has to be one of *this* asset's, not merely one that exists. */
173
+ async requireRepair(tx, workspaceId, assetId, repairId) {
174
+ const [row] = await tx
175
+ .select({ id: repairs.id })
176
+ .from(repairs)
177
+ .where(and(eq(repairs.workspaceId, workspaceId), eq(repairs.id, repairId), eq(repairs.assetId, assetId)));
178
+ if (!row)
179
+ throw KernError.notFound('Repair');
180
+ }
181
+ }
182
+ //# sourceMappingURL=attachments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attachments.js","sourceRoot":"","sources":["../../../src/server/services/attachments.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { Kernel } from '@kernhq/kernel';
2
+ /**
3
+ * The members of a workspace who genuinely hold a permission.
4
+ *
5
+ * Genuinely, not "hold the role it defaults to": `kernel.authz.can` reads custom roles and scope
6
+ * bindings as well as the built-in defaults, so a workspace that took `inventory.repair.manage` away
7
+ * from `member` and gave it to one custom role is answered correctly. Guessing from the role would
8
+ * have been one local call instead of a handful, and would have quietly told the wrong people.
9
+ *
10
+ * Every failure here is swallowed: this decides an audience for a best-effort notification, and a
11
+ * member whose principal core cannot produce right now is one fewer recipient, not a reason for a
12
+ * nightly sweep to stop half-way through a workspace.
13
+ */
14
+ export declare function membersWithPermission(kernel: Kernel, workspaceId: string, permission: string): Promise<string[]>;
15
+ //# sourceMappingURL=audience.d.ts.map