@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
@@ -1,49 +1,48 @@
1
- import { createHash } from "node:crypto";
2
1
  import { InvalidFieldKindError, InvalidFieldNameGrammarError, InvalidKeyGrammarError } from "./errors.js";
3
2
  import { INDEXABLE_FIELD_KINDS, isIndexableFieldKind, } from "./types.js";
4
3
  /**
5
- * @file CIC U-001 — the `kind`->`CAST` fixed lookup table, the identifier grammar
4
+ * @file the `kind`->`CAST` fixed lookup table, the identifier grammar
6
5
  * gate, the workspace-scoped queryable-index naming scheme, and the before/after index-transition
7
6
  * resolver (grammar-and-workspace-scoping folds).
8
7
  *
9
8
  * Purpose:
10
9
  * THIS IS THE HIGHEST-SECURITY-SEVERITY MODULE IN THE 5-PACKAGE PIPELINE — every value that ends
11
- * up inside a `CREATE INDEX ... CAST(json_extract(fields,'$.ext.{ns}.{field}') AS {type})`
10
+ * up inside a `CREATE INDEX... CAST(json_extract(fields,'$.ext.{ns}.{field}') AS {type})`
12
11
  * statement is produced here, and only here:
13
- * - `mapFieldKindToCast` (U-001-B1). STATE THE INVARIANT PRECISELY, because an earlier version
14
- * of this comment did not and the imprecision was itself a hazard: it said "a fixed, hardcoded
15
- * 5-entry table", which reads as though the ENTRY COUNT were the defense. It is not. Neither
16
- * the number of keys nor the number of distinct values is the security boundary.
17
- *
18
- * THE DEFENSE IS: this function returns a VALUE FROM THE TABLE and never returns, echoes, or
19
- * interpolates its own ARGUMENT. The function has exactly one `return`, and it is a table
20
- * lookup. Therefore the set of strings that can reach a `CAST(... AS {type})` position is
21
- * exactly the set of string literals written into `KIND_TO_CAST_LITERAL` in this source file —
22
- * compile-time constants, every one — no matter what an operator, an agent, or an attacker
23
- * supplies as `kind`. A 50-entry table of literals would be exactly as safe as a 5-entry one.
24
- *
25
- * What follows from that, and what a maintainer actually needs to know:
26
- * * Adding a KEY whose value is an existing literal cannot widen the DDL alphabet. It is not
27
- * a security event and does not need to be feared as one. (`relation: "TEXT"` is exactly
28
- * this: `"TEXT"` was already present twice.)
29
- * * Adding a new VALUE does widen it, and must be reviewed as a DDL change.
30
- * * Returning, concatenating, or template-interpolating `kind` itself — or reading the table
31
- * with a fallback such as `?? kind` — BREAKS the invariant outright. That is the edit to
32
- * refuse, and it is the one the old "5-entry" phrasing gave no way to recognise.
33
- * The reachable-value set is pinned executably by `HISTORICAL_DDL_ALPHABET` in
34
- * `__tests__/field-kind-widening.test.ts`, so this claim is a failing test and not only prose.
35
- *
36
- * Degraded failure mode, stated so the blast radius is known: if every runtime guard below
37
- * were deleted, a storage-only kind would reach the table and `KIND_TO_CAST_LITERAL["json"]`
38
- * would evaluate to `undefined` — a SQL syntax error and a loud broken feature, never an
39
- * operator-controlled string. Total guard removal degrades this module to unavailable, not to
40
- * injectable. That is the property worth preserving through any future change.
41
- * - `validateIdentifierGrammar`/`buildQueryableFieldIndexName` gate every `key`/field name
42
- * through `^[a-z][a-z0-9_]{0,63}$` before it can reach an index-name segment or JSON-path
43
- * literal (U-001-B2), and join grammar-gated segments with `/` — a delimiter outside the
44
- * grammar's own alphabet `[a-z0-9_]`, closing the namespace-injectivity collision class
45
- * (U-001-B3) — and fold in a workspace-derived segment so two workspaces defining the same
46
- * `(key, field)` pair never collide on index identity.
12
+ * - `mapFieldKindToCast`. STATE THE INVARIANT PRECISELY, because an earlier version
13
+ * of this comment did not and the imprecision was itself a hazard: it said "a fixed, hardcoded
14
+ * 5-entry table", which reads as though the ENTRY COUNT were the defense. It is not. Neither
15
+ * the number of keys nor the number of distinct values is the security boundary.
16
+ *
17
+ * THE DEFENSE IS: this function returns a VALUE FROM THE TABLE and never returns, echoes, or
18
+ * interpolates its own ARGUMENT. The function has exactly one `return`, and it is a table
19
+ * lookup. Therefore the set of strings that can reach a `CAST(... AS {type})` position is
20
+ * exactly the set of string literals written into `KIND_TO_CAST_LITERAL` in this source file —
21
+ * compile-time constants, every one — no matter what an operator, an agent, or an attacker
22
+ * supplies as `kind`. A 50-entry table of literals would be exactly as safe as a 5-entry one.
23
+ *
24
+ * What follows from that, and what a maintainer actually needs to know:
25
+ * * Adding a KEY whose value is an existing literal cannot widen the DDL alphabet. It is not
26
+ * a security event and does not need to be feared as one. (`relation: "TEXT"` is exactly
27
+ * this: `"TEXT"` was already present twice.)
28
+ * * Adding a new VALUE does widen it, and must be reviewed as a DDL change.
29
+ * * Returning, concatenating, or template-interpolating `kind` itself — or reading the table
30
+ * with a fallback such as `?? kind` — BREAKS the invariant outright. That is the edit to
31
+ * refuse, and it is the one the old "5-entry" phrasing gave no way to recognise.
32
+ * The reachable-value set is pinned executably by `HISTORICAL_DDL_ALPHABET` in
33
+ * `__tests__/field-kind-widening.test.ts`, so this claim is a failing test and not only prose.
34
+ *
35
+ * Degraded failure mode, stated so the blast radius is known: if every runtime guard below
36
+ * were deleted, a storage-only kind would reach the table and `KIND_TO_CAST_LITERAL["json"]`
37
+ * would evaluate to `undefined` — a SQL syntax error and a loud broken feature, never an
38
+ * operator-controlled string. Total guard removal degrades this module to unavailable, not to
39
+ * injectable. That is the property worth preserving through any future change.
40
+ * - `validateIdentifierGrammar`/`buildQueryableFieldIndexName` gate every `key`/field name
41
+ * through `^[a-z][a-z0-9_]{0,63}$` before it can reach an index-name segment or JSON-path
42
+ * literal, and join grammar-gated segments with `/` — a delimiter outside the
43
+ * grammar's own alphabet `[a-z0-9_]`, closing the namespace-injectivity collision class
44
+ * — and fold in a workspace-derived segment so two workspaces defining the same
45
+ * `(key, field)` pair never collide on index identity.
47
46
  *
48
47
  * How it relates to the project:
49
48
  * `resolveFieldIndexTransition` deliberately still ACCEPTS a storage-only kind on its `none` and
@@ -53,35 +52,39 @@ import { INDEXABLE_FIELD_KINDS, isIndexableFieldKind, } from "./types.js";
53
52
  *
54
53
  * `write-service.ts`'s `registerContentType`/`updateContentTypeFields` call
55
54
  * `resolveFieldIndexTransition` once per field (never two independent kind/queryable branches —
56
- * CIC U-003-B1) and hand the result to the injected `indexProvisioner` port, which is the only
55
+ * ) and hand the result to the injected `indexProvisioner` port, which is the only
57
56
  * thing that ever issues the real `CREATE INDEX`/`DROP INDEX` DDL (this module only decides what
58
57
  * to build, never executes DDL itself).
59
58
  *
60
59
  * Architectural role:
61
- * `features/content-types` domain logic. No dependencies beyond `node:crypto` (workspace-segment
62
- * hashing) and this package's own `errors.ts`/`types.ts`.
60
+ * `features/content-types` domain logic. Workspace-segment hashing uses Web Crypto so this
61
+ * universal entry needs only standard runtime APIs and this package's own `errors.ts`/`types.ts`.
62
+ * See docs/decisions/DR-001-safe-schema-and-index-transitions.md.
63
63
  */
64
64
  /**
65
- * U-001-B2 — the closed identifier grammar every `content_types.key` and field name must satisfy,
65
+ * — the closed identifier grammar every `content_types.key` and field name must satisfy,
66
66
  * as a pattern string so the agent-facing JSON Schemas in `agent-tools.ts` can publish the very
67
67
  * same grammar instead of restating it. A cross-cutting single-source-of-truth governance rule makes this grammar load-bearing for DDL
68
68
  * safety, so it must have exactly one definition; {@link IDENTIFIER_GRAMMAR} is compiled from this
69
69
  * string rather than written twice.
70
+ * See docs/decisions/DR-001-safe-schema-and-index-transitions.md.
70
71
  */
71
72
  export const IDENTIFIER_GRAMMAR_PATTERN = "^[a-z][a-z0-9_]{0,63}$";
72
- /** U-001-B2 — the closed identifier grammar every `content_types.key` and field name must satisfy. */
73
+ /** — the closed identifier grammar every `content_types.key` and field name must satisfy. See docs/decisions/DR-001-safe-schema-and-index-transitions.md. */
73
74
  const IDENTIFIER_GRAMMAR = new RegExp(IDENTIFIER_GRAMMAR_PATTERN);
74
75
  /**
75
- * Structural grammar gate for a `content_types.key` or field name (U-001-B2).
76
+ * Structural grammar gate for a `content_types.key` or field name.
76
77
  *
77
78
  * @complexity O(n) in the string's length (regex match), bounded at 64 chars.
78
79
  * @overallScore 100
80
+ * See docs/decisions/DR-001-safe-schema-and-index-transitions.md.
79
81
  */
80
- export function validateIdentifierGrammar(value) {
82
+ export function validateIdentifierGrammar(requiredArgs, optionalArgs = {}) {
83
+ const { value } = requiredArgs;
81
84
  return IDENTIFIER_GRAMMAR.test(value);
82
85
  }
83
86
  /**
84
- * U-001-B1 — the fixed, hardcoded `kind` -> SQL `CAST` type-token table. Every value here is a
87
+ * — the fixed, hardcoded `kind` -> SQL `CAST` type-token table. Every value here is a
85
88
  * plain uppercase SQL type token with no quotes/parens/whitespace; never built by concatenation.
86
89
  *
87
90
  * Keyed by `IndexableFieldKind`, NOT `ContentTypeFieldKind`: a storage-only kind has no CAST
@@ -90,6 +93,7 @@ export function validateIdentifierGrammar(value) {
90
93
  * compiler-enforced: adding a member to `INDEXABLE_FIELD_KINDS` fails the build until an entry
91
94
  * appears here. Do not replace it with a `Partial`, an index signature, or a lookup with a
92
95
  * fallback — each of those converts a build failure into a runtime surprise on the DDL path.
96
+ * See docs/decisions/DR-001-safe-schema-and-index-transitions.md.
93
97
  */
94
98
  const KIND_TO_CAST_LITERAL = {
95
99
  text: "TEXT",
@@ -109,7 +113,7 @@ const KIND_TO_CAST_LITERAL = {
109
113
  * {@link InvalidFieldKindError} for anything that is not exactly one of `INDEXABLE_FIELD_KINDS` —
110
114
  * including a storage-only kind such as `json`, which is a legal kind to declare but has no CAST
111
115
  * target. This is a lookup, never a template, so an adversarial payload can never reach the
112
- * returned string (U-001-B1).
116
+ * returned string.
113
117
  *
114
118
  * The PARAMETER type is deliberately the wider `ContentTypeFieldKind`. Narrowing it would be a
115
119
  * breaking change for `@jini-ai/cms`'s public consumers (`content-types/index.ts` documents this
@@ -123,12 +127,16 @@ const KIND_TO_CAST_LITERAL = {
123
127
  *
124
128
  * @complexity O(1) — a fixed-size object lookup behind a fixed-size membership test.
125
129
  * @overallScore 100
130
+ * See docs/decisions/DR-001-safe-schema-and-index-transitions.md.
126
131
  */
127
- export function mapFieldKindToCast(kind) {
128
- if (!isIndexableFieldKind(kind)) {
129
- throw new InvalidFieldKindError(`'${String(kind)}' is not an indexable field kind (${INDEXABLE_FIELD_KINDS.join("|")}) — U-001-B1`);
132
+ export function mapFieldKindToCast(requiredArgs, optionalArgs = {}) {
133
+ const { kind } = requiredArgs;
134
+ const candidate = { value: kind };
135
+ if (!isIndexableFieldKind(candidate)) {
136
+ // See docs/decisions/DR-001-safe-schema-and-index-transitions.md for the invariant behind this refusal; keep external identifiers out of caller-facing messages.
137
+ throw new InvalidFieldKindError({ message: `'${String(kind)}' is not an indexable field kind (${INDEXABLE_FIELD_KINDS.join("|")})` });
130
138
  }
131
- return KIND_TO_CAST_LITERAL[kind];
139
+ return KIND_TO_CAST_LITERAL[candidate.value];
132
140
  }
133
141
  /**
134
142
  * Derives a grammar-safe, workspace-scoped index-name segment from an arbitrary `workspaceId`
@@ -136,31 +144,44 @@ export function mapFieldKindToCast(kind) {
136
144
  * it to a fixed `[0-9a-f]` alphabet (a strict subset of the identifier grammar's `[a-z0-9_]`)
137
145
  * prefixed with a letter so the segment independently satisfies {@link validateIdentifierGrammar}.
138
146
  *
139
- * @complexity O(1) — a fixed-length SHA-256 digest.
147
+ * UTF-8 encoding, SHA-256, lowercase hex, and the first 16 hex characters preserve the
148
+ * historical Node hash byte-for-byte, including replacement of unpaired UTF-16 surrogates.
149
+ * Requires Web Crypto; digest failures reject the returned promise.
150
+ *
151
+ * @complexity O(n) time and space in the UTF-8 workspace ID length.
140
152
  * @overallScore 100
141
153
  */
142
- function workspaceIndexSegment(workspaceId) {
143
- const digest = createHash("sha256").update(workspaceId).digest("hex").slice(0, 16);
154
+ async function workspaceIndexSegment(workspaceId) {
155
+ const hash = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(workspaceId));
156
+ const digest = Array.from(new Uint8Array(hash).subarray(0, 8), (byte) => byte.toString(16).padStart(2, "0")).join("");
144
157
  return `w${digest}`;
145
158
  }
146
159
  /**
147
160
  * Builds the workspace-scoped queryable-field index identity for `(workspaceId, contentTypeKey,
148
161
  * fieldName)`. Both `contentTypeKey` and `fieldName` are grammar-gated first — a failure throws
149
- * before any index name is produced (U-001-B2). Segments are joined with `/`, a delimiter outside
162
+ * before any index name is produced. Segments are joined with `/`, a delimiter outside
150
163
  * the grammar's own alphabet `[a-z0-9_]`, so distinct `(key, name)` pairs can never collide by
151
- * boundary-shifting (U-001-B3, e.g. `("a_b","c")` vs `("a","b_c")`).
164
+ * boundary-shifting (e.g. `("a_b","c")` vs `("a","b_c")`).
165
+ *
166
+ * Returns a promise for the unchanged index identity. Grammar failures still throw
167
+ * synchronously before hashing; Web Crypto failures reject the promise. Callers must await
168
+ * the name before using it in DDL.
152
169
  *
153
- * @complexity O(1) plus two grammar checks and one SHA-256 hash.
170
+ * @complexity O(n) time and space in the UTF-8 workspace ID length, plus two grammar checks.
154
171
  * @overallScore 100
172
+ * See docs/decisions/DR-001-safe-schema-and-index-transitions.md.
155
173
  */
156
174
  export function buildQueryableFieldIndexName(params) {
157
- if (!validateIdentifierGrammar(params.contentTypeKey)) {
158
- throw new InvalidKeyGrammarError(`content-type key '${params.contentTypeKey}' fails the identifier grammar gate ^[a-z][a-z0-9_]{0,63}$ (U-001-B2)`);
175
+ if (!validateIdentifierGrammar({ value: params.contentTypeKey })) {
176
+ // See docs/decisions/DR-001-safe-schema-and-index-transitions.md for the invariant behind this refusal; keep external identifiers out of caller-facing messages.
177
+ throw new InvalidKeyGrammarError({ message: `content-type key '${params.contentTypeKey}' fails the identifier grammar gate ^[a-z][a-z0-9_]{0,63}$` });
159
178
  }
160
- if (!validateIdentifierGrammar(params.fieldName)) {
161
- throw new InvalidFieldNameGrammarError(`field name '${params.fieldName}' fails the identifier grammar gate ^[a-z][a-z0-9_]{0,63}$ (U-001-B2)`);
179
+ if (!validateIdentifierGrammar({ value: params.fieldName })) {
180
+ // See docs/decisions/DR-001-safe-schema-and-index-transitions.md for the invariant behind this refusal; keep external identifiers out of caller-facing messages.
181
+ throw new InvalidFieldNameGrammarError({ message: `field name '${params.fieldName}' fails the identifier grammar gate ^[a-z][a-z0-9_]{0,63}$` });
162
182
  }
163
- return `q/${workspaceIndexSegment(params.workspaceId)}/${params.contentTypeKey}/${params.fieldName}`;
183
+ const { workspaceId, contentTypeKey, fieldName } = params;
184
+ return workspaceIndexSegment(workspaceId).then((segment) => `q/${segment}/${contentTypeKey}/${fieldName}`);
164
185
  }
165
186
  /**
166
187
  * Narrows a post-call field kind for the two arms that hand a kind to the index provisioner.
@@ -174,19 +195,22 @@ export function buildQueryableFieldIndexName(params) {
174
195
  * @overallScore 100
175
196
  */
176
197
  function assertIndexableForProvisioning(kind) {
177
- if (!isIndexableFieldKind(kind)) {
178
- throw new InvalidFieldKindError(`storage-only kind '${String(kind)}' reached index provisioning; only (${INDEXABLE_FIELD_KINDS.join("|")}) may be queryable — U-001-B1`);
198
+ const candidate = { value: kind };
199
+ if (!isIndexableFieldKind(candidate)) {
200
+ // See docs/decisions/DR-001-safe-schema-and-index-transitions.md for the invariant behind this refusal; keep external identifiers out of caller-facing messages.
201
+ throw new InvalidFieldKindError({ message: `storage-only kind '${String(kind)}' reached index provisioning; only (${INDEXABLE_FIELD_KINDS.join("|")}) may be queryable` });
179
202
  }
180
- return kind;
203
+ return candidate.value;
181
204
  }
182
205
  /**
183
- * CIC U-003-B1 — resolves a single field's index transition from ONE before/after comparison of
206
+ * — resolves a single field's index transition from ONE before/after comparison of
184
207
  * its `(kind, queryable)` pair, never from two independent branches that each assume the other
185
208
  * property is unchanged (the bug class this closes: a field whose `kind` AND `queryable` both
186
- * change in the same call must still resolve correctly — REQ-30(b)).
209
+ * change in the same call must still resolve correctly — (b)).
187
210
  *
188
211
  * @complexity O(1) — four comparisons, one of four fixed outcomes.
189
212
  * @overallScore 100
213
+ * See docs/decisions/DR-001-safe-schema-and-index-transitions.md.
190
214
  */
191
215
  export function resolveFieldIndexTransition(params) {
192
216
  const beforeQueryable = params.before?.queryable ?? false;
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * See `../entries/index.js`'s header for why these two ship as separate subpaths rather than one.
8
8
  */
9
- export type { ContentTypeFieldKind, IndexableFieldKind, StorageOnlyFieldKind, ContentTypeFieldDef, ContentTypeStatus, ContentTypeRecord, ActorPrincipalKind, ActorIdentityInput, Result, } from "./types.js";
9
+ export type { ContentTypeFieldKind, IndexableFieldKind, StorageOnlyFieldKind, ContentTypeFieldDef, ContentTypeStatus, ContentTypeRecord, ActorPrincipalKind, ActorIdentityInput, } from "./types.js";
10
10
  /**
11
11
  * `CONTENT_TYPE_FIELD_KINDS`/`isContentTypeFieldKind` answer "may this kind be DECLARED and
12
12
  * STORED". `INDEXABLE_FIELD_KINDS`/`isIndexableFieldKind` answer the narrower "may this kind be
@@ -36,7 +36,8 @@ StorageOnlyFieldNotQueryableError, InvalidFieldShapeError, QueryableFieldCapExce
36
36
  * single definition of the identifier grammar — the published agent-tool schemas reference it
37
37
  * rather than restating it, and a consumer validating input ahead of a call must reach the same
38
38
  * one. `mapFieldKindToCast`/`buildQueryableFieldIndexName` are what a host's own DDL provisioner
39
- * needs in order to generate the same index names this module expects to find.
39
+ * needs in order to generate the same index names this module expects to find. The index-name
40
+ * builder returns a promise: await its Web Crypto hash before passing the name to DDL.
40
41
  */
41
42
  export type { FieldIndexState, FieldIndexTransition } from "./index-provisioning.js";
42
43
  export { IDENTIFIER_GRAMMAR_PATTERN, validateIdentifierGrammar, mapFieldKindToCast, buildQueryableFieldIndexName, resolveFieldIndexTransition, } from "./index-provisioning.js";
@@ -60,7 +61,7 @@ export { planCleanup, executeCleanup } from "./cleanup.js";
60
61
  */
61
62
  export { InMemoryContentTypeRepo, NoopContentTypeIndexProvisioner, toContentTypeOutbox, } from "./repo.memory.js";
62
63
  /** The agent-tool surface for this domain (see `agent-tools.ts` for what is deliberately omitted). */
63
- export { contentTypesAgentToolCatalog, type AgentToolDefinition as ContentTypesAgentToolDefinition, type AgentToolSideEffect as ContentTypesAgentToolSideEffect, type AgentToolActorClassRule as ContentTypesAgentToolActorClassRule, } from "./agent-tools.js";
64
+ export { contentTypesAgentToolCatalog, } from "./agent-tools.js";
64
65
  /**
65
66
  * The agent-tool wiring for this domain.
66
67
  *
@@ -1,7 +1,8 @@
1
- import type { ClockPort } from "../core/ports.js";
1
+ import type { Clock } from "@jini-ai/core/primitives";
2
2
  import type { AuthorizeFn, ContentTypeRepoPort, OutboxPort } from "./write-service.js";
3
3
  import type { TeardownIndexProvisionerPort } from "./lifecycle.js";
4
- import type { ActorPrincipalKind, ContentTypeRecord, Result } from "./types.js";
4
+ import type { Result } from "@jini-ai/core/primitives";
5
+ import type { ActorPrincipalKind, ContentTypeRecord } from "./types.js";
5
6
  /**
6
7
  * @file Content-type lifecycle `op` dispatch (the closed-union-dispatch convention,
7
8
  * `features/settings/definitions-dispatch.ts`'s pattern reused verbatim for this domain).
@@ -15,10 +16,12 @@ import type { ActorPrincipalKind, ContentTypeRecord, Result } from "./types.js";
15
16
  export declare const CONTENT_TYPE_LIFECYCLE_OP_NAMES: readonly ["deprecate", "reactivate", "tombstone"];
16
17
  export type ContentTypeLifecycleOp = (typeof CONTENT_TYPE_LIFECYCLE_OP_NAMES)[number];
17
18
  /** Narrows an untrusted `op` string to {@link ContentTypeLifecycleOp}, or `null` if it isn't one. */
18
- export declare function parseContentTypeLifecycleOp(op: unknown): ContentTypeLifecycleOp | null;
19
+ export declare function parseContentTypeLifecycleOp(requiredArgs: {
20
+ op: unknown;
21
+ }, optionalArgs?: Record<string, never>): ContentTypeLifecycleOp | null;
19
22
  export interface LifecycleDispatchDeps {
20
23
  repo: ContentTypeRepoPort;
21
- clock: ClockPort;
24
+ clock: Clock;
22
25
  authorize: AuthorizeFn;
23
26
  outbox: OutboxPort;
24
27
  indexProvisioner: TeardownIndexProvisionerPort;
@@ -31,7 +34,10 @@ export interface LifecycleDispatchInput {
31
34
  /** Threaded straight through to {@link LifecycleTransitionInput.principalKind} — the audit provenance stamped on the revision row. */
32
35
  principalKind?: ActorPrincipalKind;
33
36
  }
34
- export type ContentTypeLifecycleHandler = (deps: LifecycleDispatchDeps, input: LifecycleDispatchInput) => Promise<Result<{
37
+ export type ContentTypeLifecycleHandler = (required: {
38
+ deps: LifecycleDispatchDeps;
39
+ input: LifecycleDispatchInput;
40
+ }, optional?: Record<string, never>) => Promise<Result<{
35
41
  contentType: ContentTypeRecord;
36
42
  }, Error>>;
37
43
  export declare const CONTENT_TYPE_LIFECYCLE_OPS: Record<ContentTypeLifecycleOp, ContentTypeLifecycleHandler>;
@@ -11,7 +11,8 @@ import { deprecateContentType, reactivateContentType, tombstoneContentType } fro
11
11
  */
12
12
  export const CONTENT_TYPE_LIFECYCLE_OP_NAMES = ["deprecate", "reactivate", "tombstone"];
13
13
  /** Narrows an untrusted `op` string to {@link ContentTypeLifecycleOp}, or `null` if it isn't one. */
14
- export function parseContentTypeLifecycleOp(op) {
14
+ export function parseContentTypeLifecycleOp(requiredArgs, optionalArgs = {}) {
15
+ const { op } = requiredArgs;
15
16
  return typeof op === "string" && CONTENT_TYPE_LIFECYCLE_OP_NAMES.includes(op)
16
17
  ? op
17
18
  : null;
@@ -20,9 +21,9 @@ export function parseContentTypeLifecycleOp(op) {
20
21
  * (defense-in-depth against a prototype-chain key ever resolving, matching the settings
21
22
  * dispatch table's own convention). */
22
23
  const contentTypeLifecycleOps = {
23
- deprecate: (deps, input) => deprecateContentType({ deps, input }),
24
- reactivate: (deps, input) => reactivateContentType({ deps: { repo: deps.repo, clock: deps.clock, authorize: deps.authorize }, input }),
25
- tombstone: (deps, input) => tombstoneContentType({ deps, input }),
24
+ deprecate: ({ deps, input }) => deprecateContentType({ deps, input }),
25
+ reactivate: ({ deps, input }) => reactivateContentType({ deps: { repo: deps.repo, clock: deps.clock, authorize: deps.authorize }, input }),
26
+ tombstone: ({ deps, input }) => tombstoneContentType({ deps, input }),
26
27
  };
27
28
  export const CONTENT_TYPE_LIFECYCLE_OPS = Object.assign(Object.create(null), contentTypeLifecycleOps);
28
29
  //# sourceMappingURL=lifecycle-dispatch.js.map
@@ -1,28 +1,30 @@
1
- import type { ClockPort } from "../core/ports.js";
1
+ import type { Clock } from "@jini-ai/core/primitives";
2
2
  import type { AuthorizeFn, ContentTypeRepoPort, OutboxPort } from "./write-service.js";
3
- import type { ActorPrincipalKind, ContentTypeRecord, Result } from "./types.js";
3
+ import type { Result } from "@jini-ai/core/primitives";
4
+ import type { ActorPrincipalKind, ContentTypeRecord } from "./types.js";
4
5
  /**
5
- * @file REQ-09..12 — the content-type lifecycle state machine `active ⇄ deprecated ->
6
- * tombstone` (INV-06).
6
+ * @file..12 — the content-type lifecycle state machine `active ⇄ deprecated ->
7
+ * tombstone`.
7
8
  *
8
9
  * Purpose:
9
10
  * `deprecateContentType`/`reactivateContentType` are the reversible `active <-> deprecated` pair
10
- * (deprecate blocks new-entry creation only, per REQ-10/REQ-28's asymmetry — existing entries stay
11
+ * (deprecate blocks new-entry creation only, per asymmetry — existing entries stay
11
12
  * fully readable/writable). `tombstoneContentType` is the one-way terminal transition: it MUST be
12
- * entered from `deprecated` (EC-09, "must deprecate first") and never left (INV-06) — both
13
+ * entered from `deprecated` ("must deprecate first") and never left — both
13
14
  * `reactivateContentType` and `deprecateContentType` reject unconditionally against a tombstoned
14
- * row (the property `operationTests INV-06` case). Tombstoning tears down every queryable-field
15
- * index the type ever provisioned (AC-17) and enqueues the same-call outbox event
15
+ * row (the property `operationTests ` case). Tombstoning tears down every queryable-field
16
+ * index the type ever provisioned and enqueues the same-call outbox event
16
17
  * required for `content_type.deprecated`/`content_type.tombstoned`.
17
18
  *
18
19
  * How it relates to the project:
19
20
  * `reactivateContentType` deliberately does not accept `outbox`/`indexProvisioner` deps — no
20
21
  * certified test requires a reactivation event or index reprovisioning on this transition, and
21
- * REQ-09..12 name only deprecate/tombstone as outbox-obligated.
22
+ * 12 name only deprecate/tombstone as outbox-obligated.
22
23
  *
23
24
  * Architectural role:
24
25
  * `features/content-types` domain logic, composing `write-service.ts`'s repo/outbox/index-
25
26
  * provisioner port shapes rather than redeclaring them.
27
+ * See docs/decisions/DR-002-content-lifecycle-and-cleanup.md.
26
28
  */
27
29
  export interface LifecycleTransitionInput {
28
30
  workspaceId: string;
@@ -35,19 +37,20 @@ export interface LifecycleTransitionInput {
35
37
  export interface DeprecateContentTypeRequired {
36
38
  deps: {
37
39
  repo: ContentTypeRepoPort;
38
- clock: ClockPort;
40
+ clock: Clock;
39
41
  authorize: AuthorizeFn;
40
42
  outbox: OutboxPort;
41
43
  };
42
44
  input: LifecycleTransitionInput;
43
45
  }
44
46
  /**
45
- * AC-13/AC-19 — flips an active type to `deprecated` (blocks only NEW entry creation, REQ-10) and
46
- * enqueues `content_type.deprecated` in the same call. Rejected unconditionally (INV-06) if the
47
+ * — flips an active type to `deprecated` (blocks only NEW entry creation) and
48
+ * enqueues `content_type.deprecated` in the same call. Rejected unconditionally if the
47
49
  * type is already `tombstone` — deprecate can never be used to "step back" out of tombstone.
48
50
  *
49
51
  * @complexity O(1) plus one repo read, one same-tx write pair, and one outbox enqueue.
50
52
  * @overallScore 100
53
+ * See docs/decisions/DR-002-content-lifecycle-and-cleanup.md.
51
54
  */
52
55
  export declare function deprecateContentType(required: DeprecateContentTypeRequired): Promise<Result<{
53
56
  contentType: ContentTypeRecord;
@@ -55,17 +58,18 @@ export declare function deprecateContentType(required: DeprecateContentTypeRequi
55
58
  export interface ReactivateContentTypeRequired {
56
59
  deps: {
57
60
  repo: ContentTypeRepoPort;
58
- clock: ClockPort;
61
+ clock: Clock;
59
62
  authorize: AuthorizeFn;
60
63
  };
61
64
  input: LifecycleTransitionInput;
62
65
  }
63
66
  /**
64
- * AC-13 — flips a `deprecated` (or already-`active`, a no-op-shaped success) type back to
65
- * `active`. Rejected unconditionally (AC-14/INV-06) against a tombstoned row.
67
+ * — flips a `deprecated` (or already-`active`, a no-op-shaped success) type back to
68
+ * `active`. Rejected unconditionally against a tombstoned row.
66
69
  *
67
70
  * @complexity O(1) plus one repo read and one same-tx write pair.
68
71
  * @overallScore 100
72
+ * See docs/decisions/DR-002-content-lifecycle-and-cleanup.md.
69
73
  */
70
74
  export declare function reactivateContentType(required: ReactivateContentTypeRequired): Promise<Result<{
71
75
  contentType: ContentTypeRecord;
@@ -80,7 +84,7 @@ export interface TeardownIndexProvisionerPort {
80
84
  export interface TombstoneContentTypeRequired {
81
85
  deps: {
82
86
  repo: ContentTypeRepoPort;
83
- clock: ClockPort;
87
+ clock: Clock;
84
88
  authorize: AuthorizeFn;
85
89
  outbox: OutboxPort;
86
90
  indexProvisioner: TeardownIndexProvisionerPort;
@@ -88,15 +92,16 @@ export interface TombstoneContentTypeRequired {
88
92
  input: LifecycleTransitionInput;
89
93
  }
90
94
  /**
91
- * AC-17/AC-20/EC-09 — the one-way terminal transition into `tombstone`. Must be entered from
95
+ * — the one-way terminal transition into `tombstone`. Must be entered from
92
96
  * `deprecated` (any other status, including an already-tombstoned row, is rejected with the same
93
- * "must deprecate first" guard — EC-09/INV-06 collapse to one check). Tears down every queryable-
97
+ * "must deprecate first" guard — collapse to one check). Tears down every queryable-
94
98
  * field index the type ever provisioned BEFORE persisting the status flip, and enqueues
95
99
  * `content_type.tombstoned` in the same call.
96
100
  *
97
101
  * @complexity O(1) plus one repo read, one index-teardown call, one same-tx write pair, and one
98
102
  * outbox enqueue.
99
103
  * @overallScore 100
104
+ * See docs/decisions/DR-002-content-lifecycle-and-cleanup.md.
100
105
  */
101
106
  export declare function tombstoneContentType(required: TombstoneContentTypeRequired): Promise<Result<{
102
107
  contentType: ContentTypeRecord;