@jini-ai/cms 0.3.11 → 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 (258) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +47 -19
  3. package/dist/content-types/agent-tools.d.ts +3 -56
  4. package/dist/content-types/agent-tools.js +22 -6
  5. package/dist/content-types/cleanup.d.ts +18 -13
  6. package/dist/content-types/cleanup.js +16 -11
  7. package/dist/content-types/errors.d.ts +57 -24
  8. package/dist/content-types/errors.js +56 -39
  9. package/dist/content-types/field-defs.d.ts +8 -4
  10. package/dist/content-types/field-defs.js +19 -14
  11. package/dist/content-types/index-provisioning.d.ts +61 -47
  12. package/dist/content-types/index-provisioning.js +90 -66
  13. package/dist/content-types/index.d.ts +4 -3
  14. package/dist/content-types/lifecycle-dispatch.d.ts +11 -5
  15. package/dist/content-types/lifecycle-dispatch.js +5 -4
  16. package/dist/content-types/lifecycle.d.ts +23 -18
  17. package/dist/content-types/lifecycle.js +71 -64
  18. package/dist/content-types/repo.memory.d.ts +5 -4
  19. package/dist/content-types/repo.memory.js +3 -2
  20. package/dist/content-types/tool-registrations.d.ts +6 -8
  21. package/dist/content-types/tool-registrations.js +24 -28
  22. package/dist/content-types/types.d.ts +17 -13
  23. package/dist/content-types/types.js +11 -5
  24. package/dist/content-types/write-service.d.ts +20 -22
  25. package/dist/content-types/write-service.js +78 -75
  26. package/dist/core/authorization.d.ts +15 -0
  27. package/dist/core/authorization.js +2 -0
  28. package/dist/core/commands/change-set.d.ts +15 -7
  29. package/dist/core/commands/command.d.ts +45 -26
  30. package/dist/core/commands/command.js +73 -63
  31. package/dist/core/entity-liveness.d.ts +11 -7
  32. package/dist/core/entity-liveness.js +5 -4
  33. package/dist/core/index.d.ts +12 -5
  34. package/dist/core/index.js +10 -4
  35. package/dist/core/ports.d.ts +25 -24
  36. package/dist/core/ports.js +1 -0
  37. package/dist/core/tools/args.d.ts +49 -0
  38. package/dist/core/tools/args.js +46 -0
  39. package/dist/core/tools/index.d.ts +3 -0
  40. package/dist/core/tools/index.js +3 -0
  41. package/dist/entries/agent-tools.d.ts +1 -73
  42. package/dist/entries/agent-tools.js +19 -13
  43. package/dist/entries/errors.d.ts +28 -14
  44. package/dist/entries/errors.js +17 -11
  45. package/dist/entries/field-validation.d.ts +5 -3
  46. package/dist/entries/field-validation.js +7 -5
  47. package/dist/entries/index.d.ts +2 -2
  48. package/dist/entries/lifecycle-dispatch.d.ts +5 -2
  49. package/dist/entries/lifecycle-dispatch.js +2 -1
  50. package/dist/entries/list.d.ts +4 -2
  51. package/dist/entries/list.js +4 -3
  52. package/dist/entries/repo.memory.d.ts +12 -10
  53. package/dist/entries/repo.memory.js +11 -10
  54. package/dist/entries/tool-registrations.d.ts +6 -8
  55. package/dist/entries/tool-registrations.js +42 -41
  56. package/dist/entries/types.d.ts +0 -7
  57. package/dist/entries/write-service.d.ts +28 -29
  58. package/dist/entries/write-service.js +114 -105
  59. package/dist/http/settings/cms-adapter.d.ts +20 -0
  60. package/dist/http/settings/cms-adapter.js +124 -0
  61. package/dist/http/settings/contracts.d.ts +135 -0
  62. package/dist/http/settings/contracts.js +2 -0
  63. package/dist/http/settings/http.d.ts +10 -0
  64. package/dist/http/settings/http.js +211 -0
  65. package/dist/http/settings/index.d.ts +5 -0
  66. package/dist/http/settings/index.js +4 -0
  67. package/dist/http/settings/shared.d.ts +68 -0
  68. package/dist/http/settings/shared.js +54 -0
  69. package/dist/http/settings/stream.d.ts +24 -0
  70. package/dist/http/settings/stream.js +147 -0
  71. package/dist/media/agent-tools.d.ts +1 -66
  72. package/dist/media/agent-tools.js +5 -1
  73. package/dist/media/blob-gc-lock.d.ts +6 -2
  74. package/dist/media/blob-gc-lock.js +4 -2
  75. package/dist/media/blob-gc.d.ts +64 -51
  76. package/dist/media/blob-gc.js +62 -52
  77. package/dist/media/blob-key.d.ts +1 -1
  78. package/dist/media/content-type-sniffer.d.ts +3 -1
  79. package/dist/media/content-type-sniffer.js +1 -1
  80. package/dist/media/html-attributes.d.ts +9 -5
  81. package/dist/media/html-attributes.js +8 -6
  82. package/dist/media/image-transformer.js +1 -1
  83. package/dist/media/image-transformer.sharp.d.ts +6 -0
  84. package/dist/media/image-transformer.sharp.js +12 -6
  85. package/dist/media/import/fetch-image.d.ts +24 -0
  86. package/dist/media/import/fetch-image.js +137 -0
  87. package/dist/media/import/index.d.ts +3 -0
  88. package/dist/media/import/index.js +3 -0
  89. package/dist/media/import/ports.d.ts +38 -0
  90. package/dist/media/index.d.ts +7 -1
  91. package/dist/media/index.js +1 -0
  92. package/dist/media/media-service.d.ts +14 -10
  93. package/dist/media/media-service.js +57 -52
  94. package/dist/media/ports.d.ts +3 -2
  95. package/dist/media/rendition-service.d.ts +3 -3
  96. package/dist/media/rendition-service.js +38 -37
  97. package/dist/media/repo.memory.d.ts +18 -7
  98. package/dist/media/repo.memory.js +12 -6
  99. package/dist/media/tool-registrations.d.ts +16 -15
  100. package/dist/media/tool-registrations.js +84 -72
  101. package/dist/media/transform-lock.d.ts +22 -12
  102. package/dist/media/transform-lock.js +14 -13
  103. package/dist/media/transform-registry.d.ts +3 -3
  104. package/dist/media/transform-registry.js +19 -18
  105. package/dist/media/transform-types.d.ts +8 -3
  106. package/dist/media/transform-types.js +10 -5
  107. package/dist/media/types.d.ts +21 -4
  108. package/dist/media/types.js +15 -2
  109. package/dist/navigation/agent-tools.d.ts +1 -80
  110. package/dist/navigation/agent-tools.js +15 -3
  111. package/dist/navigation/contracts.d.ts +1 -1
  112. package/dist/navigation/index.d.ts +2 -2
  113. package/dist/navigation/menu-service.d.ts +57 -40
  114. package/dist/navigation/menu-service.js +80 -69
  115. package/dist/navigation/ports.d.ts +1 -1
  116. package/dist/navigation/read-model.d.ts +3 -1
  117. package/dist/navigation/read-model.js +2 -2
  118. package/dist/navigation/reconcile.d.ts +2 -2
  119. package/dist/navigation/reconcile.js +2 -1
  120. package/dist/navigation/repo.memory.d.ts +7 -3
  121. package/dist/navigation/repo.memory.js +4 -2
  122. package/dist/navigation/resolver.d.ts +4 -1
  123. package/dist/navigation/resolver.js +1 -1
  124. package/dist/navigation/tool-registrations.d.ts +6 -8
  125. package/dist/navigation/tool-registrations.js +44 -42
  126. package/dist/navigation/types.d.ts +1 -1
  127. package/dist/presentation/presentation.d.ts +11 -3
  128. package/dist/presentation/presentation.js +13 -6
  129. package/dist/presentation/repo.memory.d.ts +6 -2
  130. package/dist/presentation/repo.memory.js +3 -2
  131. package/dist/settings/agent-tools.d.ts +2 -75
  132. package/dist/settings/agent-tools.js +49 -45
  133. package/dist/settings/agent-value-write-tools.d.ts +22 -0
  134. package/dist/settings/agent-value-write-tools.js +154 -0
  135. package/dist/settings/agent-writable-preferences.js +8 -20
  136. package/dist/settings/agent-write-denylist.d.ts +19 -0
  137. package/dist/settings/agent-write-denylist.js +15 -0
  138. package/dist/settings/change-feed.d.ts +11 -2
  139. package/dist/settings/change-feed.js +4 -4
  140. package/dist/settings/definitions-dispatch.d.ts +8 -3
  141. package/dist/settings/definitions-dispatch.js +5 -5
  142. package/dist/settings/dictionaries/index.d.ts +5 -2
  143. package/dist/settings/dictionaries/index.js +2 -2
  144. package/dist/settings/dictionaries/settings-dialog.ar.js +0 -1
  145. package/dist/settings/dictionaries/settings-dialog.bn.js +0 -1
  146. package/dist/settings/dictionaries/settings-dialog.de.js +0 -1
  147. package/dist/settings/dictionaries/settings-dialog.es.js +0 -1
  148. package/dist/settings/dictionaries/settings-dialog.fa.js +0 -1
  149. package/dist/settings/dictionaries/settings-dialog.fr.js +0 -1
  150. package/dist/settings/dictionaries/settings-dialog.hi.js +0 -1
  151. package/dist/settings/dictionaries/settings-dialog.hu.js +0 -1
  152. package/dist/settings/dictionaries/settings-dialog.id.js +0 -1
  153. package/dist/settings/dictionaries/settings-dialog.it.js +0 -1
  154. package/dist/settings/dictionaries/settings-dialog.ja.js +0 -1
  155. package/dist/settings/dictionaries/settings-dialog.ko.js +0 -1
  156. package/dist/settings/dictionaries/settings-dialog.pl.js +0 -1
  157. package/dist/settings/dictionaries/settings-dialog.pt-BR.js +0 -1
  158. package/dist/settings/dictionaries/settings-dialog.ru.js +0 -1
  159. package/dist/settings/dictionaries/settings-dialog.th.js +0 -1
  160. package/dist/settings/dictionaries/settings-dialog.tr.js +0 -1
  161. package/dist/settings/dictionaries/settings-dialog.uk.js +0 -1
  162. package/dist/settings/dictionaries/settings-dialog.ur.js +0 -1
  163. package/dist/settings/dictionaries/settings-dialog.zh-CN.js +0 -1
  164. package/dist/settings/dictionaries/settings-dialog.zh-TW.js +0 -1
  165. package/dist/settings/ensure-definitions.d.ts +5 -5
  166. package/dist/settings/errors.d.ts +7 -1
  167. package/dist/settings/errors.js +7 -1
  168. package/dist/settings/index.d.ts +4 -1
  169. package/dist/settings/index.js +1 -0
  170. package/dist/settings/ports.d.ts +6 -5
  171. package/dist/settings/principal-lookup.d.ts +9 -0
  172. package/dist/settings/principal-lookup.js +2 -0
  173. package/dist/settings/purge-service.d.ts +5 -4
  174. package/dist/settings/purge-service.js +3 -2
  175. package/dist/settings/settings.d.ts +22 -7
  176. package/dist/settings/settings.js +47 -26
  177. package/dist/settings/tool-registrations.d.ts +25 -32
  178. package/dist/settings/tool-registrations.js +66 -56
  179. package/dist/settings/types.d.ts +1 -1
  180. package/dist/settings/ui-tab-definitions.d.ts +1 -1
  181. package/dist/settings/write-service.d.ts +32 -26
  182. package/dist/settings/write-service.js +26 -20
  183. package/dist/taxonomy/agent-tools.d.ts +1 -66
  184. package/dist/taxonomy/agent-tools.js +25 -15
  185. package/dist/taxonomy/content-lookup.js +1 -1
  186. package/dist/taxonomy/index.d.ts +2 -2
  187. package/dist/taxonomy/merge-term.d.ts +7 -4
  188. package/dist/taxonomy/merge-term.js +7 -5
  189. package/dist/taxonomy/repo.memory.d.ts +42 -19
  190. package/dist/taxonomy/repo.memory.js +14 -12
  191. package/dist/taxonomy/validation-chain.d.ts +39 -18
  192. package/dist/taxonomy/validation-chain.js +35 -25
  193. package/dist/taxonomy/write-service.d.ts +103 -44
  194. package/dist/taxonomy/write-service.js +221 -179
  195. package/dist/trash/adapters/record-store.d.ts +52 -0
  196. package/dist/trash/adapters/record-store.js +36 -0
  197. package/dist/trash/cursor.d.ts +20 -0
  198. package/dist/trash/cursor.js +36 -0
  199. package/dist/trash/follow-ups.d.ts +76 -0
  200. package/dist/trash/follow-ups.js +46 -0
  201. package/dist/trash/index.d.ts +8 -0
  202. package/dist/trash/index.js +8 -0
  203. package/dist/trash/ports.d.ts +308 -0
  204. package/dist/trash/ports.js +2 -0
  205. package/dist/trash/repo.memory.d.ts +50 -0
  206. package/dist/trash/repo.memory.js +114 -0
  207. package/dist/trash/sweeper.d.ts +77 -0
  208. package/dist/trash/sweeper.js +127 -0
  209. package/dist/trash/write-service.d.ts +100 -0
  210. package/dist/trash/write-service.js +242 -0
  211. package/dist/workspace/agent-tools.d.ts +1 -59
  212. package/dist/workspace/agent-tools.js +27 -12
  213. package/dist/workspace/create.d.ts +31 -6
  214. package/dist/workspace/create.js +18 -5
  215. package/dist/workspace/delete.d.ts +18 -13
  216. package/dist/workspace/delete.js +20 -12
  217. package/dist/workspace/index.d.ts +1 -1
  218. package/dist/workspace/repo.memory.d.ts +19 -4
  219. package/dist/workspace/repo.memory.js +26 -4
  220. package/dist/workspace/tool-registrations.d.ts +2 -2
  221. package/dist/workspace/tool-registrations.js +13 -13
  222. package/dist/workspace/update.d.ts +6 -5
  223. package/dist/workspace/update.js +9 -8
  224. package/package.json +44 -38
  225. package/dist/identity/admin-crud-service.d.ts +0 -280
  226. package/dist/identity/admin-crud-service.js +0 -468
  227. package/dist/identity/agent-tool-input.d.ts +0 -58
  228. package/dist/identity/agent-tool-input.js +0 -126
  229. package/dist/identity/agent-tools.d.ts +0 -101
  230. package/dist/identity/agent-tools.js +0 -290
  231. package/dist/identity/auth-service.d.ts +0 -118
  232. package/dist/identity/auth-service.js +0 -170
  233. package/dist/identity/authorize.d.ts +0 -90
  234. package/dist/identity/authorize.js +0 -94
  235. package/dist/identity/grant-service.d.ts +0 -198
  236. package/dist/identity/grant-service.js +0 -361
  237. package/dist/identity/hasher.d.ts +0 -39
  238. package/dist/identity/hasher.js +0 -39
  239. package/dist/identity/index.d.ts +0 -62
  240. package/dist/identity/index.js +0 -54
  241. package/dist/identity/password-policy.d.ts +0 -57
  242. package/dist/identity/password-policy.js +0 -66
  243. package/dist/identity/permission-migrations.d.ts +0 -86
  244. package/dist/identity/permission-migrations.js +0 -67
  245. package/dist/identity/permissions.d.ts +0 -35
  246. package/dist/identity/permissions.js +0 -603
  247. package/dist/identity/ports.d.ts +0 -201
  248. package/dist/identity/repo.memory.d.ts +0 -164
  249. package/dist/identity/repo.memory.js +0 -224
  250. package/dist/identity/seed.d.ts +0 -54
  251. package/dist/identity/seed.js +0 -429
  252. package/dist/identity/tool-registrations.d.ts +0 -50
  253. package/dist/identity/tool-registrations.js +0 -419
  254. package/dist/identity/types.d.ts +0 -185
  255. package/dist/identity/types.js +0 -64
  256. package/dist/identity/username.d.ts +0 -12
  257. package/dist/identity/username.js +0 -14
  258. /package/dist/{identity → media/import}/ports.js +0 -0
@@ -0,0 +1,147 @@
1
+ /** Starts after HTTP authorization. All scheduling and storage are injected. */
2
+ /** Poll with bounded pages, tenant-scoped ids and retryable reads; close cancels all schedules.
3
+ * O(page size) per tick and O(1) connection state; repository effects live behind the feed port.
4
+ * Invalid scheduler intervals/page sizes throw RangeError before opening; initial read errors propagate.
5
+ */
6
+ export async function openSettingsChangeStream(required, optional = {}) {
7
+ const { request, response, workspaceId, principalId, changeFeed, scheduler } = required;
8
+ const pageSize = optional.revisionPageSize ?? 200;
9
+ const intervals = [optional.pollIntervalMs ?? 1_000, optional.keepaliveIntervalMs ?? 25_000, optional.reauthorizeIntervalMs ?? 30_000];
10
+ if (!Number.isSafeInteger(pageSize) || pageSize <= 0 || intervals.some((n) => !Number.isFinite(n) || n <= 0)) {
11
+ throw new RangeError("settings feed page size and intervals must be positive");
12
+ }
13
+ let closed = false;
14
+ let ticking = false;
15
+ let checkingAuthorization = false;
16
+ const cancellations = [];
17
+ const reportError = (operation, error) => {
18
+ // Reporting is best-effort: a host hook must never turn recovery into an unhandled rejection.
19
+ try {
20
+ if (optional.onError)
21
+ optional.onError({ operation, error });
22
+ else
23
+ console.error(`[settings] change feed ${operation} failed`, error);
24
+ }
25
+ catch { /* Diagnostic failures cannot interrupt stream cleanup or retries. */ }
26
+ };
27
+ const recover = (operation, effect) => {
28
+ try {
29
+ effect();
30
+ }
31
+ catch (error) {
32
+ reportError(operation, error);
33
+ }
34
+ };
35
+ const release = () => {
36
+ // Every resource gets its own recovery boundary so one failing cancellation cannot leak others.
37
+ for (const cancel of cancellations.splice(0))
38
+ recover("cancel", cancel);
39
+ recover("request-listener", () => { request.off("close", close); });
40
+ recover("response-listener", () => { response.off("close", close); });
41
+ };
42
+ const close = () => {
43
+ if (closed)
44
+ return;
45
+ closed = true;
46
+ release();
47
+ recover("close", () => { required.onClose({ close }); });
48
+ if (!response.writableEnded && !response.destroyed)
49
+ recover("end", () => { response.end(); });
50
+ };
51
+ request.on("close", close);
52
+ response.on("close", close);
53
+ try {
54
+ const head = await changeFeed.head({});
55
+ if (closed || required.isDisposed() || response.destroyed || response.writableEnded) {
56
+ close();
57
+ return close;
58
+ }
59
+ const resumeFrom = Number(request.headers["last-event-id"]);
60
+ let cursor = Number.isFinite(resumeFrom) && resumeFrom >= 0 ? Math.min(resumeFrom, head) : head;
61
+ let lastEmittedId = cursor;
62
+ const viewer = { workspaceId, principalId };
63
+ response.writeHead(200, {
64
+ "Content-Type": "text/event-stream", "Cache-Control": "no-cache, no-transform",
65
+ ...(request.httpVersionMajor < 2 ? { Connection: "keep-alive" } : {}), "X-Accel-Buffering": "no",
66
+ });
67
+ const tick = async () => {
68
+ if (closed || ticking)
69
+ return;
70
+ ticking = true;
71
+ try {
72
+ // Snapshot head before the page: concurrent writes must never be skipped.
73
+ const ledgerHead = await changeFeed.head({});
74
+ if (closed)
75
+ return;
76
+ const batch = await changeFeed.collect({ sinceSeq: cursor, limit: pageSize, viewer });
77
+ if (closed)
78
+ return;
79
+ const examined = batch.examinedCount < pageSize ? ledgerHead : batch.cursor;
80
+ cursor = Math.max(cursor, examined, batch.cursor);
81
+ if (!batch.namespaces.length)
82
+ return;
83
+ // The internal global head is never disclosed as an event id.
84
+ lastEmittedId = Math.max(lastEmittedId, batch.cursor);
85
+ response.write(`id: ${lastEmittedId}\nevent: settings-changed\ndata: ${JSON.stringify({ namespaces: batch.namespaces })}\n\n`);
86
+ }
87
+ catch (error) {
88
+ if (!closed)
89
+ reportError("poll", error);
90
+ }
91
+ finally {
92
+ ticking = false;
93
+ }
94
+ };
95
+ const reauthorize = async () => {
96
+ if (closed || checkingAuthorization)
97
+ return;
98
+ checkingAuthorization = true;
99
+ try {
100
+ const result = await required.authorize({ principalId, workspaceId, permission: required.permission, entityType: "setting-value" });
101
+ if (!closed && !result.allowed)
102
+ close();
103
+ }
104
+ catch (error) {
105
+ if (!closed)
106
+ reportError("re-authorization", error);
107
+ }
108
+ finally {
109
+ checkingAuthorization = false;
110
+ }
111
+ };
112
+ const schedule = (intervalMs, callback) => {
113
+ const cancel = scheduler.every({ intervalMs, callback });
114
+ if (closed)
115
+ recover("cancel", cancel);
116
+ else
117
+ cancellations.push(cancel);
118
+ };
119
+ schedule(intervals[0], () => { void tick(); });
120
+ schedule(intervals[1], () => {
121
+ if (closed)
122
+ return;
123
+ try {
124
+ response.write(": keepalive\n\n");
125
+ }
126
+ catch (error) {
127
+ reportError("keepalive", error);
128
+ close();
129
+ }
130
+ });
131
+ schedule(intervals[2], () => { void reauthorize(); });
132
+ if (!closed)
133
+ response.write(": connected\n\n");
134
+ return close;
135
+ }
136
+ catch (error) {
137
+ // Cancel schedules even when opening a feed fails. Let the HTTP wrapper map the error.
138
+ if (response.headersSent)
139
+ close();
140
+ else {
141
+ closed = true;
142
+ release();
143
+ }
144
+ throw error;
145
+ }
146
+ }
147
+ //# sourceMappingURL=stream.js.map
@@ -1,69 +1,4 @@
1
- /**
2
- * @file The Media agent-tool catalog — this domain's instance of the per-domain
3
- * `agent-tools.ts` convention Forms/Identity already use.
4
- *
5
- * Purpose:
6
- * A static, in-process catalog describing every agent-callable tool this domain exposes and the
7
- * permission each carries. Every entry maps 1:1 onto a real exported function of
8
- * `media/media-service.ts` — this catalog never names an operation the domain cannot perform.
9
- *
10
- * Deliberate absences (the point of a catalog, not an oversight):
11
- * - There is NO `media_purge_asset`. `purgeMedia` is a HARD delete: it removes the `MediaRecord`
12
- * row permanently and triggers the blob-GC tombstone pass (`tombstoneBlobIfUnreferenced`,
13
- * `blob-gc.ts`) that can eventually reclaim the underlying bytes. It is gated behind
14
- * `media.delete.force` — a separate, narrower permission a human admin UI itself treats as a
15
- * distinct escalation tier from ordinary `media.delete` (mirrors `admin.menus.delete` vs
16
- * `admin.menus.delete.force`). An assistant is reachable by prompt injection through ordinary
17
- * operator content (a comment, a form submission) in a way a human clicking the admin "purge"
18
- * button is not, so an irreversible, storage-reclaiming delete is left human-UI-only — the same
19
- * discipline `identity/agent-tools.ts` applies to `resetUserPassword`.
20
- * - There is NO named-transform-registry tool (`registerTransform`, `media/transform-registry.ts`).
21
- * No admin HTTP route needs to expose it at all — it is a core-declared, boot-time/operational
22
- * concept, not a human admin-UI operation. Wrapping it here would invent agent capability beyond
23
- * what a host's admin surface itself exposes, which this catalog's own discipline forbids.
24
- *
25
- * How it relates to a host:
26
- * A host's tool-registration layer maps these entries into `ToolRegistration`s (see
27
- * `tool-registrations.ts`). Unlike Forms/Identity, `media-service.ts`'s functions perform NO
28
- * internal `authorize()` call of their own (confirmed: none of `UploadMediaDeps`/
29
- * `UpdateMediaMetadataDeps`/`TrashMediaDeps` carry an `authorize` field) — a host's admin HTTP
30
- * routes check authorization inline, before calling the service function. `tool-registrations.ts`
31
- * therefore performs that SAME inline authorize check itself (the identical permission string and
32
- * `entityType: "media"` a host route would use) rather than relying on a domain-layer gate that
33
- * does not exist for this library — a disclosed, deliberate divergence from the Forms/Identity/
34
- * content-types pass-through-`ToolPolicy` reasoning, not an oversight.
35
- *
36
- * Architectural role:
37
- * `media` domain declaration. Imports only the constant its own domain already enforces
38
- * (`DEFAULT_ALLOWED_MIME_TYPES` from `media-service.ts`), so the published JSON Schema's allowed
39
- * content types cannot drift from the validator.
40
- *
41
- * `media_upload_asset`'s size-cap wording (2026-09-21): this catalog is a module-load-time constant
42
- * shared by every host (see this file's own module doc above), but a host can now override
43
- * `uploadMedia`'s cap per-call via `MediaToolDeps.maxUploadBytes` (`tool-registrations.ts`). Stating
44
- * `DEFAULT_MAX_UPLOAD_BYTES` (10 MiB) here would be an outright lie for a host that raised its own
45
- * cap (e.g. Tovu's 50 MiB) — the model would be told a wrong, lower number than what the tool
46
- * actually accepts. The description and schema below therefore describe the cap generically instead
47
- * of stating a figure; the size-rejection error itself (`uploadMedia`'s `MediaValidationError`,
48
- * `media-service.ts`) states the ACTUAL configured limit in MB, which is the one place that can be
49
- * host-accurate.
50
- */
51
- export type AgentToolSideEffect = "none" | "mutates-durable-state" | "mints-token";
52
- export type AgentToolActorClassRule = "confirmer-must-equal-own-delegatedBy" | "user-only" | "none";
53
- export interface AgentToolDefinition {
54
- name: string;
55
- description: string;
56
- sideEffects: AgentToolSideEffect;
57
- authorization: {
58
- permission: string;
59
- };
60
- actorClassRule?: AgentToolActorClassRule;
61
- /**
62
- * JSON Schema for this tool's `input`, published to the model via `ToolDescriptor.inputSchema`
63
- * (a host's tool-registration layer refuses to wire any tool lacking one).
64
- */
65
- inputSchema?: Readonly<Record<string, unknown>>;
66
- }
1
+ import type { AgentToolDefinition } from "@jini-ai/core";
67
2
  /**
68
3
  * The Media domain's fixed agent-tool catalog — the 4 operations a host's admin HTTP surface
69
4
  * exposes that are safe to wrap, out of its 5 total (see file header for why `purge` is excluded).
@@ -42,7 +42,7 @@
42
42
  * shared by every host (see this file's own module doc above), but a host can now override
43
43
  * `uploadMedia`'s cap per-call via `MediaToolDeps.maxUploadBytes` (`tool-registrations.ts`). Stating
44
44
  * `DEFAULT_MAX_UPLOAD_BYTES` (10 MiB) here would be an outright lie for a host that raised its own
45
- * cap (e.g. Tovu's 50 MiB) — the model would be told a wrong, lower number than what the tool
45
+ * cap (e.g. a host's 50 MiB) — the model would be told a wrong, lower number than what the tool
46
46
  * actually accepts. The description and schema below therefore describe the cap generically instead
47
47
  * of stating a figure; the size-rejection error itself (`uploadMedia`'s `MediaValidationError`,
48
48
  * `media-service.ts`) states the ACTUAL configured limit in MB, which is the one place that can be
@@ -50,6 +50,10 @@
50
50
  */
51
51
  import { MEDIA_HTML_ATTRIBUTE_ALLOWED_NAMES } from "./html-attributes.js";
52
52
  import { DEFAULT_ALLOWED_MIME_TYPES } from "./media-service.js";
53
+ /**
54
+ * JSON Schema for this tool's `input`, published to the model via `ToolDescriptor.inputSchema`
55
+ * (a host's tool-registration layer refuses to wire any tool lacking one).
56
+ */
53
57
  const MEDIA_ID_SCHEMA = {
54
58
  type: "string",
55
59
  description: "The media asset's id, as returned by media_upload_asset or media_list_assets.",
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @file In-process, per-sha256 mutex for the blob-GC protocol
3
- * (INV-1's "Serialization" clause).
3
+ * ("Serialization" clause).
4
4
  *
5
5
  * Purpose:
6
6
  * The real protocol serializes every `asset_blobs` state transition
@@ -20,6 +20,7 @@
20
20
  * decorative comment. See `blob-gc.ts`'s file header for what this does NOT
21
21
  * cover (cross-process safety, which needs a real DB transaction — a host's
22
22
  * own SQLite adapter would supply that).
23
+ * See docs/decisions/DR-004-journaled-blob-gc.md.
23
24
  */
24
25
  /**
25
26
  * Runs `criticalSection` exclusively with respect to any other call
@@ -33,5 +34,8 @@
33
34
  * by the number of other calls currently queued for the same key.
34
35
  * @overallScore 100
35
36
  */
36
- export declare function withSha256Lock<T>(key: string, criticalSection: () => Promise<T>): Promise<T>;
37
+ export declare function withSha256Lock<T>(requiredArgs: {
38
+ key: string;
39
+ criticalSection: () => Promise<T>;
40
+ }, optionalArgs?: Record<string, never>): Promise<T>;
37
41
  //# sourceMappingURL=blob-gc-lock.d.ts.map
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @file In-process, per-sha256 mutex for the blob-GC protocol
3
- * (INV-1's "Serialization" clause).
3
+ * ("Serialization" clause).
4
4
  *
5
5
  * Purpose:
6
6
  * The real protocol serializes every `asset_blobs` state transition
@@ -20,6 +20,7 @@
20
20
  * decorative comment. See `blob-gc.ts`'s file header for what this does NOT
21
21
  * cover (cross-process safety, which needs a real DB transaction — a host's
22
22
  * own SQLite adapter would supply that).
23
+ * See docs/decisions/DR-004-journaled-blob-gc.md.
23
24
  */
24
25
  /**
25
26
  * Tracks the tail of the promise chain for each in-flight/queued key. An
@@ -40,7 +41,8 @@ const lockTails = new Map();
40
41
  * by the number of other calls currently queued for the same key.
41
42
  * @overallScore 100
42
43
  */
43
- export function withSha256Lock(key, criticalSection) {
44
+ export function withSha256Lock(requiredArgs, optionalArgs = {}) {
45
+ const { key, criticalSection } = requiredArgs;
44
46
  const priorTail = lockTails.get(key) ?? Promise.resolve();
45
47
  const run = priorTail.then(criticalSection, criticalSection);
46
48
  const settledTail = run.then(() => undefined, () => undefined);
@@ -1,52 +1,53 @@
1
1
  /**
2
2
  * @file Blob-GC protocol ("References, deletion, and GC
3
- * (never-brick)", INV-1) — the audited tombstone -> delete-pass -> unlink-pass
3
+ * (never-brick)") — the audited tombstone -> delete-pass -> unlink-pass
4
4
  * design, scoped to what an in-memory-repo build can actually run today.
5
5
  *
6
6
  * Built in this pass:
7
- * - The real "unreferenced" predicate's sha256-uniqueness-across-non-purged-
8
- * media-entries conjunct ({@link isBlobUnreferenced}), backed by the actual
9
- * `mediaRepo` data `purgeMedia` already has.
10
- * - Ordering (INV-1a): {@link runBlobGcDeletePass} deletes the `asset_blobs`
11
- * row and writes the `blob_gc_journal` entry together, before any bytes are
12
- * touched; {@link runBlobGcUnlinkPass} only ever unlinks bytes for a row
13
- * whose deletion has already happened. `uploadMedia` (see
14
- * `media-service.ts`) already writes bytes before the row.
15
- * - Serialization: every transition below runs inside {@link withSha256Lock}
16
- * (`blob-gc-lock.ts`) — this build's substitute for `BEGIN IMMEDIATE`
17
- * against an in-memory table (see that file's header for why).
18
- * - The two-phase, journaled shape: {@link tombstoneBlobIfUnreferenced} ->
19
- * {@link runBlobGcDeletePass} (re-checks the predicate + `gc_grace`,
20
- * journals, deletes the row) -> {@link runBlobGcUnlinkPass} (drains the
21
- * journal, removes bytes). {@link runBlobGcCycle} is a convenience batch
22
- * wrapper around the last two for tests/manual operation; nothing calls it
23
- * on a schedule.
24
- * - `gc_grace = max(30d default, retained-snapshot age)`, with the snapshot
25
- * input wired as a stub (see below) so a real value slots in later without
26
- * reworking callers.
7
+ * - The real "unreferenced" predicate's sha256-uniqueness-across-non-purged-
8
+ * media-entries conjunct ({@link isBlobUnreferenced}), backed by the actual
9
+ * `mediaRepo` data `purgeMedia` already has.
10
+ * - Ordering : {@link runBlobGcDeletePass} first writes the `blob_gc_journal`
11
+ * entry, then deletes the `asset_blobs` row in separate awaited operations, before any bytes are
12
+ * touched; {@link runBlobGcUnlinkPass} only ever unlinks bytes for a row
13
+ * whose deletion has already happened. `uploadMedia` (see
14
+ * `media-service.ts`) already writes bytes before the row.
15
+ * - Serialization: every transition below runs inside {@link withSha256Lock}
16
+ * (`blob-gc-lock.ts`) — this build's substitute for `BEGIN IMMEDIATE`
17
+ * against an in-memory table (see that file's header for why).
18
+ * - The two-phase, journaled shape: {@link tombstoneBlobIfUnreferenced} ->
19
+ * {@link runBlobGcDeletePass} (re-checks the predicate + `gc_grace`,
20
+ * journals, deletes the row) -> {@link runBlobGcUnlinkPass} (drains the
21
+ * journal, removes bytes). {@link runBlobGcCycle} is a convenience batch
22
+ * wrapper around the last two for tests/manual operation; nothing calls it
23
+ * on a schedule.
24
+ * - `gc_grace = max(30d default, retained-snapshot age)`, with the snapshot
25
+ * input wired as a stub (see below) so a real value slots in later without
26
+ * reworking callers.
27
27
  *
28
28
  * Deliberately NOT built (disclosed, matches this task's explicit scope):
29
- * - **`entry_refs` where-used index** ({@link hasLiveEntryRefs}) — belongs to
30
- * the generic entries model, which is accepted design only, not
31
- * running code yet (see `types.ts`'s file header on `MediaRecord` being a
32
- * bespoke table for the same reason). Stubbed to always return `false`
33
- * ("no live refs found").
34
- * - **Retained-snapshot conjunct** ({@link getOldestRetainedSnapshotAgeMs}) —
35
- * owned by a pending Storage/Backups snapshot primitive, which doesn't
36
- * exist yet either. Stubbed to always
37
- * return `undefined` ("no retained snapshot").
38
- * - **Monthly orphan sweep** ({@link runMonthlyOrphanSweepStub}) — the fourth
39
- * protocol phase, reconciling bytes on disk with no `asset_blobs` row at
40
- * all (e.g. a crash before any GC protocol existed). Needs a scheduler
41
- * this library doesn't provide. Named so the seam exists; performs no work
42
- * and is never called.
43
- * - **Cross-process safety** — `withSha256Lock` only serializes within one
44
- * Node process. A real multi-instance deployment needs the real DB
45
- * transaction the original design specifies, which a host's own persistence adapter
46
- * would supply; this build has no multi-writer story for media at all yet
47
- * (in-memory repo), so that gap is inherited, not new.
29
+ * - **`entry_refs` where-used index** ({@link hasLiveEntryRefs}) — belongs to
30
+ * the generic entries model, which is accepted design only, not
31
+ * running code yet (see `types.ts`'s file header on `MediaRecord` being a
32
+ * bespoke table for the same reason). Stubbed to always return `false`
33
+ * ("no live refs found").
34
+ * - **Retained-snapshot conjunct** ({@link getOldestRetainedSnapshotAgeMs}) —
35
+ * owned by a pending Storage/Backups snapshot primitive, which doesn't
36
+ * exist yet either. Stubbed to always
37
+ * return `undefined` ("no retained snapshot").
38
+ * - **Monthly orphan sweep** ({@link runMonthlyOrphanSweepStub}) — the fourth
39
+ * protocol phase, reconciling bytes on disk with no `asset_blobs` row at
40
+ * all (e.g. a crash before any GC protocol existed). Needs a scheduler
41
+ * this library doesn't provide. Named so the seam exists; performs no work
42
+ * and is never called.
43
+ * - **Cross-process safety** — `withSha256Lock` only serializes within one
44
+ * Node process. A real multi-instance deployment needs the real DB
45
+ * transaction the original design specifies, which a host's own persistence adapter
46
+ * would supply; this build has no multi-writer story for media at all yet
47
+ * (in-memory repo), so that gap is inherited, not new.
48
+ * See docs/decisions/DR-004-journaled-blob-gc.md.
48
49
  */
49
- import type { ClockPort, IdGeneratorPort, UUID } from "../core/ports.js";
50
+ import type { Clock, IdGenerator, UUID } from "@jini-ai/core/primitives";
50
51
  import type { AssetBlobRepoPort, BlobGcJournalRepoPort, BlobStorePort, MediaRepoPort } from "./ports.js";
51
52
  /** Default retention window before a tombstoned blob's delete-pass may run. */
52
53
  export declare const DEFAULT_GC_GRACE_MS: number;
@@ -100,7 +101,7 @@ export declare function isBlobUnreferenced(required: IsBlobUnreferencedRequired)
100
101
  export interface TombstoneBlobDeps {
101
102
  mediaRepo: MediaRepoPort;
102
103
  blobRepo: AssetBlobRepoPort;
103
- clock: ClockPort;
104
+ clock: Clock;
104
105
  }
105
106
  export interface TombstoneBlobRequired {
106
107
  deps: TombstoneBlobDeps;
@@ -128,8 +129,8 @@ export interface RunBlobGcDeletePassDeps {
128
129
  mediaRepo: MediaRepoPort;
129
130
  blobRepo: AssetBlobRepoPort;
130
131
  journalRepo: BlobGcJournalRepoPort;
131
- clock: ClockPort;
132
- idGen: IdGeneratorPort;
132
+ clock: Clock;
133
+ idGen: IdGenerator;
133
134
  }
134
135
  export interface RunBlobGcDeletePassRequired {
135
136
  deps: RunBlobGcDeletePassDeps;
@@ -147,11 +148,20 @@ export interface RunBlobGcDeletePassRequired {
147
148
  * re-check is what the protocol calls for and is what future non-stub
148
149
  * entry_refs/snapshot conjuncts would need). On success: writes the
149
150
  * `blob_gc_journal` entry and removes the `asset_blobs` row, in that order,
150
- * inside the same locked section — INV-1a's "unlinked only after the row
151
+ * inside the same locked section — "unlinked only after the row
151
152
  * deletion commits" half depends on nothing touching bytes here at all.
153
+ * These operations are ordered, not atomic. A journal-save rejection leaves the row alone;
154
+ * a row-delete rejection leaves a pending entry plus the row. The unlink-pass rechecks ANY
155
+ * row (including a tombstone), skips its bytes and drains that stale entry, so a later delete
156
+ * retry can journal again. If row deletion succeeded before interruption, the pending entry
157
+ * lets unlink resume; unlink failure retains the entry, and byte removal must be idempotent.
158
+ * Durable recovery requires the journal save to commit before row deletion on the same store;
159
+ * the in-memory adapters survive neither a process crash nor restart. Across processes the
160
+ * host must additionally serialize row ownership checks, uploads and unlinking (see header).
152
161
  *
153
162
  * @complexity O(n) — dominated by the re-check's `isBlobUnreferenced` scan.
154
163
  * @overallScore 100
164
+ * See docs/decisions/DR-004-journaled-blob-gc.md.
155
165
  */
156
166
  export declare function runBlobGcDeletePass(required: RunBlobGcDeletePassRequired): Promise<{
157
167
  deleted: boolean;
@@ -235,12 +245,15 @@ export declare function runBlobGcCycle(required: RunBlobGcCycleRequired): Promis
235
245
  * @complexity O(1) (stub — does nothing).
236
246
  * @overallScore 100
237
247
  */
238
- export declare function runMonthlyOrphanSweepStub(_deps: {
239
- blobStore: BlobStorePort;
240
- blobRepo: AssetBlobRepoPort;
241
- }, _input: {
242
- workspaceId: UUID;
243
- }): Promise<{
248
+ export declare function runMonthlyOrphanSweepStub(_required: {
249
+ deps: {
250
+ blobStore: BlobStorePort;
251
+ blobRepo: AssetBlobRepoPort;
252
+ };
253
+ input: {
254
+ workspaceId: UUID;
255
+ };
256
+ }, _optional?: Record<string, never>): Promise<{
244
257
  implemented: false;
245
258
  }>;
246
259
  //# sourceMappingURL=blob-gc.d.ts.map
@@ -1,3 +1,4 @@
1
+ import { nowIso as kernelNowIso } from "@jini-ai/core/primitives";
1
2
  import { withSha256Lock } from "./blob-gc-lock.js";
2
3
  /** Default retention window before a tombstoned blob's delete-pass may run. */
3
4
  export const DEFAULT_GC_GRACE_MS = 30 * 24 * 60 * 60 * 1000; // 30 days
@@ -104,18 +105,18 @@ export async function isBlobUnreferenced(required) {
104
105
  */
105
106
  export async function tombstoneBlobIfUnreferenced(required) {
106
107
  const { deps, input } = required;
107
- return withSha256Lock(input.sha256, async () => {
108
- const row = await deps.blobRepo.findByHash(input);
109
- if (!row)
110
- return { tombstoned: false, reason: "no-blob-row" };
111
- if (row.status === "tombstoned")
112
- return { tombstoned: true, reason: "already-tombstoned" };
113
- const unreferenced = await isBlobUnreferenced({ deps, input });
114
- if (!unreferenced)
115
- return { tombstoned: false, reason: "still-referenced" };
116
- await deps.blobRepo.save({ ...row, status: "tombstoned", tombstonedAt: deps.clock.nowIso() });
117
- return { tombstoned: true, reason: "tombstoned" };
118
- });
108
+ return withSha256Lock({ key: input.sha256, criticalSection: async () => {
109
+ const row = await deps.blobRepo.findByHash(input);
110
+ if (!row)
111
+ return { tombstoned: false, reason: "no-blob-row" };
112
+ if (row.status === "tombstoned")
113
+ return { tombstoned: true, reason: "already-tombstoned" };
114
+ const unreferenced = await isBlobUnreferenced({ deps, input });
115
+ if (!unreferenced)
116
+ return { tombstoned: false, reason: "still-referenced" };
117
+ await deps.blobRepo.save({ ...row, status: "tombstoned", tombstonedAt: kernelNowIso({ clock: deps.clock }) });
118
+ return { tombstoned: true, reason: "tombstoned" };
119
+ } });
119
120
  }
120
121
  /**
121
122
  * Delete-pass. Gated on the row being `tombstoned` AND `gc_grace` having
@@ -126,38 +127,47 @@ export async function tombstoneBlobIfUnreferenced(required) {
126
127
  * re-check is what the protocol calls for and is what future non-stub
127
128
  * entry_refs/snapshot conjuncts would need). On success: writes the
128
129
  * `blob_gc_journal` entry and removes the `asset_blobs` row, in that order,
129
- * inside the same locked section — INV-1a's "unlinked only after the row
130
+ * inside the same locked section — "unlinked only after the row
130
131
  * deletion commits" half depends on nothing touching bytes here at all.
132
+ * These operations are ordered, not atomic. A journal-save rejection leaves the row alone;
133
+ * a row-delete rejection leaves a pending entry plus the row. The unlink-pass rechecks ANY
134
+ * row (including a tombstone), skips its bytes and drains that stale entry, so a later delete
135
+ * retry can journal again. If row deletion succeeded before interruption, the pending entry
136
+ * lets unlink resume; unlink failure retains the entry, and byte removal must be idempotent.
137
+ * Durable recovery requires the journal save to commit before row deletion on the same store;
138
+ * the in-memory adapters survive neither a process crash nor restart. Across processes the
139
+ * host must additionally serialize row ownership checks, uploads and unlinking (see header).
131
140
  *
132
141
  * @complexity O(n) — dominated by the re-check's `isBlobUnreferenced` scan.
133
142
  * @overallScore 100
143
+ * See docs/decisions/DR-004-journaled-blob-gc.md.
134
144
  */
135
145
  export async function runBlobGcDeletePass(required) {
136
146
  const { deps, input } = required;
137
- return withSha256Lock(input.sha256, async () => {
138
- const row = await deps.blobRepo.findByHash(input);
139
- if (!row)
140
- return { deleted: false, reason: "no-blob-row" };
141
- if (row.status !== "tombstoned" || !row.tombstonedAt) {
142
- return { deleted: false, reason: "not-tombstoned" };
143
- }
144
- const graceMs = resolveGcGraceMs(input);
145
- const elapsedMs = new Date(deps.clock.nowIso()).getTime() - new Date(row.tombstonedAt).getTime();
146
- if (elapsedMs < graceMs)
147
- return { deleted: false, reason: "grace-period-not-elapsed" };
148
- const stillUnreferenced = await isBlobUnreferenced({ deps, input });
149
- if (!stillUnreferenced)
150
- return { deleted: false, reason: "resurrected" };
151
- await deps.journalRepo.save({
152
- id: deps.idGen.newId(),
153
- workspaceId: input.workspaceId,
154
- sha256: input.sha256,
155
- storageKey: row.storageKey,
156
- journaledAt: deps.clock.nowIso(),
157
- });
158
- await deps.blobRepo.remove(input);
159
- return { deleted: true, reason: "deleted" };
160
- });
147
+ return withSha256Lock({ key: input.sha256, criticalSection: async () => {
148
+ const row = await deps.blobRepo.findByHash(input);
149
+ if (!row)
150
+ return { deleted: false, reason: "no-blob-row" };
151
+ if (row.status !== "tombstoned" || !row.tombstonedAt) {
152
+ return { deleted: false, reason: "not-tombstoned" };
153
+ }
154
+ const graceMs = resolveGcGraceMs(input);
155
+ const elapsedMs = new Date(kernelNowIso({ clock: deps.clock })).getTime() - new Date(row.tombstonedAt).getTime();
156
+ if (elapsedMs < graceMs)
157
+ return { deleted: false, reason: "grace-period-not-elapsed" };
158
+ const stillUnreferenced = await isBlobUnreferenced({ deps, input });
159
+ if (!stillUnreferenced)
160
+ return { deleted: false, reason: "resurrected" };
161
+ await deps.journalRepo.save({
162
+ id: deps.idGen.newId(),
163
+ workspaceId: input.workspaceId,
164
+ sha256: input.sha256,
165
+ storageKey: row.storageKey,
166
+ journaledAt: kernelNowIso({ clock: deps.clock }),
167
+ });
168
+ await deps.blobRepo.remove(input);
169
+ return { deleted: true, reason: "deleted" };
170
+ } });
161
171
  }
162
172
  /**
163
173
  * Unlink-pass. Drains every journal entry for the workspace; for each, under
@@ -188,20 +198,20 @@ export async function runBlobGcUnlinkPass(required) {
188
198
  const unlinked = [];
189
199
  const skipped = [];
190
200
  for (const entry of entries) {
191
- await withSha256Lock(entry.sha256, async () => {
192
- const liveRow = await deps.blobRepo.findByHash({
193
- workspaceId: entry.workspaceId,
194
- sha256: entry.sha256,
195
- });
196
- if (liveRow) {
197
- skipped.push(entry.sha256);
198
- }
199
- else {
200
- await deps.blobStore.remove({ storageKey: entry.storageKey });
201
- unlinked.push(entry.sha256);
202
- }
203
- await deps.journalRepo.remove({ workspaceId: entry.workspaceId, id: entry.id });
204
- });
201
+ await withSha256Lock({ key: entry.sha256, criticalSection: async () => {
202
+ const liveRow = await deps.blobRepo.findByHash({
203
+ workspaceId: entry.workspaceId,
204
+ sha256: entry.sha256,
205
+ });
206
+ if (liveRow) {
207
+ skipped.push(entry.sha256);
208
+ }
209
+ else {
210
+ await deps.blobStore.remove({ storageKey: entry.storageKey });
211
+ unlinked.push(entry.sha256);
212
+ }
213
+ await deps.journalRepo.remove({ workspaceId: entry.workspaceId, id: entry.id });
214
+ } });
205
215
  }
206
216
  return { unlinked, skipped };
207
217
  }
@@ -248,7 +258,7 @@ export async function runBlobGcCycle(required) {
248
258
  * @complexity O(1) (stub — does nothing).
249
259
  * @overallScore 100
250
260
  */
251
- export async function runMonthlyOrphanSweepStub(_deps, _input) {
261
+ export async function runMonthlyOrphanSweepStub(_required, _optional = {}) {
252
262
  return { implemented: false };
253
263
  }
254
264
  //# sourceMappingURL=blob-gc.js.map
@@ -14,7 +14,7 @@
14
14
  * `media-service.ts` file header). A future epoch-aware rewrite would change
15
15
  * this function's output shape, not its callers' contract.
16
16
  */
17
- import type { UUID } from "../core/ports.js";
17
+ import type { UUID } from "@jini-ai/core/primitives";
18
18
  /**
19
19
  * Derives `ws/{workspaceId}/blobs/{sha256[0..1]}/{sha256}`.
20
20
  *
@@ -45,5 +45,7 @@ export type SniffedContentType = "image/png" | "image/jpeg" | "image/gif" | "ima
45
45
  * {@link TEXT_SNIFF_WINDOW} bytes, a fixed constant independent of `bytes.length`.
46
46
  * @overallScore 100
47
47
  */
48
- export declare function sniffContentType(bytes: Uint8Array): SniffedContentType;
48
+ export declare function sniffContentType({ bytes }: {
49
+ bytes: Uint8Array;
50
+ }, _optional?: Record<string, never>): SniffedContentType;
49
51
  //# sourceMappingURL=content-type-sniffer.d.ts.map
@@ -162,7 +162,7 @@ function isSvgLike(text) {
162
162
  * {@link TEXT_SNIFF_WINDOW} bytes, a fixed constant independent of `bytes.length`.
163
163
  * @overallScore 100
164
164
  */
165
- export function sniffContentType(bytes) {
165
+ export function sniffContentType({ bytes }, _optional = {}) {
166
166
  if (isPng(bytes))
167
167
  return "image/png";
168
168
  if (isJpeg(bytes))