@jini-ai/cms 0.3.6 → 0.3.7

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 (299) hide show
  1. package/dist/content-types/index-provisioning.d.ts +1 -1
  2. package/dist/content-types/index-provisioning.js +1 -1
  3. package/dist/core/ports.d.ts +1 -1
  4. package/dist/identity/auth-service.d.ts +1 -1
  5. package/dist/identity/auth-service.js +1 -1
  6. package/dist/identity/seed.d.ts +1 -1
  7. package/dist/identity/seed.js +3 -3
  8. package/dist/media/agent-tools.d.ts +13 -3
  9. package/dist/media/agent-tools.js +45 -10
  10. package/dist/media/html-attributes.d.ts +6 -6
  11. package/dist/media/html-attributes.js +6 -6
  12. package/dist/media/index.d.ts +1 -1
  13. package/dist/media/index.js +1 -1
  14. package/dist/media/media-service.d.ts +2 -1
  15. package/dist/media/media-service.js +23 -7
  16. package/dist/media/tool-registrations.d.ts +12 -0
  17. package/dist/media/tool-registrations.js +20 -12
  18. package/dist/media/types.d.ts +1 -1
  19. package/dist/navigation/index.d.ts +2 -2
  20. package/dist/navigation/index.js +2 -2
  21. package/dist/navigation/menu-service.d.ts +4 -4
  22. package/dist/navigation/menu-service.js +4 -4
  23. package/dist/settings/dictionaries/index.d.ts +3 -3
  24. package/dist/settings/dictionaries/index.js +3 -3
  25. package/dist/taxonomy/agent-tools.d.ts +5 -4
  26. package/dist/taxonomy/agent-tools.js +33 -10
  27. package/dist/taxonomy/content-lookup.d.ts +35 -0
  28. package/dist/taxonomy/content-lookup.js +28 -0
  29. package/dist/taxonomy/index.d.ts +2 -2
  30. package/dist/taxonomy/index.js +2 -2
  31. package/dist/taxonomy/repo.memory.d.ts +11 -2
  32. package/dist/taxonomy/repo.memory.js +11 -0
  33. package/dist/taxonomy/write-service.d.ts +44 -15
  34. package/dist/taxonomy/write-service.js +60 -8
  35. package/package.json +3 -2
  36. package/dist/content-types/agent-tools.d.ts.map +0 -1
  37. package/dist/content-types/agent-tools.js.map +0 -1
  38. package/dist/content-types/cleanup.d.ts.map +0 -1
  39. package/dist/content-types/cleanup.js.map +0 -1
  40. package/dist/content-types/errors.d.ts.map +0 -1
  41. package/dist/content-types/errors.js.map +0 -1
  42. package/dist/content-types/field-defs.d.ts.map +0 -1
  43. package/dist/content-types/field-defs.js.map +0 -1
  44. package/dist/content-types/index-provisioning.d.ts.map +0 -1
  45. package/dist/content-types/index-provisioning.js.map +0 -1
  46. package/dist/content-types/index.d.ts.map +0 -1
  47. package/dist/content-types/index.js.map +0 -1
  48. package/dist/content-types/lifecycle-dispatch.d.ts.map +0 -1
  49. package/dist/content-types/lifecycle-dispatch.js.map +0 -1
  50. package/dist/content-types/lifecycle.d.ts.map +0 -1
  51. package/dist/content-types/lifecycle.js.map +0 -1
  52. package/dist/content-types/list.d.ts.map +0 -1
  53. package/dist/content-types/list.js.map +0 -1
  54. package/dist/content-types/repo.memory.d.ts.map +0 -1
  55. package/dist/content-types/repo.memory.js.map +0 -1
  56. package/dist/content-types/tool-registrations.d.ts.map +0 -1
  57. package/dist/content-types/tool-registrations.js.map +0 -1
  58. package/dist/content-types/types.d.ts.map +0 -1
  59. package/dist/content-types/types.js.map +0 -1
  60. package/dist/content-types/write-service.d.ts.map +0 -1
  61. package/dist/content-types/write-service.js.map +0 -1
  62. package/dist/core/commands/change-set.d.ts.map +0 -1
  63. package/dist/core/commands/change-set.js.map +0 -1
  64. package/dist/core/commands/command.d.ts.map +0 -1
  65. package/dist/core/commands/command.js.map +0 -1
  66. package/dist/core/index.d.ts.map +0 -1
  67. package/dist/core/index.js.map +0 -1
  68. package/dist/core/ports.d.ts.map +0 -1
  69. package/dist/core/ports.js.map +0 -1
  70. package/dist/core/tools/registration-kit.d.ts.map +0 -1
  71. package/dist/core/tools/registration-kit.js.map +0 -1
  72. package/dist/entries/agent-tools.d.ts.map +0 -1
  73. package/dist/entries/agent-tools.js.map +0 -1
  74. package/dist/entries/errors.d.ts.map +0 -1
  75. package/dist/entries/errors.js.map +0 -1
  76. package/dist/entries/field-validation.d.ts.map +0 -1
  77. package/dist/entries/field-validation.js.map +0 -1
  78. package/dist/entries/index.d.ts.map +0 -1
  79. package/dist/entries/index.js.map +0 -1
  80. package/dist/entries/lifecycle-dispatch.d.ts.map +0 -1
  81. package/dist/entries/lifecycle-dispatch.js.map +0 -1
  82. package/dist/entries/list.d.ts.map +0 -1
  83. package/dist/entries/list.js.map +0 -1
  84. package/dist/entries/repo.memory.d.ts.map +0 -1
  85. package/dist/entries/repo.memory.js.map +0 -1
  86. package/dist/entries/tool-registrations.d.ts.map +0 -1
  87. package/dist/entries/tool-registrations.js.map +0 -1
  88. package/dist/entries/types.d.ts.map +0 -1
  89. package/dist/entries/types.js.map +0 -1
  90. package/dist/entries/write-service.d.ts.map +0 -1
  91. package/dist/entries/write-service.js.map +0 -1
  92. package/dist/identity/admin-crud-service.d.ts.map +0 -1
  93. package/dist/identity/admin-crud-service.js.map +0 -1
  94. package/dist/identity/agent-tool-input.d.ts.map +0 -1
  95. package/dist/identity/agent-tool-input.js.map +0 -1
  96. package/dist/identity/agent-tools.d.ts.map +0 -1
  97. package/dist/identity/agent-tools.js.map +0 -1
  98. package/dist/identity/auth-service.d.ts.map +0 -1
  99. package/dist/identity/auth-service.js.map +0 -1
  100. package/dist/identity/authorize.d.ts.map +0 -1
  101. package/dist/identity/authorize.js.map +0 -1
  102. package/dist/identity/grant-service.d.ts.map +0 -1
  103. package/dist/identity/grant-service.js.map +0 -1
  104. package/dist/identity/hasher.d.ts.map +0 -1
  105. package/dist/identity/hasher.js.map +0 -1
  106. package/dist/identity/index.d.ts.map +0 -1
  107. package/dist/identity/index.js.map +0 -1
  108. package/dist/identity/password-policy.d.ts.map +0 -1
  109. package/dist/identity/password-policy.js.map +0 -1
  110. package/dist/identity/permission-migrations.d.ts.map +0 -1
  111. package/dist/identity/permission-migrations.js.map +0 -1
  112. package/dist/identity/permissions.d.ts.map +0 -1
  113. package/dist/identity/permissions.js.map +0 -1
  114. package/dist/identity/ports.d.ts.map +0 -1
  115. package/dist/identity/ports.js.map +0 -1
  116. package/dist/identity/repo.memory.d.ts.map +0 -1
  117. package/dist/identity/repo.memory.js.map +0 -1
  118. package/dist/identity/seed.d.ts.map +0 -1
  119. package/dist/identity/seed.js.map +0 -1
  120. package/dist/identity/tool-registrations.d.ts.map +0 -1
  121. package/dist/identity/tool-registrations.js.map +0 -1
  122. package/dist/identity/types.d.ts.map +0 -1
  123. package/dist/identity/types.js.map +0 -1
  124. package/dist/identity/username.d.ts.map +0 -1
  125. package/dist/identity/username.js.map +0 -1
  126. package/dist/media/agent-tools.d.ts.map +0 -1
  127. package/dist/media/agent-tools.js.map +0 -1
  128. package/dist/media/blob-gc-lock.d.ts.map +0 -1
  129. package/dist/media/blob-gc-lock.js.map +0 -1
  130. package/dist/media/blob-gc.d.ts.map +0 -1
  131. package/dist/media/blob-gc.js.map +0 -1
  132. package/dist/media/blob-key.d.ts.map +0 -1
  133. package/dist/media/blob-key.js.map +0 -1
  134. package/dist/media/blob-store.fs.d.ts.map +0 -1
  135. package/dist/media/blob-store.fs.js.map +0 -1
  136. package/dist/media/blob-store.memory.d.ts.map +0 -1
  137. package/dist/media/blob-store.memory.js.map +0 -1
  138. package/dist/media/content-type-sniffer.d.ts.map +0 -1
  139. package/dist/media/content-type-sniffer.js.map +0 -1
  140. package/dist/media/html-attributes.d.ts.map +0 -1
  141. package/dist/media/html-attributes.js.map +0 -1
  142. package/dist/media/image-transformer.d.ts.map +0 -1
  143. package/dist/media/image-transformer.js.map +0 -1
  144. package/dist/media/image-transformer.sharp.d.ts.map +0 -1
  145. package/dist/media/image-transformer.sharp.js.map +0 -1
  146. package/dist/media/index.d.ts.map +0 -1
  147. package/dist/media/index.js.map +0 -1
  148. package/dist/media/media-service.d.ts.map +0 -1
  149. package/dist/media/media-service.js.map +0 -1
  150. package/dist/media/ports.d.ts.map +0 -1
  151. package/dist/media/ports.js.map +0 -1
  152. package/dist/media/rendition-service.d.ts.map +0 -1
  153. package/dist/media/rendition-service.js.map +0 -1
  154. package/dist/media/repo.memory.d.ts.map +0 -1
  155. package/dist/media/repo.memory.js.map +0 -1
  156. package/dist/media/tool-registrations.d.ts.map +0 -1
  157. package/dist/media/tool-registrations.js.map +0 -1
  158. package/dist/media/transform-lock.d.ts.map +0 -1
  159. package/dist/media/transform-lock.js.map +0 -1
  160. package/dist/media/transform-registry.d.ts.map +0 -1
  161. package/dist/media/transform-registry.js.map +0 -1
  162. package/dist/media/transform-types.d.ts.map +0 -1
  163. package/dist/media/transform-types.js.map +0 -1
  164. package/dist/media/types.d.ts.map +0 -1
  165. package/dist/media/types.js.map +0 -1
  166. package/dist/navigation/agent-tools.d.ts.map +0 -1
  167. package/dist/navigation/agent-tools.js.map +0 -1
  168. package/dist/navigation/contracts.d.ts.map +0 -1
  169. package/dist/navigation/contracts.js.map +0 -1
  170. package/dist/navigation/index.d.ts.map +0 -1
  171. package/dist/navigation/index.js.map +0 -1
  172. package/dist/navigation/menu-service.d.ts.map +0 -1
  173. package/dist/navigation/menu-service.js.map +0 -1
  174. package/dist/navigation/ports.d.ts.map +0 -1
  175. package/dist/navigation/ports.js.map +0 -1
  176. package/dist/navigation/read-model.d.ts.map +0 -1
  177. package/dist/navigation/read-model.js.map +0 -1
  178. package/dist/navigation/reconcile.d.ts.map +0 -1
  179. package/dist/navigation/reconcile.js.map +0 -1
  180. package/dist/navigation/repo.memory.d.ts.map +0 -1
  181. package/dist/navigation/repo.memory.js.map +0 -1
  182. package/dist/navigation/resolver.d.ts.map +0 -1
  183. package/dist/navigation/resolver.js.map +0 -1
  184. package/dist/navigation/tool-registrations.d.ts.map +0 -1
  185. package/dist/navigation/tool-registrations.js.map +0 -1
  186. package/dist/navigation/types.d.ts.map +0 -1
  187. package/dist/navigation/types.js.map +0 -1
  188. package/dist/presentation/index.d.ts.map +0 -1
  189. package/dist/presentation/index.js.map +0 -1
  190. package/dist/presentation/presentation.d.ts.map +0 -1
  191. package/dist/presentation/presentation.js.map +0 -1
  192. package/dist/presentation/repo.memory.d.ts.map +0 -1
  193. package/dist/presentation/repo.memory.js.map +0 -1
  194. package/dist/server/index.d.ts.map +0 -1
  195. package/dist/server/index.js.map +0 -1
  196. package/dist/settings/agent-tools.d.ts.map +0 -1
  197. package/dist/settings/agent-tools.js.map +0 -1
  198. package/dist/settings/agent-writable-preferences.d.ts.map +0 -1
  199. package/dist/settings/agent-writable-preferences.js.map +0 -1
  200. package/dist/settings/change-feed.d.ts.map +0 -1
  201. package/dist/settings/change-feed.js.map +0 -1
  202. package/dist/settings/definitions-dispatch.d.ts.map +0 -1
  203. package/dist/settings/definitions-dispatch.js.map +0 -1
  204. package/dist/settings/dictionaries/index.d.ts.map +0 -1
  205. package/dist/settings/dictionaries/index.js.map +0 -1
  206. package/dist/settings/dictionaries/settings-dialog.ar.d.ts.map +0 -1
  207. package/dist/settings/dictionaries/settings-dialog.ar.js.map +0 -1
  208. package/dist/settings/dictionaries/settings-dialog.bn.d.ts.map +0 -1
  209. package/dist/settings/dictionaries/settings-dialog.bn.js.map +0 -1
  210. package/dist/settings/dictionaries/settings-dialog.de.d.ts.map +0 -1
  211. package/dist/settings/dictionaries/settings-dialog.de.js.map +0 -1
  212. package/dist/settings/dictionaries/settings-dialog.es.d.ts.map +0 -1
  213. package/dist/settings/dictionaries/settings-dialog.es.js.map +0 -1
  214. package/dist/settings/dictionaries/settings-dialog.fa.d.ts.map +0 -1
  215. package/dist/settings/dictionaries/settings-dialog.fa.js.map +0 -1
  216. package/dist/settings/dictionaries/settings-dialog.fr.d.ts.map +0 -1
  217. package/dist/settings/dictionaries/settings-dialog.fr.js.map +0 -1
  218. package/dist/settings/dictionaries/settings-dialog.hi.d.ts.map +0 -1
  219. package/dist/settings/dictionaries/settings-dialog.hi.js.map +0 -1
  220. package/dist/settings/dictionaries/settings-dialog.hu.d.ts.map +0 -1
  221. package/dist/settings/dictionaries/settings-dialog.hu.js.map +0 -1
  222. package/dist/settings/dictionaries/settings-dialog.id.d.ts.map +0 -1
  223. package/dist/settings/dictionaries/settings-dialog.id.js.map +0 -1
  224. package/dist/settings/dictionaries/settings-dialog.it.d.ts.map +0 -1
  225. package/dist/settings/dictionaries/settings-dialog.it.js.map +0 -1
  226. package/dist/settings/dictionaries/settings-dialog.ja.d.ts.map +0 -1
  227. package/dist/settings/dictionaries/settings-dialog.ja.js.map +0 -1
  228. package/dist/settings/dictionaries/settings-dialog.ko.d.ts.map +0 -1
  229. package/dist/settings/dictionaries/settings-dialog.ko.js.map +0 -1
  230. package/dist/settings/dictionaries/settings-dialog.pl.d.ts.map +0 -1
  231. package/dist/settings/dictionaries/settings-dialog.pl.js.map +0 -1
  232. package/dist/settings/dictionaries/settings-dialog.pt-BR.d.ts.map +0 -1
  233. package/dist/settings/dictionaries/settings-dialog.pt-BR.js.map +0 -1
  234. package/dist/settings/dictionaries/settings-dialog.ru.d.ts.map +0 -1
  235. package/dist/settings/dictionaries/settings-dialog.ru.js.map +0 -1
  236. package/dist/settings/dictionaries/settings-dialog.th.d.ts.map +0 -1
  237. package/dist/settings/dictionaries/settings-dialog.th.js.map +0 -1
  238. package/dist/settings/dictionaries/settings-dialog.tr.d.ts.map +0 -1
  239. package/dist/settings/dictionaries/settings-dialog.tr.js.map +0 -1
  240. package/dist/settings/dictionaries/settings-dialog.uk.d.ts.map +0 -1
  241. package/dist/settings/dictionaries/settings-dialog.uk.js.map +0 -1
  242. package/dist/settings/dictionaries/settings-dialog.ur.d.ts.map +0 -1
  243. package/dist/settings/dictionaries/settings-dialog.ur.js.map +0 -1
  244. package/dist/settings/dictionaries/settings-dialog.zh-CN.d.ts.map +0 -1
  245. package/dist/settings/dictionaries/settings-dialog.zh-CN.js.map +0 -1
  246. package/dist/settings/dictionaries/settings-dialog.zh-TW.d.ts.map +0 -1
  247. package/dist/settings/dictionaries/settings-dialog.zh-TW.js.map +0 -1
  248. package/dist/settings/ensure-definitions.d.ts.map +0 -1
  249. package/dist/settings/ensure-definitions.js.map +0 -1
  250. package/dist/settings/errors.d.ts.map +0 -1
  251. package/dist/settings/errors.js.map +0 -1
  252. package/dist/settings/index.d.ts.map +0 -1
  253. package/dist/settings/index.js.map +0 -1
  254. package/dist/settings/ports.d.ts.map +0 -1
  255. package/dist/settings/ports.js.map +0 -1
  256. package/dist/settings/purge-service.d.ts.map +0 -1
  257. package/dist/settings/purge-service.js.map +0 -1
  258. package/dist/settings/repo.memory.d.ts.map +0 -1
  259. package/dist/settings/repo.memory.js.map +0 -1
  260. package/dist/settings/settings.d.ts.map +0 -1
  261. package/dist/settings/settings.js.map +0 -1
  262. package/dist/settings/tool-registrations.d.ts.map +0 -1
  263. package/dist/settings/tool-registrations.js.map +0 -1
  264. package/dist/settings/types.d.ts.map +0 -1
  265. package/dist/settings/types.js.map +0 -1
  266. package/dist/settings/ui-tab-definitions.d.ts.map +0 -1
  267. package/dist/settings/ui-tab-definitions.js.map +0 -1
  268. package/dist/settings/write-service.d.ts.map +0 -1
  269. package/dist/settings/write-service.js.map +0 -1
  270. package/dist/taxonomy/agent-tools.d.ts.map +0 -1
  271. package/dist/taxonomy/agent-tools.js.map +0 -1
  272. package/dist/taxonomy/content-lookup.d.ts.map +0 -1
  273. package/dist/taxonomy/content-lookup.js.map +0 -1
  274. package/dist/taxonomy/index.d.ts.map +0 -1
  275. package/dist/taxonomy/index.js.map +0 -1
  276. package/dist/taxonomy/list.d.ts.map +0 -1
  277. package/dist/taxonomy/list.js.map +0 -1
  278. package/dist/taxonomy/merge-term.d.ts.map +0 -1
  279. package/dist/taxonomy/merge-term.js.map +0 -1
  280. package/dist/taxonomy/repo.memory.d.ts.map +0 -1
  281. package/dist/taxonomy/repo.memory.js.map +0 -1
  282. package/dist/taxonomy/validation-chain.d.ts.map +0 -1
  283. package/dist/taxonomy/validation-chain.js.map +0 -1
  284. package/dist/taxonomy/write-service.d.ts.map +0 -1
  285. package/dist/taxonomy/write-service.js.map +0 -1
  286. package/dist/workspace/agent-tools.d.ts.map +0 -1
  287. package/dist/workspace/agent-tools.js.map +0 -1
  288. package/dist/workspace/create.d.ts.map +0 -1
  289. package/dist/workspace/create.js.map +0 -1
  290. package/dist/workspace/delete.d.ts.map +0 -1
  291. package/dist/workspace/delete.js.map +0 -1
  292. package/dist/workspace/index.d.ts.map +0 -1
  293. package/dist/workspace/index.js.map +0 -1
  294. package/dist/workspace/repo.memory.d.ts.map +0 -1
  295. package/dist/workspace/repo.memory.js.map +0 -1
  296. package/dist/workspace/tool-registrations.d.ts.map +0 -1
  297. package/dist/workspace/tool-registrations.js.map +0 -1
  298. package/dist/workspace/update.d.ts.map +0 -1
  299. package/dist/workspace/update.js.map +0 -1
@@ -83,7 +83,7 @@ export declare function validateIdentifierGrammar(value: string): boolean;
83
83
  *
84
84
  * The PARAMETER type is deliberately the wider `ContentTypeFieldKind`. Narrowing it would be a
85
85
  * breaking change for `@jini-ai/cms`'s public consumers (`content-types/index.ts` documents this
86
- * function as what a host's own DDL provisioner calls, and Tovu re-exports it), and it would also
86
+ * function as what a host's own DDL provisioner calls, and the host re-exports it), and it would also
87
87
  * make the runtime guard below unreachable-looking to a reader while remaining fully reachable to
88
88
  * an untyped JavaScript caller. The runtime gate — not the signature — is what excludes
89
89
  * storage-only kinds.
@@ -113,7 +113,7 @@ const KIND_TO_CAST_LITERAL = {
113
113
  *
114
114
  * The PARAMETER type is deliberately the wider `ContentTypeFieldKind`. Narrowing it would be a
115
115
  * breaking change for `@jini-ai/cms`'s public consumers (`content-types/index.ts` documents this
116
- * function as what a host's own DDL provisioner calls, and Tovu re-exports it), and it would also
116
+ * function as what a host's own DDL provisioner calls, and the host re-exports it), and it would also
117
117
  * make the runtime guard below unreachable-looking to a reader while remaining fully reachable to
118
118
  * an untyped JavaScript caller. The runtime gate — not the signature — is what excludes
119
119
  * storage-only kinds.
@@ -130,7 +130,7 @@ export interface OutboxPort {
130
130
  * attempt cap) — an implementation must persist exactly what it is told, not re-derive the
131
131
  * decision from the row's own stored `attempts` (2026-09-06: this parameter replaced that
132
132
  * adapter-side re-derivation, mirroring `WebhookDeliveryRepoPort.markFailed`'s `nextStatus`
133
- * shape in the Tovu host). `"pending"` re-enters the retry queue at `nextAttemptAt`; `"failed"`
133
+ * shape in the host). `"pending"` re-enters the retry queue at `nextAttemptAt`; `"failed"`
134
134
  * is terminal — `claimPending` only ever selects `"pending"` rows, so a `"failed"` row is
135
135
  * permanently excluded from retry regardless of `nextAttemptAt`.
136
136
  */
@@ -15,7 +15,7 @@ import { type PrincipalRecord, type SessionRecord } from "./types.js";
15
15
  * verifying credentials, and a host that identifies a principal by some other means (e.g. a
16
16
  * loopback-only single-use boot token, which has nothing to verify a password against) calls it
17
17
  * directly instead of re-implementing session construction. Before this export existed, a
18
- * downstream host (Tovu) had duplicated this file's private `hashToken` byte-for-byte because
18
+ * downstream host had duplicated this file's private `hashToken` byte-for-byte because
19
19
  * `login()` was the only minter and hard-requires a password — this closes that fork.
20
20
  *
21
21
  * Architectural role:
@@ -16,7 +16,7 @@ import { normalizeUsername } from "./username.js";
16
16
  * verifying credentials, and a host that identifies a principal by some other means (e.g. a
17
17
  * loopback-only single-use boot token, which has nothing to verify a password against) calls it
18
18
  * directly instead of re-implementing session construction. Before this export existed, a
19
- * downstream host (Tovu) had duplicated this file's private `hashToken` byte-for-byte because
19
+ * downstream host had duplicated this file's private `hashToken` byte-for-byte because
20
20
  * `login()` was the only minter and hard-requires a password — this closes that fork.
21
21
  *
22
22
  * Architectural role:
@@ -34,7 +34,7 @@ export interface SeedIdentityResult {
34
34
  * - A seed that RAN TO COMPLETION is a no-op — the owner-username lookup below early-returns after
35
35
  * a single confirming read (`ensureOwnerRoleBinding`) and writes nothing. (This path does not
36
36
  * reconcile a built-in policy whose permission list has since grown; that is the host's job — see
37
- * Tovu's `applyBuiltinRoleGrants` / `migrateDeprecatedPermissionGrants`, chained onto
37
+ * the host's `applyBuiltinRoleGrants` / `migrateDeprecatedPermissionGrants`, chained onto
38
38
  * `identityReady`.)
39
39
  * - "Ran to completion" is judged on the owner user AND its role link, not the user alone. The link
40
40
  * is the seed's last write, so the user is not on its own evidence the seed finished — see
@@ -149,7 +149,7 @@ const BUILTIN_VIEWER_PERMISSIONS = ["content.read", "changeset.read", "plugin.re
149
149
  /**
150
150
  * Why every step below is "find, then save only what is missing" rather than a straight insert.
151
151
  *
152
- * This function's writes are NOT transactional and its caller is not awaited by its hosts (Tovu
152
+ * This function's writes are NOT transactional and its caller is not awaited by its hosts (one host
153
153
  * hands `seedIdentity`'s promise back as `RouteDeps.identityReady` and returns synchronously), so
154
154
  * a process that exits before the seed finishes — a fast-failing CLI command, a Ctrl-C, a crash —
155
155
  * leaves the workspace half-seeded. `seedIdentity`'s own early-return guard keys on the OWNER USER,
@@ -249,7 +249,7 @@ async function seedBuiltinRoleWithPolicy(required) {
249
249
  * still log in, and holds no role — and `resolveEffectivePermissions` (`authorize.ts`) starts from
250
250
  * `principal_roles` + `principal_policies`, so with neither present EVERY permission evaluates to
251
251
  * `no_grant`. Without this step the guard early-returns on the user it finds forever and the site
252
- * stays bricked for its owner, silently. Neither host-side reconciler closes it either: Tovu's
252
+ * stays bricked for its owner, silently. Neither host-side reconciler closes it either: the host's
253
253
  * `migrateDeprecatedPermissionGrants` and `applyBuiltinRoleGrants` are handed policy and role repos
254
254
  * only, never `principalRoles`, so neither can write a principal->role link at all.
255
255
  *
@@ -296,7 +296,7 @@ async function ensureOwnerRoleBinding(required) {
296
296
  * - A seed that RAN TO COMPLETION is a no-op — the owner-username lookup below early-returns after
297
297
  * a single confirming read (`ensureOwnerRoleBinding`) and writes nothing. (This path does not
298
298
  * reconcile a built-in policy whose permission list has since grown; that is the host's job — see
299
- * Tovu's `applyBuiltinRoleGrants` / `migrateDeprecatedPermissionGrants`, chained onto
299
+ * the host's `applyBuiltinRoleGrants` / `migrateDeprecatedPermissionGrants`, chained onto
300
300
  * `identityReady`.)
301
301
  * - "Ran to completion" is judged on the owner user AND its role link, not the user alone. The link
302
302
  * is the seed's last write, so the user is not on its own evidence the seed finished — see
@@ -34,9 +34,19 @@
34
34
  * content-types pass-through-`ToolPolicy` reasoning, not an oversight.
35
35
  *
36
36
  * Architectural role:
37
- * `media` domain declaration. Imports only the constants its own domain already enforces
38
- * (`DEFAULT_MAX_UPLOAD_BYTES`/`DEFAULT_ALLOWED_MIME_TYPES` from `media-service.ts`), so the
39
- * published JSON Schemas cannot drift from the validators.
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.
40
50
  */
41
51
  export type AgentToolSideEffect = "none" | "mutates-durable-state" | "mints-token";
42
52
  export type AgentToolActorClassRule = "confirmer-must-equal-own-delegatedBy" | "user-only" | "none";
@@ -34,11 +34,22 @@
34
34
  * content-types pass-through-`ToolPolicy` reasoning, not an oversight.
35
35
  *
36
36
  * Architectural role:
37
- * `media` domain declaration. Imports only the constants its own domain already enforces
38
- * (`DEFAULT_MAX_UPLOAD_BYTES`/`DEFAULT_ALLOWED_MIME_TYPES` from `media-service.ts`), so the
39
- * published JSON Schemas cannot drift from the validators.
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.
40
50
  */
41
- import { DEFAULT_ALLOWED_MIME_TYPES, DEFAULT_MAX_UPLOAD_BYTES } from "./media-service.js";
51
+ import { MEDIA_HTML_ATTRIBUTE_ALLOWED_NAMES } from "./html-attributes.js";
52
+ import { DEFAULT_ALLOWED_MIME_TYPES } from "./media-service.js";
42
53
  const MEDIA_ID_SCHEMA = {
43
54
  type: "string",
44
55
  description: "The media asset's id, as returned by media_upload_asset or media_list_assets.",
@@ -54,7 +65,7 @@ const MEDIA_ID_SCHEMA = {
54
65
  export const mediaAgentToolCatalog = [
55
66
  {
56
67
  name: "media_list_assets",
57
- description: "Lists every media asset in the workspace (all statuses — active and trashed), with id, title, alt, caption, credit, sha256, and status. Read-only. Call this to find a mediaId before updating or trashing an asset.",
68
+ description: "Lists every media asset in the workspace (all statuses — active and trashed), with id, slug, title, alt, caption, credit, sha256, and status. Read-only. Call this to find a mediaId before updating or trashing an asset.",
58
69
  sideEffects: "none",
59
70
  authorization: { permission: "media.read" },
60
71
  inputSchema: {
@@ -68,8 +79,9 @@ export const mediaAgentToolCatalog = [
68
79
  name: "media_upload_asset",
69
80
  description: `Uploads a new media asset from base64-encoded bytes. Rejects a content type outside the allowed set ` +
70
81
  `(${[...DEFAULT_ALLOWED_MIME_TYPES].join(", ")} — SVG is never accepted, even here: it requires a sanitizer ` +
71
- `this build does not have) or a file over ${DEFAULT_MAX_UPLOAD_BYTES} bytes. Uploading the same bytes twice ` +
72
- `always creates two separate library entries (the underlying blob is deduplicated, but each upload is its own asset).`,
82
+ `this build does not have) or a file over this host's configured size cap (a rejected upload's error ` +
83
+ `states the exact limit, in MB). Uploading the same bytes twice always creates two separate library ` +
84
+ `entries (the underlying blob is deduplicated, but each upload is its own asset).`,
73
85
  sideEffects: "mutates-durable-state",
74
86
  authorization: { permission: "media.upload" },
75
87
  inputSchema: {
@@ -86,7 +98,7 @@ export const mediaAgentToolCatalog = [
86
98
  dataBase64: {
87
99
  type: "string",
88
100
  minLength: 1,
89
- description: `The file's bytes, base64-encoded. Decoded size must be over 0 and at most ${DEFAULT_MAX_UPLOAD_BYTES} bytes.`,
101
+ description: "The file's bytes, base64-encoded. Decoded size must be over 0 bytes and within this host's configured upload size cap (a too-large upload's error states the exact limit, in MB).",
90
102
  },
91
103
  alt: { type: "string", description: "Optional accessibility alt text." },
92
104
  caption: { type: "string", description: "Optional display caption." },
@@ -96,8 +108,11 @@ export const mediaAgentToolCatalog = [
96
108
  },
97
109
  {
98
110
  name: "media_update_metadata",
99
- description: "Updates a media asset's editorial metadata (title/alt/caption/credit only). The asset's underlying bytes " +
100
- "(sha256) are write-once and cannot be changed by this or any tool — to replace the file itself, upload a new asset.",
111
+ description: "Updates a media asset's metadata: title, alt, caption, credit, and its asset-wide presentation (cssClass, " +
112
+ "htmlAttributes). Omit a field to leave it unchanged. htmlAttributes/cssClass apply EVERYWHERE this asset " +
113
+ "renders; to change one placement on one page only, put the attribute on that page's data-embed-config " +
114
+ "marker element instead (pages_write_region). The bytes (sha256) are write-once and cannot be changed by " +
115
+ "this or any tool — to replace the file itself, upload a new asset.",
101
116
  sideEffects: "mutates-durable-state",
102
117
  authorization: { permission: "media.update" },
103
118
  inputSchema: {
@@ -110,6 +125,26 @@ export const mediaAgentToolCatalog = [
110
125
  alt: { type: "string", description: "New alt text. Omit to leave unchanged." },
111
126
  caption: { type: "string", description: "New caption. Omit to leave unchanged." },
112
127
  credit: { type: "string", description: "New credit line. Omit to leave unchanged." },
128
+ slug: {
129
+ type: "string",
130
+ description: "New slug: lowercase letters, numbers and dashes, not UUID-shaped, unique in the workspace (a slug " +
131
+ "another asset uses is refused and nothing is written). Changing it breaks any page marker that " +
132
+ "references the OLD slug.",
133
+ },
134
+ cssClass: {
135
+ type: "string",
136
+ description: "Space-separated class names added to the rendered <img>/<video> tag everywhere this asset renders. " +
137
+ "Replaces the stored value; empty string clears it.",
138
+ },
139
+ htmlAttributes: {
140
+ type: "string",
141
+ description: "Extra attributes for the rendered <img>/<video> tag, written as HTML attribute text, e.g. " +
142
+ '`autoplay muted loop playsinline` or `loading="eager" data-motion="fade"`. REPLACES the whole ' +
143
+ "stored value: read the current value first and keep what should stay. Empty string clears it. " +
144
+ `Allowed names only: ${MEDIA_HTML_ATTRIBUTE_ALLOWED_NAMES.join(", ")}, plus any data-* or aria-*. ` +
145
+ "Anything else (on* handlers, javascript: values, style, class) is rejected and NOTHING is written. " +
146
+ "Browsers autoplay only video that is also muted (and playsinline on iPhone).",
147
+ },
113
148
  },
114
149
  },
115
150
  },
@@ -1,17 +1,17 @@
1
1
  /**
2
2
  * @file Server-side allowlist for `MediaRecord.htmlAttributes` (2026-09-07) — the enforcement half
3
- * of a validator that ALSO has a browser-side copy in Tovu's admin
3
+ * of a validator that ALSO has a browser-side copy in the consuming product's admin
4
4
  * (`apps/admin/src/features/media/rules.ts`, `parseMediaHtmlAttributes` and friends).
5
5
  *
6
6
  * Deliberately duplicated, not shared via one imported module, for the same reason this codebase
7
- * already hand-copies `DEFAULT_ALLOWED_MIME_TYPES` into Tovu's `FILE_HANDLER_ALLOWED_MIME_TYPES`/
7
+ * already hand-copies `DEFAULT_ALLOWED_MIME_TYPES` into that admin's `FILE_HANDLER_ALLOWED_MIME_TYPES`/
8
8
  * `IMPORTABLE_CONTENT_TYPES`: `@jini-ai/cms/media`'s barrel (`index.ts`) re-exports files that
9
9
  * import real Node built-ins (`node:crypto` in `media-service.ts`, `node:fs` in
10
10
  * `blob-store.fs.ts`, native `sharp` bindings in `image-transformer.sharp.ts`) — importing this
11
- * subpath from a browser-bundled Vite app (Tovu's `apps/admin`) risks pulling those into the admin
11
+ * subpath from a browser-bundled Vite app (that product's `apps/admin`) risks pulling those into the admin
12
12
  * SPA's bundle even if only this one pure function is actually used, and this admin's own copy
13
13
  * already exists, tested, with no such risk. This file is the copy every Node-side consumer
14
- * (`media-service.ts`'s own `updateMediaMetadata`, and Tovu's `apps/website` render path, both pure
14
+ * (`media-service.ts`'s own `updateMediaMetadata`, and that product's `apps/website` render path, both pure
15
15
  * backend code) can safely import from `@jini-ai/cms/media` instead of re-deriving the rules a
16
16
  * third time.
17
17
  *
@@ -21,7 +21,7 @@
21
21
  * needed — that is the one piece of drift risk this duplication accepts, the same risk every other
22
22
  * hand-copied list in this codebase already accepts.
23
23
  *
24
- * This is a SECURITY boundary, not a syntax convenience: media metadata is authored in Tovu's admin
24
+ * This is a SECURITY boundary, not a syntax convenience: media metadata is authored in the consuming product's admin
25
25
  * but rendered on the public site, so a free-text HTML-attribute passthrough is a stored-XSS vector
26
26
  * (`onerror`, `onclick`, `style`, `href="javascript:"`, any `on*` handler) the moment it reaches a
27
27
  * public page. A client-side-only check is not a control, since the API accepts whatever a caller
@@ -33,7 +33,7 @@
33
33
  * families (checked separately in {@link isAllowedMediaHtmlAttributeName}). Picked for the owner's
34
34
  * stated near-term uses (animations, custom WebMCP hooks) plus the standard `<img>`/`<video>`
35
35
  * attributes those uses actually need; extend this list, not the parser, when a new one is needed —
36
- * and keep it in sync with the identical constant in Tovu's admin copy (see this file's header).
36
+ * and keep it in sync with the identical constant in that admin's copy (see this file's header).
37
37
  */
38
38
  export declare const MEDIA_HTML_ATTRIBUTE_ALLOWED_NAMES: readonly ["loading", "decoding", "playsinline", "muted", "loop", "autoplay", "poster"];
39
39
  /** Whether `name` is on the allowlist — a well-formed attribute name
@@ -1,17 +1,17 @@
1
1
  /**
2
2
  * @file Server-side allowlist for `MediaRecord.htmlAttributes` (2026-09-07) — the enforcement half
3
- * of a validator that ALSO has a browser-side copy in Tovu's admin
3
+ * of a validator that ALSO has a browser-side copy in the consuming product's admin
4
4
  * (`apps/admin/src/features/media/rules.ts`, `parseMediaHtmlAttributes` and friends).
5
5
  *
6
6
  * Deliberately duplicated, not shared via one imported module, for the same reason this codebase
7
- * already hand-copies `DEFAULT_ALLOWED_MIME_TYPES` into Tovu's `FILE_HANDLER_ALLOWED_MIME_TYPES`/
7
+ * already hand-copies `DEFAULT_ALLOWED_MIME_TYPES` into that admin's `FILE_HANDLER_ALLOWED_MIME_TYPES`/
8
8
  * `IMPORTABLE_CONTENT_TYPES`: `@jini-ai/cms/media`'s barrel (`index.ts`) re-exports files that
9
9
  * import real Node built-ins (`node:crypto` in `media-service.ts`, `node:fs` in
10
10
  * `blob-store.fs.ts`, native `sharp` bindings in `image-transformer.sharp.ts`) — importing this
11
- * subpath from a browser-bundled Vite app (Tovu's `apps/admin`) risks pulling those into the admin
11
+ * subpath from a browser-bundled Vite app (that product's `apps/admin`) risks pulling those into the admin
12
12
  * SPA's bundle even if only this one pure function is actually used, and this admin's own copy
13
13
  * already exists, tested, with no such risk. This file is the copy every Node-side consumer
14
- * (`media-service.ts`'s own `updateMediaMetadata`, and Tovu's `apps/website` render path, both pure
14
+ * (`media-service.ts`'s own `updateMediaMetadata`, and that product's `apps/website` render path, both pure
15
15
  * backend code) can safely import from `@jini-ai/cms/media` instead of re-deriving the rules a
16
16
  * third time.
17
17
  *
@@ -21,7 +21,7 @@
21
21
  * needed — that is the one piece of drift risk this duplication accepts, the same risk every other
22
22
  * hand-copied list in this codebase already accepts.
23
23
  *
24
- * This is a SECURITY boundary, not a syntax convenience: media metadata is authored in Tovu's admin
24
+ * This is a SECURITY boundary, not a syntax convenience: media metadata is authored in the consuming product's admin
25
25
  * but rendered on the public site, so a free-text HTML-attribute passthrough is a stored-XSS vector
26
26
  * (`onerror`, `onclick`, `style`, `href="javascript:"`, any `on*` handler) the moment it reaches a
27
27
  * public page. A client-side-only check is not a control, since the API accepts whatever a caller
@@ -33,7 +33,7 @@
33
33
  * families (checked separately in {@link isAllowedMediaHtmlAttributeName}). Picked for the owner's
34
34
  * stated near-term uses (animations, custom WebMCP hooks) plus the standard `<img>`/`<video>`
35
35
  * attributes those uses actually need; extend this list, not the parser, when a new one is needed —
36
- * and keep it in sync with the identical constant in Tovu's admin copy (see this file's header).
36
+ * and keep it in sync with the identical constant in that admin's copy (see this file's header).
37
37
  */
38
38
  export const MEDIA_HTML_ATTRIBUTE_ALLOWED_NAMES = [
39
39
  "loading",
@@ -21,7 +21,7 @@ export { withSha256Lock } from "./blob-gc-lock.js";
21
21
  export { DEFAULT_GC_GRACE_MS, resolveGcGraceMs, isBlobUnreferenced, tombstoneBlobIfUnreferenced, runBlobGcDeletePass, runBlobGcUnlinkPass, runBlobGcCycle, runMonthlyOrphanSweepStub, } from "./blob-gc.js";
22
22
  export { InMemoryBlobStore } from "./blob-store.memory.js";
23
23
  export { LocalFsBlobStore, type LocalFsBlobStoreDeps } from "./blob-store.fs.js";
24
- export { DEFAULT_MAX_UPLOAD_BYTES, DEFAULT_ALLOWED_MIME_TYPES, resolveWriteOnceSource, uploadMedia, listMedia, getMediaById, findMediaByIdOrSlug, updateMediaMetadata, trashMedia, purgeMedia, type UploadMediaInput, type UploadMediaDeps, type UpdateMediaMetadataInput, type FindMediaByIdOrSlugRequired, } from "./media-service.js";
24
+ export { DEFAULT_MAX_UPLOAD_BYTES, DEFAULT_ALLOWED_MIME_TYPES, resolveWriteOnceSource, uploadMedia, listMedia, getMediaById, findMediaByIdOrSlug, updateMediaMetadata, trashMedia, purgeMedia, isValidMediaSlugFormat, type UploadMediaInput, type UploadMediaDeps, type UpdateMediaMetadataInput, type FindMediaByIdOrSlugRequired, } from "./media-service.js";
25
25
  export type { TransformFit, TransformFormat, TransformParams, TransformDefinitionRecord, } from "./transform-types.js";
26
26
  export { TransformValidationError, mimeForTransformFormat, MAX_TRANSFORM_DIMENSION_PX } from "./transform-types.js";
27
27
  export { withRenditionLock, withTransformRegistryLock } from "./transform-lock.js";
@@ -6,7 +6,7 @@ export { withSha256Lock } from "./blob-gc-lock.js";
6
6
  export { DEFAULT_GC_GRACE_MS, resolveGcGraceMs, isBlobUnreferenced, tombstoneBlobIfUnreferenced, runBlobGcDeletePass, runBlobGcUnlinkPass, runBlobGcCycle, runMonthlyOrphanSweepStub, } from "./blob-gc.js";
7
7
  export { InMemoryBlobStore } from "./blob-store.memory.js";
8
8
  export { LocalFsBlobStore } from "./blob-store.fs.js";
9
- export { DEFAULT_MAX_UPLOAD_BYTES, DEFAULT_ALLOWED_MIME_TYPES, resolveWriteOnceSource, uploadMedia, listMedia, getMediaById, findMediaByIdOrSlug, updateMediaMetadata, trashMedia, purgeMedia, } from "./media-service.js";
9
+ export { DEFAULT_MAX_UPLOAD_BYTES, DEFAULT_ALLOWED_MIME_TYPES, resolveWriteOnceSource, uploadMedia, listMedia, getMediaById, findMediaByIdOrSlug, updateMediaMetadata, trashMedia, purgeMedia, isValidMediaSlugFormat, } from "./media-service.js";
10
10
  export { TransformValidationError, mimeForTransformFormat, MAX_TRANSFORM_DIMENSION_PX } from "./transform-types.js";
11
11
  export { withRenditionLock, withTransformRegistryLock } from "./transform-lock.js";
12
12
  export { registerTransform, getLatestTransformDefinition, isLatestTransformVersion, isReferencedByPublishedContent, } from "./transform-registry.js";
@@ -23,7 +23,7 @@ export declare const DEFAULT_MAX_UPLOAD_BYTES: number;
23
23
  * image types above, an accepted video is never re-encoded — there is no
24
24
  * `TransformFormat` for video (`transform-types.ts`'s union is image-only), so
25
25
  * a video asset's public URL bypasses the transform/rendition pipeline
26
- * entirely and serves the original bytes as-is (host concern; see Tovu's
26
+ * entirely and serves the original bytes as-is (host concern; see the host's
27
27
  * `routes/site/media-rendition.ts`). This widens the SAME advisory,
28
28
  * client-declared-string check item 4 above already discloses as untrusted —
29
29
  * no new ingress hardening was added for video.
@@ -49,6 +49,7 @@ export declare function resolveWriteOnceSource(required: {
49
49
  existing: MediaSource | undefined;
50
50
  requestedSha256: string;
51
51
  }, _optional?: Record<string, never>): MediaSource;
52
+ export declare function isValidMediaSlugFormat(slug: string): boolean;
52
53
  export interface UploadMediaInput {
53
54
  workspaceId: UUID;
54
55
  bytes: Uint8Array;
@@ -58,6 +58,22 @@ import { describeMediaHtmlAttributeError, parseMediaHtmlAttributes } from "./htm
58
58
  export { MediaConflictError, MediaNotFoundError, MediaSourceImmutableError, MediaStillReferencedError, MediaValidationError, };
59
59
  /** Default byte-size cap (this pass's stand-in for the real ingress policy's streaming cap). */
60
60
  export const DEFAULT_MAX_UPLOAD_BYTES = 10 * 1024 * 1024; // 10 MiB
61
+ /**
62
+ * Renders a byte count as a human-readable MB figure for the size-cap rejection below (2026-09-21).
63
+ * Every real caller passes a MiB-scale `maxUploadBytes` (10 MiB default, or a host override such as
64
+ * Tovu's 50 MiB), so one decimal place is enough precision without ever showing a raw byte count —
65
+ * the whole point of this fix (see `uploadMedia`'s size-cap check, and `agent-tools.ts`'s file header
66
+ * for why the catalog description can no longer state a specific number either). `toFixed(1)`'s
67
+ * trailing `.0` is trimmed so a clean multiple of 1 MB (the common case) reads as `10 MB`, not `10.0
68
+ * MB`.
69
+ *
70
+ * @complexity O(1).
71
+ */
72
+ function formatMaxUploadBytesAsMb(bytes) {
73
+ const mb = bytes / (1024 * 1024);
74
+ const rounded = mb.toFixed(1);
75
+ return `${rounded.endsWith(".0") ? rounded.slice(0, -2) : rounded} MB`;
76
+ }
61
77
  /**
62
78
  * Advisory MIME allowlist. SVG is deliberately EXCLUDED (not "TODO, forgot") —
63
79
  * SVG must be sanitized at ingest before it's safe to store;
@@ -77,7 +93,7 @@ export const DEFAULT_MAX_UPLOAD_BYTES = 10 * 1024 * 1024; // 10 MiB
77
93
  * image types above, an accepted video is never re-encoded — there is no
78
94
  * `TransformFormat` for video (`transform-types.ts`'s union is image-only), so
79
95
  * a video asset's public URL bypasses the transform/rendition pipeline
80
- * entirely and serves the original bytes as-is (host concern; see Tovu's
96
+ * entirely and serves the original bytes as-is (host concern; see the host's
81
97
  * `routes/site/media-rendition.ts`). This widens the SAME advisory,
82
98
  * client-declared-string check item 4 above already discloses as untrusted —
83
99
  * no new ingress hardening was added for video.
@@ -150,7 +166,7 @@ const MEDIA_SLUG_UUID_SHAPE_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9
150
166
  * human for a shorter value — silently truncating an internally-derived value is the same
151
167
  * "coerce, don't fail" convention `deriveTitleFromFilename`/`slugifyMediaTitle` already use. */
152
168
  const MEDIA_MAX_SLUG_LENGTH = 120;
153
- function isValidMediaSlugFormat(slug) {
169
+ export function isValidMediaSlugFormat(slug) {
154
170
  return MEDIA_SLUG_FORMAT_PATTERN.test(slug) && !MEDIA_SLUG_UUID_SHAPE_PATTERN.test(slug);
155
171
  }
156
172
  /** Truncates a slug candidate to `maxLength`, stripping a trailing dash the cut can introduce (so a
@@ -165,9 +181,9 @@ function capSlugCandidate(candidate, maxLength) {
165
181
  }
166
182
  /** Turns free text into a slug candidate: lowercase, non-alphanumeric runs collapsed to one dash,
167
183
  * leading/trailing dashes trimmed. Same algorithm as `post.ts`'s private `slugify` (duplicated, not
168
- * imported — cross-repo: `post.ts` lives in Tovu, this file in `@jini-ai/cms`; this codebase's own
184
+ * imported — cross-repo: `post.ts` lives in the host, this file in `@jini-ai/cms`; this codebase's own
169
185
  * precedent for a small hand-copied helper mirrored across a repo boundary is `DEFAULT_ALLOWED_MIME_TYPES`
170
- * vs. Tovu's `FILE_HANDLER_ALLOWED_MIME_TYPES`/`IMPORTABLE_CONTENT_TYPES`). Never throws and never
186
+ * vs. the host's `FILE_HANDLER_ALLOWED_MIME_TYPES`/`IMPORTABLE_CONTENT_TYPES`). Never throws and never
171
187
  * returns `undefined` — an all-punctuation input collapses to `""`, which every caller here treats
172
188
  * as "derive nothing, fall back to a fixed default" rather than a malformed-input error. */
173
189
  function slugifyMediaTitle(title) {
@@ -181,12 +197,12 @@ function slugifyMediaTitle(title) {
181
197
  * Derives a unique-per-workspace slug from `title`, suffixing `-2`, `-3`, … on collision — the
182
198
  * identical loop shape `post.ts`'s `createPost` uses for its own derived-slug path. Shared by
183
199
  * {@link uploadMedia} (derive-on-create) and the backfill a host runs once for pre-existing rows
184
- * with no slug yet (see Tovu's `development/scripts/backfill-media-slugs.ts`).
200
+ * with no slug yet (see the host's `development/scripts/backfill-media-slugs.ts`).
185
201
  *
186
202
  * `base` falls back to `"untitled"` when `title` slugifies to the empty string (all-punctuation or
187
203
  * non-Latin titles that `slugifyMediaTitle` strips to nothing) — `uploadMedia`'s own title is never
188
204
  * empty (`deriveTitleFromFilename` guarantees a non-empty string), but the backfill script
189
- * (`development/scripts/backfill-media-slugs.ts`, Tovu) calls this against arbitrary pre-existing
205
+ * (`development/scripts/backfill-media-slugs.ts`, host-side) calls this against arbitrary pre-existing
190
206
  * titles, so this fallback is load-bearing there, not just a defensive floor. Note this is the SAME
191
207
  * fallback base every empty-slugifying title collapses onto, so two such titles in one workspace
192
208
  * collide on `"untitled"` and are disambiguated by the ordinary suffix loop below exactly like any
@@ -248,7 +264,7 @@ export async function uploadMedia(required, optional = {}) {
248
264
  throw new MediaValidationError("uploaded file is empty");
249
265
  }
250
266
  if (input.bytes.byteLength > maxUploadBytes) {
251
- throw new MediaValidationError(`uploaded file exceeds the ${maxUploadBytes}-byte size cap`);
267
+ throw new MediaValidationError(`uploaded file exceeds the ${formatMaxUploadBytesAsMb(maxUploadBytes)} size cap`);
252
268
  }
253
269
  const sha256 = createHash("sha256").update(input.bytes).digest("hex");
254
270
  const nowIso = deps.clock.nowIso();
@@ -92,6 +92,18 @@ export interface MediaToolDeps {
92
92
  media: MediaRecord;
93
93
  bytes: Uint8Array;
94
94
  }) => Promise<void>;
95
+ /**
96
+ * Optional host-supplied override of `uploadMedia`'s own `DEFAULT_MAX_UPLOAD_BYTES` (10 MiB,
97
+ * `media-service.ts`), forwarded verbatim as `uploadMedia`'s third (`optional`) argument in the
98
+ * `media_upload_asset` handler below (2026-09-21). Before this field existed, the handler called
99
+ * `uploadMedia({ deps, input })` with no third argument at all, so EVERY host's assistant upload
100
+ * tool was hard-capped at 10 MiB regardless of what limit that host's own HTTP upload route
101
+ * enforced — a host raising its own cap (e.g. to 50 MiB) had no way to raise this tool's cap to
102
+ * match. Omitted: `uploadMedia`'s own default applies unchanged, matching this field's own
103
+ * pre-2026-09-21 absence for every host that has not opted in yet — the same degrade-soft
104
+ * contract {@link resolvePublicUrls} and {@link recordUploadContentType} already established.
105
+ */
106
+ maxUploadBytes?: number;
95
107
  }
96
108
  /**
97
109
  * This wiring layer's OWN risk classification, authored from what each handler below actually
@@ -30,6 +30,7 @@ function isMediaShapeRejection(error) {
30
30
  function toMediaToolView(record) {
31
31
  return {
32
32
  id: record.id,
33
+ slug: record.slug,
33
34
  title: record.title,
34
35
  alt: record.alt,
35
36
  caption: record.caption,
@@ -37,6 +38,8 @@ function toMediaToolView(record) {
37
38
  sha256: record.source.sha256,
38
39
  status: record.status,
39
40
  version: record.version,
41
+ cssClass: record.cssClass,
42
+ htmlAttributes: record.htmlAttributes,
40
43
  };
41
44
  }
42
45
  /**
@@ -92,7 +95,7 @@ export function buildMediaRegistrations(routeDeps) {
92
95
  credit: typeof input.credit === "string" ? input.credit : undefined,
93
96
  createdByPrincipal: ctx.principal.id,
94
97
  },
95
- });
98
+ }, { maxUploadBytes: routeDeps.maxUploadBytes });
96
99
  if (routeDeps.recordUploadContentType) {
97
100
  try {
98
101
  await routeDeps.recordUploadContentType({ media, bytes });
@@ -121,18 +124,23 @@ export function buildMediaRegistrations(routeDeps) {
121
124
  const input = requireInputRecord(ctx.input);
122
125
  const mediaId = requireString(input, "mediaId");
123
126
  await requireToolPermission(routeDeps, { principalId: ctx.principal.id, permission: "media.update", entityType: "media", entityId: mediaId });
124
- const { media } = await updateMediaMetadata({
125
- deps: { clock: routeDeps.clock, mediaRepo: routeDeps.mediaRepo },
126
- input: {
127
- workspaceId: routeDeps.workspaceId,
128
- id: mediaId,
129
- title: typeof input.title === "string" ? input.title : undefined,
130
- alt: typeof input.alt === "string" ? input.alt : undefined,
131
- caption: typeof input.caption === "string" ? input.caption : undefined,
132
- credit: typeof input.credit === "string" ? input.credit : undefined,
133
- },
127
+ return withSchemaOnRejection({ toolId: "media_update_metadata", catalog: CATALOG_BY_ID, isShapeRejection: isMediaShapeRejection }, async () => {
128
+ const { media } = await updateMediaMetadata({
129
+ deps: { clock: routeDeps.clock, mediaRepo: routeDeps.mediaRepo },
130
+ input: {
131
+ workspaceId: routeDeps.workspaceId,
132
+ id: mediaId,
133
+ title: typeof input.title === "string" ? input.title : undefined,
134
+ alt: typeof input.alt === "string" ? input.alt : undefined,
135
+ caption: typeof input.caption === "string" ? input.caption : undefined,
136
+ credit: typeof input.credit === "string" ? input.credit : undefined,
137
+ slug: typeof input.slug === "string" ? input.slug : undefined,
138
+ cssClass: typeof input.cssClass === "string" ? input.cssClass : undefined,
139
+ htmlAttributes: typeof input.htmlAttributes === "string" ? input.htmlAttributes : undefined,
140
+ },
141
+ });
142
+ return { media: toMediaToolView(media) };
134
143
  });
135
- return { media: toMediaToolView(media) };
136
144
  },
137
145
  media_trash_asset: async (ctx) => {
138
146
  const mediaId = requireString(requireInputRecord(ctx.input), "mediaId");
@@ -50,7 +50,7 @@ export interface MediaRecord {
50
50
  *
51
51
  * Derived from `title` at upload time (`deriveMediaSlug`), then independently editable afterward
52
52
  * — renaming `title` does NOT recompute this field; it changes only on an explicit `slug` write.
53
- * Unique per `(workspaceId, slug)`, enforced by the host's own DB index (see Tovu's
53
+ * Unique per `(workspaceId, slug)`, enforced by the host's own DB index (see the host's
54
54
  * `idx_media_workspace_slug`) — `updateMediaMetadata`'s own `findBySlug` check is a friendly-error
55
55
  * courtesy on top of that, not the enforcement itself, the same split `posts_workspace_slug_unique`
56
56
  * already establishes for `post`'s identical `slug` field.
@@ -20,10 +20,10 @@ export { InMemoryMenuRepo, InMemoryNavLocationBindingRepo, type MenuRepoPort, }
20
20
  * `isAllowedHref`/`ALLOWED_HREF_SHAPES_DESCRIPTION` (2026-09-03): the canonical author-link
21
21
  * allowlist, promoted here specifically so a HOST can import it rather than hand-copy it — the
22
22
  * dependency direction a host -> `@jini-ai/cms` is the one that is actually legal (the reverse is
23
- * not). Tovu's own render-time `safeHref` (`server/inbound/public-http/http/site/render.ts` and its
23
+ * not). The host's own render-time `safeHref` (`server/inbound/public-http/http/site/render.ts` and its
24
24
  * `features/theme/static-render.ts` duplicate) have not yet been migrated to import this — see
25
25
  * `menu-service.ts`'s own doc comment on `isAllowedHref` for the retirement plan and why nothing in
26
- * `check:boundaries` blocks either Tovu call site from doing so.
26
+ * `check:boundaries` blocks either host-side call site from doing so.
27
27
  */
28
28
  export { createMenu, updateMenuTree, assignLocation, deleteMenu, validateAndCloneTree, isAllowedHref, ALLOWED_HREF_SHAPES_DESCRIPTION, MenuNotFoundError, MenuValidationError, MenuConflictError, MenuLocationBoundError, DEFAULT_MAX_TREE_DEPTH, DEFAULT_MAX_ITEM_COUNT, type TreeValidationLimits, type CreateMenuDeps, type CreateMenuServiceInput, type CreateMenuRequired, type CreateMenuOptional, type UpdateMenuTreeDeps, type UpdateMenuTreeServiceInput, type UpdateMenuTreeRequired, type UpdateMenuTreeOptional, type AssignLocationDeps, type AssignLocationServiceInput, type AssignLocationRequired, type AssignLocationOptional, type DeleteMenuDeps, type DeleteMenuServiceInput, type DeleteMenuRequired, type DeleteMenuOptional, } from "./menu-service.js";
29
29
  export { resolveForLocation, resolveMenuDoc, type ResolveTargetHrefFn, type ResolvedTargetHref, type ResolveForLocationDeps, type ResolveForLocationServiceInput, type ResolveForLocationRequired, type ResolveForLocationOptional, } from "./resolver.js";
@@ -11,10 +11,10 @@ export { InMemoryMenuRepo, InMemoryNavLocationBindingRepo, } from "./repo.memory
11
11
  * `isAllowedHref`/`ALLOWED_HREF_SHAPES_DESCRIPTION` (2026-09-03): the canonical author-link
12
12
  * allowlist, promoted here specifically so a HOST can import it rather than hand-copy it — the
13
13
  * dependency direction a host -> `@jini-ai/cms` is the one that is actually legal (the reverse is
14
- * not). Tovu's own render-time `safeHref` (`server/inbound/public-http/http/site/render.ts` and its
14
+ * not). The host's own render-time `safeHref` (`server/inbound/public-http/http/site/render.ts` and its
15
15
  * `features/theme/static-render.ts` duplicate) have not yet been migrated to import this — see
16
16
  * `menu-service.ts`'s own doc comment on `isAllowedHref` for the retirement plan and why nothing in
17
- * `check:boundaries` blocks either Tovu call site from doing so.
17
+ * `check:boundaries` blocks either host-side call site from doing so.
18
18
  */
19
19
  export { createMenu, updateMenuTree, assignLocation, deleteMenu, validateAndCloneTree, isAllowedHref, ALLOWED_HREF_SHAPES_DESCRIPTION, MenuNotFoundError, MenuValidationError, MenuConflictError, MenuLocationBoundError, DEFAULT_MAX_TREE_DEPTH, DEFAULT_MAX_ITEM_COUNT, } from "./menu-service.js";
20
20
  export { resolveForLocation, resolveMenuDoc, } from "./resolver.js";
@@ -48,13 +48,13 @@ export declare const DEFAULT_MAX_TREE_DEPTH = 5;
48
48
  export declare const DEFAULT_MAX_ITEM_COUNT = 500;
49
49
  /**
50
50
  * The CANONICAL href allowlist for an author-authored link — same accepted shapes (`#…`, same-origin
51
- * `/…`, `http(s)://`, `mailto:`) as Tovu's render-time `safeHref` in `render.ts` and its
51
+ * `/…`, `http(s)://`, `mailto:`) as the host's render-time `safeHref` in `render.ts` and its
52
52
  * `features/theme/static-render.ts` duplicate. **This is now the single source of truth**: exported
53
53
  * from this package's public surface (`navigation/index.ts` -> `@jini-ai/cms/navigation`) so the
54
- * dependency runs the direction that is actually legal — Tovu already depends on `@jini-ai/cms`, not
54
+ * dependency runs the direction that is actually legal — the host already depends on `@jini-ai/cms`, not
55
55
  * the reverse — rather than each consumer hand-copying the predicate.
56
56
  *
57
- * The two Tovu copies (`render.ts:252`, `features/theme/static-render.ts:210`, both a coercing
57
+ * The two host-side copies (`render.ts:252`, `features/theme/static-render.ts:210`, both a coercing
58
58
  * `value => passes ? value : "#"` wrapper around the identical predicate below) have **not yet been
59
59
  * retired to import this** — that edit is intentionally out of this change's scope (a
60
60
  * `check:boundaries` no-deep-import concern is NOT what blocks it: `render.ts` already imports
@@ -79,7 +79,7 @@ export declare const DEFAULT_MAX_ITEM_COUNT = 500;
79
79
  * workspace's stored menu data (`content.db`, `content.seed.db`) before this change: every existing
80
80
  * `url`-kind href already starts with `#` or `/`, so this tightening rejects nothing that exists
81
81
  * today — it governs `createMenu` too, so it also reaches the Jini MCP agent-tool path, not just
82
- * hrefs written through Tovu's admin editor.
82
+ * hrefs written through the host's admin editor.
83
83
  *
84
84
  * @param rawHref - The href to check, already confirmed non-empty by `validateTarget`. Any string is
85
85
  * otherwise a valid input — this function performs no other precondition checks.
@@ -67,13 +67,13 @@ const SAFE_HREF_RESOLUTION_BASE = "http://tovu-safehref.invalid/";
67
67
  const SAFE_HREF_RESOLUTION_ORIGIN = new URL(SAFE_HREF_RESOLUTION_BASE).origin;
68
68
  /**
69
69
  * The CANONICAL href allowlist for an author-authored link — same accepted shapes (`#…`, same-origin
70
- * `/…`, `http(s)://`, `mailto:`) as Tovu's render-time `safeHref` in `render.ts` and its
70
+ * `/…`, `http(s)://`, `mailto:`) as the host's render-time `safeHref` in `render.ts` and its
71
71
  * `features/theme/static-render.ts` duplicate. **This is now the single source of truth**: exported
72
72
  * from this package's public surface (`navigation/index.ts` -> `@jini-ai/cms/navigation`) so the
73
- * dependency runs the direction that is actually legal — Tovu already depends on `@jini-ai/cms`, not
73
+ * dependency runs the direction that is actually legal — the host already depends on `@jini-ai/cms`, not
74
74
  * the reverse — rather than each consumer hand-copying the predicate.
75
75
  *
76
- * The two Tovu copies (`render.ts:252`, `features/theme/static-render.ts:210`, both a coercing
76
+ * The two host-side copies (`render.ts:252`, `features/theme/static-render.ts:210`, both a coercing
77
77
  * `value => passes ? value : "#"` wrapper around the identical predicate below) have **not yet been
78
78
  * retired to import this** — that edit is intentionally out of this change's scope (a
79
79
  * `check:boundaries` no-deep-import concern is NOT what blocks it: `render.ts` already imports
@@ -98,7 +98,7 @@ const SAFE_HREF_RESOLUTION_ORIGIN = new URL(SAFE_HREF_RESOLUTION_BASE).origin;
98
98
  * workspace's stored menu data (`content.db`, `content.seed.db`) before this change: every existing
99
99
  * `url`-kind href already starts with `#` or `/`, so this tightening rejects nothing that exists
100
100
  * today — it governs `createMenu` too, so it also reaches the Jini MCP agent-tool path, not just
101
- * hrefs written through Tovu's admin editor.
101
+ * hrefs written through the host's admin editor.
102
102
  *
103
103
  * @param rawHref - The href to check, already confirmed non-empty by `validateTarget`. Any string is
104
104
  * otherwise a valid input — this function performs no other precondition checks.
@@ -4,11 +4,11 @@
4
4
  *
5
5
  * Relocated here from Jini's `@jini-ai/ui` (2026-08-08): these keys aren't UI-rendering logic,
6
6
  * they're settings-domain presentation copy, and this package's `settings` module is the actual
7
- * domain owner shared across every host (Tovu, Zana, ...). `@jini-ai/ui` keeps the *component*
7
+ * domain owner shared across every host. `@jini-ai/ui` keeps the *component*
8
8
  * that renders a settings dialog; a host supplies the copy, and this is the shared copy any
9
9
  * OD-parity host can reuse instead of re-authoring its own. See project memory "Settings-dialog
10
- * i18n relocation" for the full decision record and the sibling move of the 8 Tovu-specific
11
- * capability-fact keys (those stayed host-side — see Tovu's own `settings-capabilities-i18n.ts`).
10
+ * i18n relocation" for the full decision record and the sibling move of the 8 host-specific
11
+ * capability-fact keys (those stayed host-side — see the host's own `settings-capabilities-i18n.ts`).
12
12
  *
13
13
  * No `en` dictionary: this package's convention (matching `@jini-ai/ui`) is "the English string
14
14
  * IS the key" — `translateSettingsDialog` falls back to the raw key for English and any