@cosmicdrift/kumiko-types 0.326.1 → 0.328.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 (173) hide show
  1. package/dist/action-icon.d.ts +3 -0
  2. package/dist/action-icon.js +51 -0
  3. package/{src/agent-exposure.ts → dist/agent-exposure.d.ts} +8 -15
  4. package/dist/agent-exposure.js +16 -0
  5. package/dist/concurrency-mode.d.ts +1 -0
  6. package/dist/config-scope.d.ts +1 -0
  7. package/dist/config-scope.js +1 -0
  8. package/dist/config.d.ts +234 -0
  9. package/dist/config.js +1 -0
  10. package/dist/cursor-types.d.ts +16 -0
  11. package/dist/cursor-types.js +1 -0
  12. package/dist/db-connection.d.ts +14 -0
  13. package/dist/db-connection.js +1 -0
  14. package/dist/define-handler.d.ts +58 -0
  15. package/dist/define-handler.js +1 -0
  16. package/dist/derivatives-types.d.ts +53 -0
  17. package/dist/derivatives-types.js +3 -0
  18. package/dist/entity-cache.d.ts +16 -0
  19. package/dist/entity-cache.js +1 -0
  20. package/dist/entity-handlers.d.ts +54 -0
  21. package/dist/entity-handlers.js +1 -0
  22. package/dist/entity-table-meta-types.d.ts +44 -0
  23. package/dist/entity-table-meta-types.js +6 -0
  24. package/dist/envelope-cipher-types.d.ts +5 -0
  25. package/dist/envelope-cipher-types.js +1 -0
  26. package/dist/event-store-executor-types.d.ts +86 -0
  27. package/dist/event-store-executor-types.js +1 -0
  28. package/dist/event-store-types.d.ts +37 -0
  29. package/dist/event-store-types.js +7 -0
  30. package/dist/event-type-map.d.ts +8 -0
  31. package/{src/event-type-map.ts → dist/event-type-map.js} +1 -16
  32. package/dist/executor-brand.d.ts +8 -0
  33. package/dist/executor-brand.js +1 -0
  34. package/dist/extension-options-map.d.ts +9 -0
  35. package/dist/extension-options-map.js +4 -0
  36. package/dist/feature.d.ts +518 -0
  37. package/dist/feature.js +1 -0
  38. package/dist/field-icon.d.ts +1 -0
  39. package/dist/field-icon.js +1 -0
  40. package/dist/fields.d.ts +729 -0
  41. package/dist/fields.js +50 -0
  42. package/dist/file-handle-types.d.ts +12 -0
  43. package/dist/file-handle-types.js +1 -0
  44. package/dist/file-provider-resolver-types.d.ts +3 -0
  45. package/dist/file-provider-resolver-types.js +1 -0
  46. package/dist/file-storage-provider-types.d.ts +17 -0
  47. package/dist/file-storage-provider-types.js +1 -0
  48. package/dist/geo-tz.d.ts +17 -0
  49. package/{src/geo-tz.ts → dist/geo-tz.js} +1 -20
  50. package/dist/handlers.d.ts +493 -0
  51. package/dist/handlers.js +49 -0
  52. package/dist/hook-phase.d.ts +1 -0
  53. package/dist/hook-phase.js +1 -0
  54. package/dist/hooks.d.ts +75 -0
  55. package/dist/hooks.js +1 -0
  56. package/dist/http-route.d.ts +52 -0
  57. package/dist/http-route.js +13 -0
  58. package/dist/identifiers.d.ts +8 -0
  59. package/{src/identifiers.ts → dist/identifiers.js} +7 -24
  60. package/dist/index.d.ts +1 -0
  61. package/dist/index.js +1 -0
  62. package/dist/kms-adapter-types.d.ts +68 -0
  63. package/dist/kms-adapter-types.js +49 -0
  64. package/dist/logger.d.ts +7 -0
  65. package/dist/logger.js +1 -0
  66. package/dist/multi-stream-apply-context-types.d.ts +18 -0
  67. package/dist/multi-stream-apply-context-types.js +1 -0
  68. package/dist/nav-icon.d.ts +3 -0
  69. package/dist/nav-icon.js +100 -0
  70. package/dist/nav.d.ts +39 -0
  71. package/dist/nav.js +1 -0
  72. package/dist/observability/index.d.ts +3 -0
  73. package/dist/observability/index.js +2 -0
  74. package/dist/observability/metric.d.ts +34 -0
  75. package/dist/observability/metric.js +3 -0
  76. package/dist/observability/provider.d.ts +24 -0
  77. package/dist/observability/provider.js +4 -0
  78. package/dist/observability/span.d.ts +32 -0
  79. package/dist/observability/span.js +4 -0
  80. package/dist/ownership.d.ts +33 -0
  81. package/dist/ownership.js +5 -0
  82. package/dist/projection.d.ts +38 -0
  83. package/dist/projection.js +1 -0
  84. package/dist/rate-limit-types.d.ts +19 -0
  85. package/dist/rate-limit-types.js +1 -0
  86. package/dist/relations.d.ts +25 -0
  87. package/dist/relations.js +1 -0
  88. package/dist/schema-table-types.d.ts +15 -0
  89. package/{src/schema-table-types.ts → dist/schema-table-types.js} +0 -20
  90. package/dist/screen.d.ts +1265 -0
  91. package/dist/screen.js +3 -0
  92. package/dist/search-adapter.d.ts +32 -0
  93. package/dist/search-adapter.js +1 -0
  94. package/dist/secrets-types.d.ts +52 -0
  95. package/dist/secrets-types.js +30 -0
  96. package/dist/snapshot-types.d.ts +13 -0
  97. package/dist/snapshot-types.js +1 -0
  98. package/dist/step.d.ts +304 -0
  99. package/dist/step.js +11 -0
  100. package/dist/target-ref.d.ts +5 -0
  101. package/{src/target-ref.ts → dist/target-ref.js} +1 -6
  102. package/dist/tenancy-brand.d.ts +6 -0
  103. package/dist/tenancy-brand.js +1 -0
  104. package/dist/tenant-db-types.d.ts +67 -0
  105. package/dist/tenant-db-types.js +5 -0
  106. package/dist/tree-node.d.ts +26 -0
  107. package/dist/tree-node.js +22 -0
  108. package/dist/tz-context.d.ts +48 -0
  109. package/dist/tz-context.js +6 -0
  110. package/dist/websocket-route.d.ts +46 -0
  111. package/dist/websocket-route.js +10 -0
  112. package/dist/where-clause-types.d.ts +20 -0
  113. package/dist/where-clause-types.js +1 -0
  114. package/dist/workspace.d.ts +10 -0
  115. package/dist/workspace.js +1 -0
  116. package/dist/write-error-info-types.d.ts +12 -0
  117. package/dist/write-error-info-types.js +1 -0
  118. package/package.json +362 -119
  119. package/src/changes.json +12 -0
  120. package/src/__tests__/handlers.test.ts +0 -40
  121. package/src/__tests__/plural-forms.test.ts +0 -18
  122. package/src/action-icon.ts +0 -57
  123. package/src/concurrency-mode.ts +0 -5
  124. package/src/config-scope.ts +0 -5
  125. package/src/config.ts +0 -563
  126. package/src/cursor-types.ts +0 -18
  127. package/src/db-connection.ts +0 -25
  128. package/src/define-handler.ts +0 -138
  129. package/src/derivatives-types.ts +0 -106
  130. package/src/entity-cache.ts +0 -35
  131. package/src/entity-handlers.ts +0 -68
  132. package/src/entity-table-meta-types.ts +0 -101
  133. package/src/envelope-cipher-types.ts +0 -6
  134. package/src/event-store-executor-types.ts +0 -163
  135. package/src/event-store-types.ts +0 -85
  136. package/src/executor-brand.ts +0 -19
  137. package/src/extension-options-map.ts +0 -21
  138. package/src/feature.ts +0 -1132
  139. package/src/field-icon.ts +0 -23
  140. package/src/fields.ts +0 -1114
  141. package/src/file-handle-types.ts +0 -20
  142. package/src/file-provider-resolver-types.ts +0 -7
  143. package/src/file-storage-provider-types.ts +0 -54
  144. package/src/handlers.ts +0 -1246
  145. package/src/hook-phase.ts +0 -2
  146. package/src/hooks.ts +0 -170
  147. package/src/http-route.ts +0 -78
  148. package/src/kms-adapter-types.ts +0 -128
  149. package/src/logger.ts +0 -7
  150. package/src/multi-stream-apply-context-types.ts +0 -52
  151. package/src/nav-icon.ts +0 -105
  152. package/src/nav.ts +0 -139
  153. package/src/observability/index.ts +0 -28
  154. package/src/observability/metric.ts +0 -56
  155. package/src/observability/provider.ts +0 -32
  156. package/src/observability/span.ts +0 -58
  157. package/src/ownership.ts +0 -83
  158. package/src/projection.ts +0 -170
  159. package/src/rate-limit-types.ts +0 -32
  160. package/src/relations.ts +0 -52
  161. package/src/screen.ts +0 -1593
  162. package/src/search-adapter.ts +0 -45
  163. package/src/secrets-types.ts +0 -182
  164. package/src/snapshot-types.ts +0 -36
  165. package/src/step.ts +0 -390
  166. package/src/tenancy-brand.ts +0 -9
  167. package/src/tenant-db-types.ts +0 -116
  168. package/src/tree-node.ts +0 -134
  169. package/src/tz-context.ts +0 -63
  170. package/src/where-clause-types.ts +0 -31
  171. package/src/workspace.ts +0 -42
  172. package/src/write-error-info-types.ts +0 -20
  173. /package/{src/index.ts → dist/concurrency-mode.js} +0 -0
@@ -0,0 +1,729 @@
1
+ import type { VariantSpec } from "./derivatives-types.js";
2
+ import type { OwnershipMap } from "./ownership.js";
3
+ import type { EntityTenancy } from "./tenancy-brand.js";
4
+ export type FieldAccess = {
5
+ readonly read?: OwnershipMap | readonly string[];
6
+ readonly write?: OwnershipMap | readonly string[];
7
+ };
8
+ export type ResolvedPiiFlags = {
9
+ readonly pii?: boolean;
10
+ readonly userOwned?: {
11
+ readonly ownerField: string;
12
+ };
13
+ readonly tenantOwned?: boolean;
14
+ readonly recordOwned?: true;
15
+ readonly anonymize?: () => unknown | Promise<unknown>;
16
+ readonly allowPlaintext?: string;
17
+ /** Equality-Lookups (fetchOne/filter eq) bleiben trotz Verschluesselung
18
+ * moeglich: generierte `<snake>_bidx`-Spalte traegt einen HMAC-Blind-
19
+ * Index, Query-Compiler matchen `(col = $1 OR col_bidx = $2)`. Nur auf
20
+ * text-Feldern MIT Subject-Annotation erlaubt (Boot-Validator).
21
+ * Siehe docs/plans/datenschutz/blind-index.md (kumiko-framework#818). */
22
+ readonly lookupable?: true;
23
+ /** Foreign key into the `user` entity (authorId, assigneeId, ...) with no
24
+ * annotated content of its own. Triggers no encryption — feeds only the
25
+ * GDPR-hook-coverage boot guard, which otherwise can't see subject data
26
+ * that lives purely in an FK column. */
27
+ readonly subjectRef?: true;
28
+ };
29
+ export type PersonalSubject = "self" | {
30
+ readonly of: string;
31
+ } | "tenant";
32
+ export type Findability = "exact" | "fuzzy" | "none" | "secret";
33
+ type NoPersonalAnnotation = {
34
+ readonly personal?: never;
35
+ readonly find?: never;
36
+ readonly reason?: never;
37
+ readonly anonymize?: never;
38
+ };
39
+ export type PersonalAnnotations = {
40
+ readonly personal: PersonalSubject;
41
+ readonly find: Findability;
42
+ readonly anonymize?: () => unknown | Promise<unknown>;
43
+ } | {
44
+ readonly personal: "ref";
45
+ } | {
46
+ readonly personal: false;
47
+ readonly reason: string;
48
+ readonly anonymize?: () => unknown | Promise<unknown>;
49
+ };
50
+ export type LongTextFindability = "none" | "secret";
51
+ export type PersonalAnnotationsLongText = {
52
+ readonly personal: PersonalSubject;
53
+ readonly find: LongTextFindability;
54
+ readonly anonymize?: () => unknown | Promise<unknown>;
55
+ } | {
56
+ readonly personal: "ref";
57
+ } | {
58
+ readonly personal: false;
59
+ readonly reason: string;
60
+ readonly anonymize?: () => unknown | Promise<unknown>;
61
+ };
62
+ export type PersonalAnnotationsNoFind = {
63
+ readonly personal: PersonalSubject;
64
+ readonly anonymize?: () => unknown | Promise<unknown>;
65
+ } | {
66
+ readonly personal: "ref";
67
+ } | {
68
+ readonly personal: false;
69
+ readonly reason: string;
70
+ readonly anonymize?: () => unknown | Promise<unknown>;
71
+ } | NoPersonalAnnotation;
72
+ export type RetentionDef = {
73
+ readonly keepFor: string;
74
+ readonly strategy: "hardDelete" | "softDelete" | "anonymize" | "blockDelete";
75
+ readonly reference?: string;
76
+ };
77
+ export type TextFieldDef = {
78
+ readonly type: "text";
79
+ /** Optional free-text semantics for the AI agent manifest — never rendered;
80
+ * the i18n label is what users see. */
81
+ readonly description?: string;
82
+ readonly maxLength?: number;
83
+ readonly required?: boolean;
84
+ readonly searchable?: boolean;
85
+ readonly sortable?: boolean;
86
+ /** Author erlaubt Screen-Filter auf diesem Feld (Tier 2.7c).
87
+ * Boot-Validator weist Filter mit `filterable !== true` zurück.
88
+ * Default: false — analog zu `sortable`, opt-in. */
89
+ readonly filterable?: boolean;
90
+ readonly encrypted?: boolean;
91
+ readonly sensitive?: boolean;
92
+ /** A later run reads this field's value as an instruction — prompt, rule,
93
+ * template. Every create/update whose payload writes it requires agent.risk
94
+ * "high" on the directly-dispatched entry handler; a revert doesn't undo a
95
+ * run that already read the compromised value. See irreversible-operation-gate. */
96
+ readonly readAsInstruction?: boolean;
97
+ /** "password" is a pure render hint (masked input) — no storage semantics. */
98
+ readonly format?: "email" | "url" | "phone" | "password";
99
+ readonly default?: string;
100
+ readonly access?: FieldAccess;
101
+ /** Mehrzeiliger Text — DefaultInput rendert dann ein <textarea> statt
102
+ * <input type="text">. `true` = Default 4 Zeilen, `{ rows: N }` =
103
+ * explizite Höhe. Search/sort/encrypt verhalten sich unverändert
104
+ * identisch zu single-line — nur die Render-Surface wechselt. */
105
+ readonly multiline?: boolean | {
106
+ readonly rows?: number;
107
+ };
108
+ } & ResolvedPiiFlags;
109
+ /**
110
+ * Long-form text content — source-code, markdown, blog-posts, email-
111
+ * templates, anything that can be megabytes large. Bewusst MINIMALE
112
+ * Surface gegen `text`:
113
+ *
114
+ * - **Kein `sortable`**: ORDER BY auf 100 KB-Strings kostet I/O ohne
115
+ * sinnvolles UX-Outcome (lex-Sortierung von Code ist Nonsense).
116
+ * - **Kein `searchable`**: ILIKE/Substring-Suche auf langen Texten
117
+ * skaliert nicht. Wer wirklich Volltextsuche will, nimmt den
118
+ * SearchAdapter (Meilisearch) — der hat eine eigene Pipeline mit
119
+ * Tokenizer + Index, NICHT diesen field-flag.
120
+ * - **Kein `filterable`**: WHERE auf langen Strings same Story wie
121
+ * sortable.
122
+ * - **Kein `format`**: email/url/phone sind kurz definierte Inputs,
123
+ * longText ist per Definition unstrukturiert.
124
+ *
125
+ * Type-level enforcement statt convention: wer sortable/searchable
126
+ * braucht, nimmt `text` (mit den entsprechenden Skalierungs-Trade-offs).
127
+ * DB-mapping ist identisch zu text (Postgres `text` ist unbounded).
128
+ */
129
+ export type LongTextFieldDef = {
130
+ readonly type: "longText";
131
+ readonly description?: string;
132
+ /** Optionale soft-Cap. Default unbounded (= Postgres-text-limit, 1 GB).
133
+ * Nützlich für defensive Caps wie 1 MB damit ein verirrter Browser-
134
+ * Paste nicht die DB sprengt. */
135
+ readonly maxLength?: number;
136
+ readonly required?: boolean;
137
+ readonly encrypted?: boolean;
138
+ readonly sensitive?: boolean;
139
+ /** A later run reads this field's value as an instruction — prompt, rule,
140
+ * template. Every create/update whose payload writes it requires agent.risk
141
+ * "high" on the directly-dispatched entry handler; a revert doesn't undo a
142
+ * run that already read the compromised value. See irreversible-operation-gate. */
143
+ readonly readAsInstruction?: boolean;
144
+ readonly default?: string;
145
+ readonly access?: FieldAccess;
146
+ readonly multiline?: boolean | {
147
+ readonly rows?: number;
148
+ };
149
+ } & ResolvedPiiFlags;
150
+ export type BooleanFieldDef = {
151
+ readonly type: "boolean";
152
+ readonly description?: string;
153
+ readonly required?: boolean;
154
+ readonly sortable?: boolean;
155
+ readonly filterable?: boolean;
156
+ readonly sensitive?: boolean;
157
+ readonly default?: boolean;
158
+ readonly access?: FieldAccess;
159
+ };
160
+ export type SelectOptionTone = "ok" | "warn" | "bad" | "neutral";
161
+ export type SelectFieldDef<TOptions extends readonly string[] = readonly string[]> = {
162
+ readonly type: "select";
163
+ readonly description?: string;
164
+ readonly options: TOptions;
165
+ readonly required?: boolean;
166
+ readonly sortable?: boolean;
167
+ readonly filterable?: boolean;
168
+ readonly sensitive?: boolean;
169
+ readonly default?: TOptions[number];
170
+ readonly access?: FieldAccess;
171
+ /** Renderer metadata: `"radio"` always renders the option set as a visible
172
+ * radio group, `"dropdown"` always renders the combobox. Omitted = the
173
+ * renderer's own heuristic picks (radio group for a few short labels,
174
+ * dropdown otherwise). A `"radio"` group with many or long labels wraps —
175
+ * the author owns that trade-off, the renderer does not override it. */
176
+ readonly display?: "radio" | "dropdown";
177
+ /** Status tone per option, shown as a toned badge with dot in lists and
178
+ * detail bands. Options without an entry keep the renderer's own
179
+ * value-based heuristic. */
180
+ readonly optionTones?: {
181
+ readonly [K in TOptions[number]]?: SelectOptionTone;
182
+ };
183
+ } & ResolvedPiiFlags;
184
+ export type MultiSelectFieldDef<TOptions extends readonly string[] = readonly string[]> = {
185
+ readonly type: "multiSelect";
186
+ readonly description?: string;
187
+ readonly options: TOptions;
188
+ readonly required?: boolean;
189
+ readonly filterable?: boolean;
190
+ readonly sensitive?: boolean;
191
+ /** Default-Auswahl. Jeder Eintrag muss in `options` sein (Boot-Validator). */
192
+ readonly default?: readonly TOptions[number][];
193
+ readonly access?: FieldAccess;
194
+ /** Renderer metadata: `"checkboxes"` renders every option as a visible
195
+ * checkbox with a select-all toggle. Default (omitted) stays the combobox
196
+ * dropdown. */
197
+ readonly display?: "checkboxes" | "dropdown";
198
+ /** Renderer metadata: column count for the `"checkboxes"` grid at the widest
199
+ * breakpoint; narrow viewports always collapse to one column. Omitted =
200
+ * the renderer picks a sensible count. Ignored unless
201
+ * `display: "checkboxes"`. */
202
+ readonly columns?: 1 | 2 | 3 | 4;
203
+ /** Renderer metadata: how many grid rows stay visible before the
204
+ * `"checkboxes"` grid becomes scrollable. Omitted = the grid grows with its
205
+ * content and never scrolls. Ignored unless `display: "checkboxes"`. */
206
+ readonly maxRows?: number;
207
+ } & ResolvedPiiFlags;
208
+ /**
209
+ * Storage: `integer` flag decides the Postgres column type, not just Zod
210
+ * validation. `integer: true` → `integer` column (32-bit, ~±2.1 billion),
211
+ * write-boundary enforces `.int()`. Omitted/`false` (the default) →
212
+ * `double precision` column — fractional values are accepted end to end.
213
+ * Need exact decimal storage instead of a binary float (money-adjacent
214
+ * math, no representation error)? Use `createDecimalField` (`numeric`).
215
+ */
216
+ export type NumberFieldDef = {
217
+ readonly type: "number";
218
+ readonly description?: string;
219
+ readonly required?: boolean;
220
+ readonly sortable?: boolean;
221
+ readonly filterable?: boolean;
222
+ readonly sensitive?: boolean;
223
+ readonly default?: number;
224
+ readonly access?: FieldAccess;
225
+ readonly min?: number;
226
+ /** Upper bound at the write boundary (Zod `.max`). */
227
+ readonly max?: number;
228
+ /** `true` → `integer` column + `.int()` Zod validation. Omitted/`false` →
229
+ * `double precision` column, fractional values allowed. */
230
+ readonly integer?: boolean;
231
+ /** Display-only suffix: a static string, or a sibling field name whose
232
+ * value supplies it. Never converts the stored number. */
233
+ readonly unit?: string | {
234
+ readonly field: string;
235
+ };
236
+ /** Thousands-grouping in the rendered display (Intl `useGrouping`).
237
+ * Default `true`. Set `false` for values that read wrong grouped (e.g. a
238
+ * model year: "2021", not "2.021"). Never affects the stored value. */
239
+ readonly grouping?: boolean;
240
+ } & ResolvedPiiFlags;
241
+ /**
242
+ * 64-bit-Integer-Spalte fuer Audit-Counter, Byte-Sizes, Event-IDs und
243
+ * andere Werte die >2^31 (~2.1 Mrd) wandern koennen. Storage als
244
+ * Postgres `bigint`, JS-Round-trip als `number` (mode:"number" — sicher
245
+ * bis 2^53 ≈ 9 PB, JSON-serialisierbar). Wer >2^53 braucht (rare),
246
+ * nutzt einen `text`-Field mit eigenem Codec.
247
+ *
248
+ * Vorrang vor `NumberFieldDef`-(integer 32-bit-Cap, ~2.1 GB) immer dann
249
+ * wenn der Wert physisch ueber dieses Limit klettern kann: Bytes,
250
+ * Events, Counters in High-Throughput-Apps, Cumulative-Sums. Money
251
+ * hat dafuer den eigenen `MoneyFieldDef` (mit Currency-Spalte).
252
+ */
253
+ export type BigIntFieldDef = {
254
+ readonly type: "bigInt";
255
+ readonly description?: string;
256
+ readonly required?: boolean;
257
+ readonly sortable?: boolean;
258
+ readonly filterable?: boolean;
259
+ readonly sensitive?: boolean;
260
+ readonly default?: number;
261
+ readonly access?: FieldAccess;
262
+ } & ResolvedPiiFlags;
263
+ /**
264
+ * Exact decimal — Postgres `numeric(precision, scale)`. For values that need
265
+ * fractional precision the integer `number` field (32-bit int) and `money`
266
+ * field (BIGINT minor units + currency) can't hold: interest rates,
267
+ * percentages, ratios, measurements.
268
+ *
269
+ * `precision` = total significant digits, `scale` = digits after the decimal
270
+ * point (both required — no silent default that could truncate). pg returns
271
+ * `numeric` as a string to preserve precision; the read-codec surfaces it as
272
+ * a JS `number` (safe ≤ 2^53, same trade-off as `bigInt` mode:"number" — a
273
+ * value past that boundary loses precision, so keep `precision - scale` ≤ 15).
274
+ */
275
+ export type DecimalFieldDef = {
276
+ readonly type: "decimal";
277
+ readonly description?: string;
278
+ readonly precision: number;
279
+ readonly scale: number;
280
+ readonly required?: boolean;
281
+ readonly sortable?: boolean;
282
+ readonly filterable?: boolean;
283
+ readonly sensitive?: boolean;
284
+ readonly default?: number;
285
+ readonly access?: FieldAccess;
286
+ } & ResolvedPiiFlags;
287
+ /** Where a money field's currency comes from. `"tenant"` is the
288
+ * tenant-settings bundle's per-tenant currency (`tenant-settings:config:
289
+ * currency`, kumiko-framework#2933); `"literal"` names a fixed ISO code and
290
+ * is checked against the app's `currencies` list at boot
291
+ * (kumiko-framework#2839). Discriminated union so a `{ kind: "field", of }`
292
+ * variant (amount and currency-picker side by side) can still be added
293
+ * without a breaking change, once a multi-currency form actually exists. */
294
+ export type MoneyCurrencySource = {
295
+ readonly kind: "tenant";
296
+ } | {
297
+ readonly kind: "literal";
298
+ readonly code: string;
299
+ };
300
+ /** Qualified config key a `{ kind: "tenant" }` MoneyCurrencySource resolves
301
+ * against — the tenant-settings bundle's per-tenant currency. Lives here
302
+ * (client-safe) rather than framework/engine so renderer code can read it
303
+ * without a runtime-only import (kumiko-framework#2937). */
304
+ export declare const TENANT_CURRENCY_CONFIG_KEY = "tenant-settings:config:currency";
305
+ export type MoneyFieldDef = {
306
+ readonly type: "money";
307
+ readonly description?: string;
308
+ readonly required?: boolean;
309
+ readonly sortable?: boolean;
310
+ readonly filterable?: boolean;
311
+ readonly sensitive?: boolean;
312
+ readonly access?: FieldAccess;
313
+ /** Declares where this field's currency comes from for a currently-empty
314
+ * value in an entityEdit form. Undefined = unchanged existing behaviour
315
+ * (`entity.defaultCurrency ?? "EUR"`); a stored value always keeps its
316
+ * own currency regardless of this declaration. */
317
+ readonly currency?: MoneyCurrencySource;
318
+ };
319
+ export type ReferenceFieldDef = {
320
+ readonly type: "reference";
321
+ readonly description?: string;
322
+ readonly entity: string;
323
+ readonly required?: boolean;
324
+ readonly filterable?: boolean;
325
+ readonly sensitive?: boolean;
326
+ readonly access?: FieldAccess;
327
+ /** Welches Feld der referenced Entity als Display-Label im
328
+ * Select-Dropdown erscheint. Default: "id". Boot-Validator pinst
329
+ * dass das Feld auf der referenced Entity existiert. */
330
+ readonly labelField?: string;
331
+ /** Multi-Reference (Tier 2.7e-Multi): Wert ist ein Array von UUIDs
332
+ * statt single UUID. Storage als jsonb-Array<uuid>. UI rendert
333
+ * Multi-Select-Combobox mit Tag-Anzeige der gewählten Items. */
334
+ readonly multiple?: boolean;
335
+ /** Include a row in this entity's result set on a text-search hit against
336
+ * the referenced entity's `labelField`, instead of only matching the UUID
337
+ * column itself (fw#2660). Requires an explicit `labelField` — the boot
338
+ * validator rejects it otherwise, since the default "id" is a UUID column
339
+ * and ILIKE against it would crash at runtime. */
340
+ readonly searchable?: true;
341
+ /** Order a list by the referenced entity's `labelField` instead of by the raw
342
+ * UUID column (fw#2741). Requires an explicit, non-"id" `labelField` — the
343
+ * boot validator rejects it otherwise, since ordering by a UUID produces a
344
+ * sequence that looks meaningful to the user but is arbitrary. */
345
+ readonly sortable?: true;
346
+ /** Fill the picker from this query handler instead of the referenced
347
+ * entity's own list handler (fw#2780) — for targets whose identity is
348
+ * composed from joined rows, where no single column of the referenced
349
+ * entity is a readable label. Fully-qualified QN
350
+ * ("<feature>:query:<short>"), boot-validated against the registered
351
+ * handlers; result contract `{ rows: { id, label }[] }`, called with
352
+ * `{ limit, search? }` like the default list handler. `id` rather than the
353
+ * `{ value, label }` of `DashboardFilterDefinition.optionsQuery`, because
354
+ * the stored value here IS the referenced row's id.
355
+ *
356
+ * Additive to `labelField`, which keeps serving the paths a query handler
357
+ * cannot back: list cells, `searchable` and `sortable` all resolve to an
358
+ * SQL column on the referenced table.
359
+ *
360
+ * The handler must return the row for an id that is already stored, even
361
+ * when it otherwise curates its result (say, to active records only) —
362
+ * the read-only display resolves a stored value against these rows and
363
+ * falls back to the raw id for one it does not find. */
364
+ readonly optionsQuery?: string;
365
+ } & ResolvedPiiFlags;
366
+ export type DefaultCurrency = "EUR" | "USD" | "GBP" | "CHF" | "JPY" | "SEK" | "NOK" | "DKK" | "PLN" | "CZK" | "CAD" | "AUD" | "NZD" | "CNY" | "INR";
367
+ type EmbeddedSubFieldBase = {
368
+ readonly required?: boolean;
369
+ readonly searchable?: boolean;
370
+ readonly access?: FieldAccess;
371
+ };
372
+ /** `money` is a signed integer amount in minor units (cents); which currency
373
+ * it means is defined by the owning aggregate (a head-level field or tenant
374
+ * config), not per row. Unlike the top-level `money` field (BIGINT + currency
375
+ * column) the value lives in jsonb, so it must stay within ±(2^53 − 1).
376
+ *
377
+ * `decimal` bounds a JSON number to `scale` fractional digits (0–15). `scale`
378
+ * is required — no silent default that could truncate — and there is no
379
+ * `precision`: jsonb has no numeric column behind it whose width `precision`
380
+ * could describe. */
381
+ export type EmbeddedSubFieldDef = (EmbeddedSubFieldBase & {
382
+ readonly type: "text" | "number" | "boolean" | "date" | "money" | "timestamp";
383
+ }) | (EmbeddedSubFieldBase & {
384
+ readonly type: "decimal";
385
+ readonly scale: number;
386
+ }) | (EmbeddedSubFieldBase & {
387
+ readonly type: "select";
388
+ readonly options: readonly string[];
389
+ }) | (EmbeddedSubFieldBase & {
390
+ readonly type: "reference";
391
+ readonly entity: string;
392
+ readonly labelField?: string;
393
+ /** Same contract as `ReferenceFieldDef.optionsQuery` (fw#2780). */
394
+ readonly optionsQuery?: string;
395
+ });
396
+ /** A cell computed from other cells of the same row. `from` names the
397
+ * sub-fields the operation reads (order matters for `subtract`). Renderer
398
+ * metadata — the derived value is rounded server- and client-side to the
399
+ * precision its target sub-field in `schema` declares (money → integer,
400
+ * decimal → `scale`) before it's validated. */
401
+ export type EmbeddedDerivedCellDef = {
402
+ readonly op: "multiply" | "sum" | "subtract";
403
+ readonly from: readonly string[];
404
+ };
405
+ export type EmbeddedFieldDef = {
406
+ readonly type: "embedded";
407
+ readonly description?: string;
408
+ readonly required?: boolean;
409
+ readonly sensitive?: boolean;
410
+ readonly schema: Readonly<Record<string, EmbeddedSubFieldDef>>;
411
+ readonly access?: FieldAccess;
412
+ /** A list of like-shaped objects instead of exactly one: the value is an
413
+ * array and every element is validated against `schema`. Storage stays
414
+ * jsonb, the column default becomes `[]` instead of `{}`. Built via
415
+ * `createEmbeddedListField()`.
416
+ *
417
+ * Only for rows that come into being with their head and stay immutable
418
+ * with it (posting lines, invoice positions). A row with its own lifetime
419
+ * (own from/to dates, own history) belongs in its own entity referencing
420
+ * the head — an embedded array is rewritten whole on every change and has
421
+ * no per-row history. */
422
+ readonly multiple?: boolean;
423
+ /** Minimum row count for an embedded list. Only valid with `multiple: true`. */
424
+ readonly minItems?: number;
425
+ /** Maximum row count for an embedded list. Only valid with `multiple: true`. */
426
+ readonly maxItems?: number;
427
+ /** Renderer metadata: sub-field name → how to compute its cell from other
428
+ * sub-fields of the same row, rounded to that sub-field's declared
429
+ * precision. Only valid with `multiple: true`. */
430
+ readonly derived?: Readonly<Record<string, EmbeddedDerivedCellDef>>;
431
+ /** Renderer metadata: numeric sub-field names to sum in a totals row. Only
432
+ * valid with `multiple: true`. */
433
+ readonly totals?: readonly string[];
434
+ /** Maps an embedded-list subfield name to a sibling top-level money field
435
+ * name — the sum of that subfield across all rows must equal the sibling
436
+ * field's amount. Validated both client- and server-side (schema-builder
437
+ * superRefine). Only meaningful with `multiple: true`. Compares against
438
+ * rounded `derived` cells, i.e. "sum-of-rounded" not "round-of-sum" —
439
+ * see kumiko-framework#1866. */
440
+ readonly totalsMatch?: Readonly<Record<string, string>>;
441
+ } & ResolvedPiiFlags;
442
+ export type JsonbFieldDef = {
443
+ readonly type: "jsonb";
444
+ readonly description?: string;
445
+ readonly sensitive?: boolean;
446
+ /** A later run reads this field's value as an instruction — prompt, rule,
447
+ * template. Every create/update whose payload writes it requires agent.risk
448
+ * "high" on the directly-dispatched entry handler; a revert doesn't undo a
449
+ * run that already read the compromised value. See irreversible-operation-gate. */
450
+ readonly readAsInstruction?: boolean;
451
+ readonly access?: FieldAccess;
452
+ } & ResolvedPiiFlags;
453
+ export type DateFieldDef = {
454
+ readonly type: "date";
455
+ readonly description?: string;
456
+ readonly required?: boolean;
457
+ readonly sortable?: boolean;
458
+ readonly filterable?: boolean;
459
+ readonly sensitive?: boolean;
460
+ readonly access?: FieldAccess;
461
+ /** Erlaubte Datumsgrenzen als ISO `yyyy-mm-dd` (z.B. Geburtsdatum nicht
462
+ * in der Zukunft: `max` = heute). Begrenzt den Picker und wird vom
463
+ * Zod-Schema beim Write durchgesetzt. */
464
+ readonly min?: string;
465
+ readonly max?: string;
466
+ /** Format/Locale-Override für Anzeige und Eingabe-Parsing (z.B.
467
+ * "de-DE"). Default = App-Locale. */
468
+ readonly locale?: string;
469
+ } & ResolvedPiiFlags;
470
+ export type TimestampFieldDef = {
471
+ readonly type: "timestamp";
472
+ readonly description?: string;
473
+ readonly required?: boolean;
474
+ readonly sortable?: boolean;
475
+ readonly filterable?: boolean;
476
+ readonly sensitive?: boolean;
477
+ readonly access?: FieldAccess;
478
+ /**
479
+ * Marker: dieses Timestamp-Feld ist Wall-Clock-Zeit an einem Ort.
480
+ * Wert ist der Name des begleitenden tz-Felds (IANA-Zone).
481
+ *
482
+ * Beispiel: manuelles Pair
483
+ * { pickupAt: { type: "timestamp", locatedBy: "pickupTz" }, pickupTz: { type: "tz" } }
484
+ * — bevorzuge stattdessen `createLocatedTimestampField()`.
485
+ */
486
+ readonly locatedBy?: string;
487
+ /** Erlaubte Grenzen als ISO-Datetime. Begrenzt den Picker auf
488
+ * Tages-Granularität; die exakte Uhrzeit-Grenze setzt das Zod-Schema
489
+ * beim Write durch. */
490
+ readonly min?: string;
491
+ readonly max?: string;
492
+ /** Format/Locale-Override für Anzeige und Eingabe-Parsing. Default =
493
+ * App-Locale. */
494
+ readonly locale?: string;
495
+ } & ResolvedPiiFlags;
496
+ export type TzFieldDef = {
497
+ readonly type: "tz";
498
+ readonly description?: string;
499
+ readonly required?: boolean;
500
+ readonly sensitive?: boolean;
501
+ readonly access?: FieldAccess;
502
+ } & ResolvedPiiFlags;
503
+ export type LocatedTimestampFieldDef = {
504
+ readonly type: "locatedTimestamp";
505
+ readonly description?: string;
506
+ readonly required?: boolean;
507
+ readonly sortable?: boolean;
508
+ readonly filterable?: boolean;
509
+ readonly sensitive?: boolean;
510
+ readonly access?: FieldAccess;
511
+ /** Erlaubte Grenzen als ISO-Datetime (Wall-Clock). Begrenzt den Picker
512
+ * auf Tages-Granularität; die exakte Uhrzeit-Grenze setzt das Zod-Schema
513
+ * beim Write durch. */
514
+ readonly min?: string;
515
+ readonly max?: string;
516
+ /** Format/Locale-Override für Anzeige und Eingabe-Parsing. Default =
517
+ * App-Locale. */
518
+ readonly locale?: string;
519
+ } & ResolvedPiiFlags;
520
+ export type FileFieldDef = {
521
+ readonly type: "file";
522
+ readonly description?: string;
523
+ readonly required?: boolean;
524
+ readonly maxSize?: string;
525
+ readonly accept?: readonly string[];
526
+ readonly access?: FieldAccess;
527
+ } & ResolvedPiiFlags;
528
+ export type ImageFieldDef = {
529
+ readonly type: "image";
530
+ readonly description?: string;
531
+ readonly required?: boolean;
532
+ readonly maxSize?: string;
533
+ readonly accept?: readonly string[];
534
+ /** Named derived versions of the uploaded image. The name is the only
535
+ * thing a caller may ask for — the spec lives here, never in a request,
536
+ * so nobody can drive arbitrary renders. Boot-validated. */
537
+ readonly variants?: Readonly<Record<string, VariantSpec>>;
538
+ /** Forwarded to the file input's `capture` attribute so a phone opens the
539
+ * camera instead of the file picker. */
540
+ readonly capture?: "environment" | "user";
541
+ readonly access?: FieldAccess;
542
+ } & ResolvedPiiFlags;
543
+ export type FilesFieldDef = {
544
+ readonly type: "files";
545
+ readonly description?: string;
546
+ readonly maxSize?: string;
547
+ readonly accept?: readonly string[];
548
+ readonly maxCount?: number;
549
+ readonly access?: FieldAccess;
550
+ } & ResolvedPiiFlags;
551
+ export type ImagesFieldDef = {
552
+ readonly type: "images";
553
+ readonly description?: string;
554
+ readonly maxSize?: string;
555
+ readonly accept?: readonly string[];
556
+ readonly maxCount?: number;
557
+ readonly variants?: Readonly<Record<string, VariantSpec>>;
558
+ readonly access?: FieldAccess;
559
+ } & ResolvedPiiFlags;
560
+ export type FieldDefinition = TextFieldDef | LongTextFieldDef | BooleanFieldDef | SelectFieldDef | MultiSelectFieldDef | NumberFieldDef | BigIntFieldDef | DecimalFieldDef | MoneyFieldDef | ReferenceFieldDef | EmbeddedFieldDef | JsonbFieldDef | DateFieldDef | TimestampFieldDef | TzFieldDef | LocatedTimestampFieldDef | FileFieldDef | ImageFieldDef | FilesFieldDef | ImagesFieldDef;
561
+ export declare const FIELD_TYPE_NAMES: readonly FieldDefinition["type"][];
562
+ /** FieldDefinition as an entity-less inline form screen (actionForm,
563
+ * secretMint) may declare it: a `money` field there has no entity to borrow
564
+ * `defaultCurrency` from, so it must name its own currency source, or the
565
+ * form seeds a bare `0` that the handler's zod schema then rejects
566
+ * (kumiko-framework#2839). Entity fields keep the optional `currency` —
567
+ * `entity.defaultCurrency` is already boot-enforced for them. */
568
+ export type FormFieldDefinition = Exclude<FieldDefinition, MoneyFieldDef> | (MoneyFieldDef & {
569
+ readonly currency: MoneyCurrencySource;
570
+ });
571
+ export type AnyFileFieldDef = FileFieldDef | ImageFieldDef | FilesFieldDef | ImagesFieldDef;
572
+ export declare const NO_WIDGET_FIELD_TYPES: readonly FieldDefinition["type"][];
573
+ /** Display type a derived value formats as — drives the column's renderer
574
+ * choice in the view-model, parallel to FieldDefinition["type"]. Single-column
575
+ * types only: `money` is excluded because it needs a `<name>Currency`
576
+ * companion column a derived field has no place to put — use `number`/
577
+ * `decimal` plus a `{ format: "currency" }` column renderer instead. */
578
+ export type DerivedValueType = "text" | "number" | "decimal" | "boolean" | "date" | "timestamp";
579
+ /** Clock injected into `derive` — never read `Temporal.Now`/`Date` inside a
580
+ * derive body (no-date-api guard + testability). The list-query handler passes
581
+ * the read-time instant; unit tests pass a fixed one. */
582
+ export type DeriveContext = {
583
+ readonly asOf: Temporal.Instant;
584
+ };
585
+ export type DerivedFieldDef = {
586
+ readonly valueType: DerivedValueType;
587
+ /** Pure function of the stored row + clock. Returns the JSON-safe display
588
+ * value (e.g. integer minor units for a currency column, ISO string for a
589
+ * `date`). */
590
+ readonly derive: (row: Readonly<Record<string, unknown>>, ctx: DeriveContext) => unknown;
591
+ };
592
+ export type DerivedFieldsMap = Readonly<Record<string, DerivedFieldDef>>;
593
+ /** Client-facing projection of DerivedFieldDef — `derive` is server-only and
594
+ * not JSON-safe (would trip the output-walk guard), so the browser schema
595
+ * only ever carries `valueType`. A real `Pick`, not a same-shape cast: TS
596
+ * itself proves `derive` isn't there instead of a `{ valueType } as
597
+ * DerivedFieldDef` cast lying about a field that's actually missing. */
598
+ export type ClientDerivedFieldDef = Pick<DerivedFieldDef, "valueType">;
599
+ export type TransitionMap = Readonly<Record<string, readonly string[]>>;
600
+ /** Composite-Index auf einer Entity. Spalten werden via field-Name
601
+ * referenziert (camelCase). buildEntityTable mapped sie auf snake_case-
602
+ * Spaltennamen und benennt den Index nach Convention:
603
+ *
604
+ * <table>_<col1>_<col2>_idx (non-unique)
605
+ * <table>_<col1>_<col2>_unique (unique)
606
+ *
607
+ * Eine `name`-Override ist erlaubt — Convention-Bruch in Bestandscode
608
+ * vermeidet Migration-Churn beim Refactor.
609
+ *
610
+ * Single-column indices über `tenantId` sind redundant (buildEntityTable
611
+ * legt die immer automatisch an); die Boot-Validation warnt (außer
612
+ * `{ unique: true }` — semantische 1:1-Constraint, kein Performance-Hint). */
613
+ export type EntityIndexDef = {
614
+ readonly columns: readonly [string, ...string[]];
615
+ readonly unique?: boolean;
616
+ readonly name?: string;
617
+ /**
618
+ * Optional SQL-Fragment fuer Partial-Index — `CREATE [UNIQUE] INDEX
619
+ * ... WHERE <condition>`. Postgres-Pattern fuer "Index nur unter
620
+ * bestimmten Bedingungen", typisches Beispiel: ExportJob-Idempotency
621
+ * `UNIQUE(userId) WHERE status IN ('pending', 'running')`.
622
+ *
623
+ * Caller baut das Fragment via drizzle-orm `sql\`...\``-Tagged-
624
+ * Template. table-builder.ts emittiert `.where(def.where)` auf den
625
+ * Drizzle-IndexBuilder — wirkt sowohl fuer unique- als auch fuer
626
+ * non-unique-Indexes (PG erlaubt beides; non-unique partial nutzt
627
+ * man z.B. fuer scharfe BTREE-Indexes nur auf einer Status-Teilmenge
628
+ * statt voller Tabelle).
629
+ */
630
+ readonly where?: unknown;
631
+ };
632
+ export type FieldsMap = Readonly<Record<string, FieldDefinition>>;
633
+ export type ParentRefDef = {
634
+ /** Field holding the name of the host entity this row hangs off. */
635
+ readonly entityTypeField: string;
636
+ /** Field holding the host row's id. */
637
+ readonly entityIdField: string;
638
+ /** Narrow which registered entities may act as host. Omitted means every
639
+ * registered entity is a candidate, which also widens the read-gate's SQL. */
640
+ readonly allowedTypes?: readonly string[];
641
+ };
642
+ export type EntityDefinition<F extends FieldsMap = FieldsMap, T extends EntityTenancy = EntityTenancy> = {
643
+ readonly table?: string;
644
+ readonly fields: F;
645
+ readonly softDelete?: boolean;
646
+ /** "tenant" (default): auto-filtered by every db accessor. "global": shared across
647
+ * tenants, reachable only via `db.global(table)`. Not named `scope` — config keys use that. */
648
+ readonly tenancy?: T;
649
+ /** This aggregate's event stream lives on SYSTEM_TENANT_ID rather than the
650
+ * creator's tenant. Opt-in per entity (NOT inherited from r.systemScope()):
651
+ * only for genuinely tenant-independent aggregates like `user`. The first
652
+ * event (create) is what's routed; updates resolve the stream tenant upstream. */
653
+ readonly systemStream?: boolean;
654
+ readonly searchWeight?: number;
655
+ readonly defaultCurrency?: string;
656
+ /** Allowed state transitions per field. Boot validates against select options. */
657
+ readonly transitions?: Readonly<Record<string, TransitionMap>>;
658
+ /** Composite-Indices über mehrere Felder. Single-column FK-Indices und
659
+ * der tenant_id-Index werden weiterhin automatisch von buildEntityTable
660
+ * angelegt — diese Liste ist nur für Custom-Indices die der Author
661
+ * explizit deklariert (z.B. `{ unique: true, columns: ["key", "tenantId", "userId"] }`). */
662
+ readonly indexes?: readonly EntityIndexDef[];
663
+ /**
664
+ * PK-Typ der Entity.
665
+ * - `"serial"` (default): bigserial integer — schneller, kompakter, perfekt für klassische CRUD-Entities.
666
+ * - `"uuid"`: uuid mit `gen_random_uuid()` default — verpflichtend für Entities deren `id` als
667
+ * Foreign-Key-Wert in multi-tenant Kontexten reist (z.B. `tenant.id` IS der `tenantId`). Auch für
668
+ * ES-Aggregate (Phase 2+) notwendig, da Events per UUID aggregiert werden.
669
+ */
670
+ readonly idType?: "serial" | "uuid";
671
+ /**
672
+ * Row-level ownership rules (H.2). read runs as WHERE-predicate on list/
673
+ * detail/queryProjection, scoping which rows the caller sees. write runs
674
+ * pre-save on create/update/delete, scoping which rows the caller may
675
+ * modify (Straddle-safe, multi-role atomic — see engine/ownership.ts).
676
+ *
677
+ * Keys are role names; rules use the `from()` helper or `{ where }`
678
+ * escape hatch. Entity-level ownership is AND-ed with tenant isolation —
679
+ * a user's tenant filter still applies first.
680
+ */
681
+ readonly access?: {
682
+ readonly read?: OwnershipMap;
683
+ readonly write?: OwnershipMap;
684
+ };
685
+ /**
686
+ * Join-row carrier reference. Rows of this entity hang off a host row named
687
+ * by two of the entity's own fields. Both paths derive one visibility gate
688
+ * from this single declaration: write handlers deny with NotFoundError, and
689
+ * list/detail filter in SQL against the host entity's own read path (tenant
690
+ * scope, soft-delete, `access.read` ownership). A host type that names no
691
+ * registered entity is denied.
692
+ */
693
+ readonly parentRef?: ParentRefDef;
694
+ /**
695
+ * Default-Retention-Policy fuer diese Entity. Tenant-Admin kann via
696
+ * Compliance-Profile + Tenant-Override (Sprint 2) uebersteuern.
697
+ * Cleanup-Job (Sprint 2) verarbeitet die Strategy:
698
+ *
699
+ * - "hardDelete" → Row physisch weg nach keepFor
700
+ * - "softDelete" → deletedAt = now() (mit core-soft-delete-Feature)
701
+ * - "anonymize" → Felder mit `anonymize`-Funktion ueberschrieben,
702
+ * Row bleibt
703
+ * - "blockDelete" → Cleanup-Job ignoriert; User-Forget loest
704
+ * stattdessen anonymize aus. Buchhaltung, Mandate,
705
+ * Patientenakten.
706
+ *
707
+ * Siehe docs/plans/features/core-data-retention.md.
708
+ */
709
+ readonly retention?: RetentionDef;
710
+ /**
711
+ * Read-time computed fields, keyed by name. Not stored, not a DB column,
712
+ * not writable — appended to each row by the list/detail query handler and
713
+ * nameable as a column in a declarative `entityList`. See DerivedFieldDef.
714
+ */
715
+ readonly derivedFields?: DerivedFieldsMap;
716
+ /** Optional free-text semantics for the AI agent manifest — see TextFieldDef.description. */
717
+ readonly description?: string;
718
+ /**
719
+ * Opts this entity's rows into the `tenant-handover` bundled feature
720
+ * (kumiko-framework#3035): a row-bound-grant-legitimated ownership change
721
+ * from an anonymous/public tenant to a freshly signed-up account's tenant.
722
+ * An entity reachable from a transfer's root — the root itself, or any
723
+ * entity whose `parentRef` names the root's type — that is NOT declared
724
+ * `transferable: true` makes the whole transfer fail with a named error
725
+ * instead of silently leaving that entity's rows behind.
726
+ */
727
+ readonly transferable?: boolean;
728
+ };
729
+ export {};