@cosmicdrift/kumiko-types 0.270.0 → 0.274.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-types",
3
- "version": "0.270.0",
3
+ "version": "0.274.0",
4
4
  "description": "Framework-Type-Definitions für Kumiko — FeatureDefinition, BootCheck-Types und die reinen Engine-Types. Erlaubt Downstream-Konsumenten, gegen die Type-Contracts zu bauen, ohne das ganze Framework-Package zu importieren. Enthaelt keine identitaets-sensitiven Runtime-Werte mehr (Error-Klassen leben seit #1629 in kumiko-framework, Brand-Symbole nutzen Symbol.for) und ist deshalb eine plain dependency, keine peerDependency.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -6,7 +6,7 @@ import type {
6
6
  EscapeHatchDeclaration,
7
7
  HandlerContext,
8
8
  QueryEvent,
9
- RateLimitOption,
9
+ RateLimitDeclaration,
10
10
  WriteEvent,
11
11
  WriteResult,
12
12
  } from "./handlers";
@@ -39,7 +39,7 @@ export type WriteHandlerDefinition<
39
39
  readonly description?: string;
40
40
  readonly agent?: AgentHandlerHints;
41
41
  readonly unsafeSkipTransitionGuard?: boolean;
42
- readonly rateLimit?: RateLimitOption;
42
+ readonly rateLimit?: RateLimitDeclaration;
43
43
  readonly escapeHatch?: EscapeHatchDeclaration;
44
44
  readonly handler: (
45
45
  event: WriteEvent<z.infer<TSchema>>,
@@ -67,7 +67,7 @@ export type WriteHandlerInput<
67
67
  readonly description?: string;
68
68
  readonly agent?: AgentHandlerHints;
69
69
  readonly unsafeSkipTransitionGuard?: boolean;
70
- readonly rateLimit?: RateLimitOption;
70
+ readonly rateLimit?: RateLimitDeclaration;
71
71
  readonly escapeHatch?: EscapeHatchDeclaration;
72
72
  } & (
73
73
  | {
@@ -96,7 +96,7 @@ export type QueryHandlerDefinition<
96
96
  readonly access: AccessRule;
97
97
  readonly description?: string;
98
98
  readonly agent?: AgentHandlerHints;
99
- readonly rateLimit?: RateLimitOption;
99
+ readonly rateLimit?: RateLimitDeclaration;
100
100
  readonly escapeHatch?: EscapeHatchDeclaration;
101
101
  readonly handler: (
102
102
  query: QueryEvent<z.infer<TSchema>>,
@@ -125,7 +125,7 @@ export type StreamHandlerDefinition<
125
125
  readonly name: TName;
126
126
  readonly schema: TSchema;
127
127
  readonly access: AccessRule;
128
- readonly rateLimit?: RateLimitOption;
128
+ readonly rateLimit?: RateLimitDeclaration;
129
129
  // Stream handlers can't reach db.global() (that gate is write-only), but
130
130
  // they can still switch identity to SYSTEM via ctx.queryAs — this opts
131
131
  // in, same contract as WriteHandlerDefinition.escapeHatch.
@@ -1,24 +1,27 @@
1
1
  import type { EntityDefinition } from "./fields";
2
- import type { AccessRule, AgentHandlerHints, QueryHandlerDef, WriteHandlerDef } from "./handlers";
2
+ import type {
3
+ AccessRule,
4
+ AgentHandlerHints,
5
+ EscapeHatchDeclaration,
6
+ QueryHandlerDef,
7
+ WriteHandlerDef,
8
+ } from "./handlers";
3
9
 
4
10
  export type EntityHandlerOptions = {
5
11
  readonly access: AccessRule;
6
12
  readonly description?: string;
7
13
  readonly agent?: AgentHandlerHints;
8
- /** Reads and writes across every tenant instead of the caller's own — for a
9
- * SystemAdmin-only operator handler over an otherwise tenant-scoped entity.
10
- * Scope this to the ONE handler that needs it rather than making the whole
11
- * feature r.systemScope(), which would drop tenant isolation from every
12
- * other handler the feature registers too. This only lifts row filtering;
13
- * who may call the handler at all stays gated by `access`.
14
- *
15
- * On write handlers it additionally addresses the event stream by the
16
- * target row's tenant instead of the acting user's, so update/delete/
17
- * restore hit the row's own stream. `create` has no target row and stays
18
- * on the acting user's tenant. On write handlers this also satisfies
19
- * entity write-ownership rules that compare against the acting user's
20
- * tenant, so `access` is the only remaining gate — grant it to operator
21
- * roles only. */
14
+ /** Lifts row filtering for this ONE handler across every tenant instead of
15
+ * the caller's own — for a SystemAdmin-only operator handler over an
16
+ * otherwise tenant-scoped entity. Every use reports an
17
+ * `acknowledge-cross-tenant` escape-hatch audit event. On write handlers
18
+ * update/delete/restore address the target row's own tenant stream.
19
+ * `access` stays the only caller gate. Unlike `escapeHatch` on a
20
+ * hand-written handler this does NOT grant `ctx.db.unsafeRaw`,
21
+ * `db.global()` writes or identity switches. */
22
+ readonly escapeHatch?: EscapeHatchDeclaration;
23
+ /** @deprecated Use `escapeHatch: { reason }` removed in a future release;
24
+ * run `scripts/codemod/migrate-cross-tenant.ts`. */
22
25
  readonly crossTenant?: boolean;
23
26
  };
24
27
 
@@ -74,7 +74,7 @@ export type EventStoreExecutor = {
74
74
  filter?:
75
75
  | {
76
76
  readonly field: string;
77
- readonly op: "eq" | "ne" | "lt" | "gt" | "in";
77
+ readonly op: "eq" | "ne" | "lt" | "gt" | "lte" | "gte" | "in";
78
78
  readonly value: unknown;
79
79
  }
80
80
  | undefined;
@@ -83,7 +83,7 @@ export type EventStoreExecutor = {
83
83
  filters?:
84
84
  | ReadonlyArray<{
85
85
  readonly field: string;
86
- readonly op: "eq" | "ne" | "lt" | "gt" | "in";
86
+ readonly op: "eq" | "ne" | "lt" | "gt" | "lte" | "gte" | "in";
87
87
  readonly value: unknown;
88
88
  }>
89
89
  | undefined;
package/src/feature.ts CHANGED
@@ -45,7 +45,7 @@ import type {
45
45
  QualifiedEventName,
46
46
  QueryHandlerDef,
47
47
  QueryHandlerFn,
48
- RateLimitOption,
48
+ RateLimitDeclaration,
49
49
  StreamHandlerDef,
50
50
  StreamHandlerFn,
51
51
  WriteHandlerDef,
@@ -457,7 +457,7 @@ export type FeatureRegistrar<TFeature extends string = string> = {
457
457
  handler: WriteHandlerFn<z.infer<TSchema>>,
458
458
  options: {
459
459
  access: AccessRule;
460
- rateLimit?: RateLimitOption;
460
+ rateLimit?: RateLimitDeclaration;
461
461
  description?: string;
462
462
  agent?: AgentHandlerHints;
463
463
  escapeHatch?: EscapeHatchDeclaration;
@@ -473,7 +473,7 @@ export type FeatureRegistrar<TFeature extends string = string> = {
473
473
  handler: QueryHandlerFn<z.infer<TSchema>>,
474
474
  options: {
475
475
  access: AccessRule;
476
- rateLimit?: RateLimitOption;
476
+ rateLimit?: RateLimitDeclaration;
477
477
  outputSchema?: ZodType;
478
478
  description?: string;
479
479
  agent?: AgentHandlerHints;
@@ -490,7 +490,7 @@ export type FeatureRegistrar<TFeature extends string = string> = {
490
490
  handler: StreamHandlerFn<z.infer<TSchema>>,
491
491
  options: {
492
492
  access: AccessRule;
493
- rateLimit?: RateLimitOption;
493
+ rateLimit?: RateLimitDeclaration;
494
494
  escapeHatch?: EscapeHatchDeclaration;
495
495
  },
496
496
  ): HandlerRef;
package/src/handlers.ts CHANGED
@@ -287,6 +287,29 @@ export type NotifyFn = (notificationType: string, options: NotifyOptions) => Pro
287
287
  // Concrete implementation in bundled-features/delivery (cross-package boundary)
288
288
  export type NotifyFactory = (user: SessionUser, tenantId: TenantId) => NotifyFn;
289
289
 
290
+ export type EscapeHatchKind =
291
+ | "unsafe-raw"
292
+ | "acknowledge-cross-tenant"
293
+ | "global-write"
294
+ | "identity-switch"
295
+ | "unsafe-all-tenants";
296
+ export type EscapeHatchTarget = { readonly id: string; readonly tenantId: TenantId };
297
+ export type EscapeHatchUseEvent = {
298
+ readonly handler: string;
299
+ readonly kind: EscapeHatchKind;
300
+ readonly reason: string;
301
+ readonly tenantId: TenantId;
302
+ readonly actor: string;
303
+ readonly target?: EscapeHatchTarget;
304
+ };
305
+ export type EscapeHatchAuditSink = (event: EscapeHatchUseEvent) => Promise<void>;
306
+ // Bound to handler/tenant/actor of one invocation; called once per actual escape-hatch use.
307
+ export type EscapeHatchReporter = (
308
+ kind: EscapeHatchKind,
309
+ reason: string,
310
+ target?: EscapeHatchTarget,
311
+ ) => void;
312
+
290
313
  // Shared optional fields across all execution contexts
291
314
  type SharedContextFields = {
292
315
  readonly redis?: Redis;
@@ -323,6 +346,8 @@ type SharedContextFields = {
323
346
  readonly entityCache?: EntityCache;
324
347
  readonly notify?: NotifyFn;
325
348
  readonly _notifyFactory?: NotifyFactory;
349
+ // Wired at boot when the `audit` feature is mounted; absent → structured warn log.
350
+ readonly _escapeHatchAuditSink?: EscapeHatchAuditSink;
326
351
  // Tenant-scoped secrets accessor. Present when the app wired a
327
352
  // MasterKeyProvider at boot. Feature code reads ctx.secrets.get(...)
328
353
  // to pull a plaintext secret; Secret<string> carries the brand that
@@ -595,8 +620,9 @@ export type HandlerContext<TMap extends object = KumikoEventTypeMap> = SharedCon
595
620
  // name without the feature having to import the drizzle-table directly.
596
621
  //
597
622
  // Auto-applies tenant_id filter when the projection table has a tenant_id
598
- // column (or opt out with { unsafeAllTenants: true } for system-scoped reads
599
- // like cross-tenant analytics). Unknown projection name throws.
623
+ // column. { unsafeAllTenants: true } opts out but requires a grant —
624
+ // r.systemScope() or a declared escapeHatch on the handler (or hook).
625
+ // Unknown projection name throws.
600
626
  readonly queryProjection: <T = Record<string, unknown>>(
601
627
  qualifiedName: string,
602
628
  options?: { readonly unsafeAllTenants?: boolean },
@@ -690,14 +716,10 @@ export type JobContext = SharedContextFields & {
690
716
  readonly systemUser: SessionUser;
691
717
  readonly log: Logger;
692
718
  readonly triggeredBy: { readonly id: string; readonly tenantId: TenantId } | null;
693
- // Only present for jobs whose owning feature declares r.systemScope(),
694
- // mirroring HandlerContext.systemDb (dispatch-shared.ts buildHandlerContext).
695
- // assertTenantMatch()/acknowledgeCrossTenant() return a TenantDb, not the
696
- // raw DbConnection above job code that needs a DbRunner for a helper
697
- // like reindexEntity() reaches through `.raw` on that TenantDb. `.raw`
698
- // bypasses tenant filtering entirely, so it's only safe to hand to a
699
- // helper that filters by tenantId itself (as reindexEntity does) — never
700
- // pass it to code that trusts the connection to already be scoped.
719
+ // Only present for jobs whose owning feature declares r.systemScope().
720
+ // assertTenantMatch()/acknowledgeCrossTenant() return a TenantDb; a raw,
721
+ // tenant-unfiltered DbRunner instead comes from ctx.systemDb.unsafeRaw(reason)
722
+ // only safe for a helper that filters by tenantId itself (e.g. reindexEntity).
701
723
  readonly systemDb?: UncheckedSystemDb;
702
724
  readonly write: (qn: string, payload: unknown) => Promise<WriteResult>;
703
725
  readonly writeAs: (user: SessionUser, qn: string, payload: unknown) => Promise<WriteResult>;
@@ -1053,6 +1075,13 @@ export type RateLimitOption = {
1053
1075
  readonly cost?: number;
1054
1076
  };
1055
1077
 
1078
+ export type RateLimitDisabled = { readonly disabled: true; readonly reason: string };
1079
+ export type RateLimitDeclaration = RateLimitOption | RateLimitDisabled;
1080
+
1081
+ export function isRateLimitDisabled(v: RateLimitDeclaration | undefined): v is RateLimitDisabled {
1082
+ return v !== undefined && "disabled" in v && v.disabled === true;
1083
+ }
1084
+
1056
1085
  export type AgentRisk = "low" | "mid" | "high";
1057
1086
 
1058
1087
  /** Per-handler hints for the AI-agent manifest. `expose` overrides the
@@ -1075,7 +1104,7 @@ export type WriteHandlerDef = {
1075
1104
  readonly description?: string;
1076
1105
  readonly agent?: AgentHandlerHints;
1077
1106
  readonly unsafeSkipTransitionGuard?: boolean;
1078
- readonly rateLimit?: RateLimitOption;
1107
+ readonly rateLimit?: RateLimitDeclaration;
1079
1108
  readonly escapeHatch?: EscapeHatchDeclaration;
1080
1109
  // Set when the author wrote a `perform: stepsPipeline(...)` block. Boot-
1081
1110
  // validators (projection-allowlist) and Designer/AI tooling read this
@@ -1104,7 +1133,7 @@ export type QueryHandlerDef = {
1104
1133
  readonly access: AccessRule;
1105
1134
  readonly description?: string;
1106
1135
  readonly agent?: AgentHandlerHints;
1107
- readonly rateLimit?: RateLimitOption;
1136
+ readonly rateLimit?: RateLimitDeclaration;
1108
1137
  /** Zod schema of the handler's actual return value — the paged envelope
1109
1138
  * `{ rows, nextCursor, total? }` for a `definePagedQueryHandler`, or the
1110
1139
  * flat record (optionally `.nullable()`) for a plain query handler.
@@ -1125,7 +1154,7 @@ export type StreamHandlerDef = {
1125
1154
  readonly schema: ZodType;
1126
1155
  readonly handler: StreamHandlerFn;
1127
1156
  readonly access: AccessRule;
1128
- readonly rateLimit?: RateLimitOption;
1157
+ readonly rateLimit?: RateLimitDeclaration;
1129
1158
  // Stream handlers can't reach db.global() (that gate is write-only), but
1130
1159
  // they can still switch identity to SYSTEM via ctx.queryAs — this opts
1131
1160
  // in, same contract as WriteHandlerDef.escapeHatch.
package/src/screen.ts CHANGED
@@ -171,21 +171,23 @@ export type ListSortSpec = {
171
171
  // unterscheidet sie.
172
172
  //
173
173
  // Operatoren (Drizzle-konsistent):
174
- // eq/ne → field = value | field != value
175
- // lt/gt → field < value | field > value (numerisch / temporal)
176
- // in → field IN (...values), value muss readonly array sein
174
+ // eq/ne → field = value | field != value
175
+ // lt/gt → field < value | field > value (numerisch / temporal)
176
+ // lte/gte → field <= value | field >= value (numerisch / temporal)
177
+ // in → field IN (...values), value muss readonly array sein
177
178
  //
178
179
  // Field muss in der Entity existieren UND `filterable: true` haben
179
- // (Boot-Validator pinned beides). `lt`/`gt` nur auf vergleichbaren
180
- // Field-Types (number/money/date/timestamp/locatedTimestamp); auf
181
- // text/boolean/select/multiSelect lehnt der Validator das ab.
180
+ // (Boot-Validator pinned beides). `lt`/`gt`/`lte`/`gte` nur auf
181
+ // vergleichbaren Field-Types (number/money/date/timestamp/
182
+ // locatedTimestamp); auf text/boolean/select/multiSelect lehnt der
183
+ // Validator das ab.
182
184
  //
183
185
  // Security-Modell: Filter ist UX-Bucketing, KEINE Access-Boundary. Der
184
186
  // Server appliziert den filter aus dem Payload — der Client kann ihn
185
187
  // weglassen oder durch einen anderen ersetzen. Boundary bleiben
186
188
  // access-rule + Tenant-Scope; Felder mit Sicherheits-Bias (encrypted,
187
189
  // restricted) müssen dort geschützt werden, nicht über den Screen-Filter.
188
- export type ScreenFilterOp = "eq" | "ne" | "lt" | "gt" | "in";
190
+ export type ScreenFilterOp = "eq" | "ne" | "lt" | "gt" | "lte" | "gte" | "in";
189
191
  export type ScreenFilter = {
190
192
  readonly field: string;
191
193
  readonly op: ScreenFilterOp;
@@ -377,6 +379,32 @@ export type ToolbarAction =
377
379
  readonly style?: "primary" | "secondary" | "danger";
378
380
  };
379
381
 
382
+ // relatedList-only extension of ToolbarAction:
383
+ // a relatedList section's toolbar renders inside a projectionDetail, which
384
+ // has a record to evaluate against — a plain entityList/projectionList
385
+ // toolbar does not, so `visible`/`params` live here instead of on the base
386
+ // union. `visible` uses the same FieldCondition as header actions/
387
+ // RowAction; `params` reuses RowActionNavigate's RowFieldExtractor, applied
388
+ // to the enclosing record instead of a clicked row. Every field is optional,
389
+ // so a plain ToolbarAction is already a valid RelatedListToolbarAction —
390
+ // callers pass either type without a cast.
391
+ export type RelatedListToolbarAction =
392
+ | (Extract<ToolbarAction, { readonly kind: "navigate" }> & {
393
+ /** Conditional visibility, evaluated against the relatedList's parent
394
+ * record (the "Akte"). */
395
+ readonly visible?: FieldCondition;
396
+ /** Declarative URL search params extracted from the parent record,
397
+ * prefilling the target screen. Replaces the implicit
398
+ * `{ [parentParam]: parentId }` default when set. */
399
+ readonly params?: RowFieldExtractor;
400
+ })
401
+ | (Extract<ToolbarAction, { readonly kind: "writeHandler" }> & {
402
+ readonly visible?: FieldCondition;
403
+ })
404
+ | (Extract<ToolbarAction, { readonly kind: "drawer" }> & {
405
+ readonly visible?: FieldCondition;
406
+ });
407
+
380
408
  export type EntityListScreenDefinition = {
381
409
  readonly id: string;
382
410
  readonly type: "entityList";
@@ -384,6 +412,9 @@ export type EntityListScreenDefinition = {
384
412
  readonly detailFor?: string;
385
413
  readonly description?: string;
386
414
  readonly agent?: AgentHandlerHints;
415
+ /** Screen has no nav entry by design (opened via link or navved by the
416
+ * app); exempts it from the nav-area boot check. */
417
+ readonly dormant?: boolean;
387
418
  readonly entity: string;
388
419
  readonly columns: readonly ListColumnSpec[];
389
420
  // Row renderer (Desktop) — when omitted, renderer draws the default table
@@ -455,6 +486,17 @@ export type ListFacetSpec =
455
486
  readonly label: string;
456
487
  readonly trueLabel: string;
457
488
  readonly falseLabel: string;
489
+ }
490
+ | {
491
+ readonly field: string;
492
+ readonly type: "reference";
493
+ readonly label: string;
494
+ /** Entity name (same feature) or `feature:entity` (cross-feature), same
495
+ * convention as `ListColumnSpec.refEntity`. Options load at render time. */
496
+ readonly entity: string;
497
+ /** Row field on the referenced entity shown as the option label
498
+ * (default "id"). */
499
+ readonly labelField?: string;
458
500
  };
459
501
 
460
502
  export type ProjectionListScreenDefinition = {
@@ -464,6 +506,9 @@ export type ProjectionListScreenDefinition = {
464
506
  readonly detailFor?: string;
465
507
  readonly description?: string;
466
508
  readonly agent?: AgentHandlerHints;
509
+ /** Screen has no nav entry by design (opened via link or navved by the
510
+ * app); exempts it from the nav-area boot check. */
511
+ readonly dormant?: boolean;
467
512
  readonly query: string;
468
513
  readonly columns: readonly ListColumnSpec[];
469
514
  readonly rowRenderer?: PlatformComponent;
@@ -558,6 +603,9 @@ export type ProjectionDetailScreenDefinition = {
558
603
  readonly detailFor?: string;
559
604
  readonly description?: string;
560
605
  readonly agent?: AgentHandlerHints;
606
+ /** Screen has no nav entry by design (opened via link or navved by the
607
+ * app); exempts it from the nav-area boot check. */
608
+ readonly dormant?: boolean;
561
609
  readonly query: string;
562
610
  /** Query-payload key for the row-id. Default "id". */
563
611
  readonly idParam?: string;
@@ -763,6 +811,9 @@ export type DashboardScreenDefinition = {
763
811
  readonly detailFor?: string;
764
812
  readonly description?: string;
765
813
  readonly agent?: AgentHandlerHints;
814
+ /** Screen has no nav entry by design (opened via link or navved by the
815
+ * app); exempts it from the nav-area boot check. */
816
+ readonly dormant?: boolean;
766
817
  readonly panels: readonly DashboardPanelDefinition[];
767
818
  readonly filter?: DashboardFilterDefinition;
768
819
  readonly slots?: ScreenSlots;
@@ -835,7 +886,17 @@ export type EditFieldsSection = {
835
886
  * (subtitle-only section). */
836
887
  readonly description?: string;
837
888
  readonly columns?: number;
889
+ /** Mutually exclusive with `groups` — pass `[]` when using `groups`; the
890
+ * boot-validator rejects both non-empty or both empty. */
838
891
  readonly fields: readonly EditFieldSpec[];
892
+ /** Splits the section into multiple titled cards instead of one flat grid.
893
+ * Mutually exclusive with `fields`; fields named here still need to exist. */
894
+ readonly groups?: readonly {
895
+ readonly title: string;
896
+ readonly fields: readonly EditFieldSpec[];
897
+ /** Default 2. */
898
+ readonly columns?: number;
899
+ }[];
839
900
  /** Rendered left of the section title, `text-muted-foreground` — closed
840
901
  * IconKey vocabulary into the ICONS registry (renderer-web), analogous
841
902
  * to EditFieldSpec.icon. No title → no icon, and no heuristic derives
@@ -889,6 +950,15 @@ export type EditRelatedListSection = {
889
950
  readonly query: string;
890
951
  /** Query-payload key the parent record's id is passed under. Default "id". */
891
952
  readonly parentParam?: string;
953
+ /** Server-side WHERE clause pinning this section to the parent record —
954
+ * sent as `payload.filter: { field, op: "eq", value: parentId }`, kept
955
+ * out of the user-facet `filters` array so it can't be cleared by facet
956
+ * interaction. Lets a tab reuse the generic `<entity>:list` query instead
957
+ * of a bespoke child-rows handler. Mutually exclusive with `parentParam`
958
+ * (the boot-validator rejects both). `field` must be a real field on the
959
+ * entity behind `query`, and that query's Zod schema must accept
960
+ * `filter` (same requirement `filter`/`facets` already have). */
961
+ readonly parentFilter?: { readonly field: string };
892
962
  readonly columns: readonly ListColumnSpec[];
893
963
  readonly pageSize?: number;
894
964
  /** Initial sort on mount, applied client-side over the already-loaded rows
@@ -921,6 +991,11 @@ export type EditRelatedListSection = {
921
991
  * writeHandler action re-runs this section's own query, same as a
922
992
  * projectionList row action re-running its list query. */
923
993
  readonly rowActions?: readonly RowAction[];
994
+ /** Toolbar actions above the table — same type and dispatch semantics as
995
+ * `entityList`/`projectionList`'s `toolbarActions` ("+ Anlegen" etc.),
996
+ * plus `visible`/`params` evaluated against the parent record (see
997
+ * RelatedListToolbarAction). */
998
+ readonly toolbarActions?: readonly RelatedListToolbarAction[];
924
999
  /** Record field rendered as a count badge in the tab label when the
925
1000
  * enclosing `EditLayout.mode` is "tabs" (e.g. an open-items counter).
926
1001
  * Ignored outside tabs mode or when the field's value is not a finite
@@ -995,6 +1070,9 @@ export type EntityEditScreenDefinition = {
995
1070
  readonly detailFor?: string;
996
1071
  readonly description?: string;
997
1072
  readonly agent?: AgentHandlerHints;
1073
+ /** Screen has no nav entry by design (opened via link or navved by the
1074
+ * app); exempts it from the nav-area boot check. */
1075
+ readonly dormant?: boolean;
998
1076
  /** Derived by buildAppSchema from the navigate `params` targeting this
999
1077
  * screen — the only URL query keys the create form prefills. An authored
1000
1078
  * value is overwritten. */
@@ -1092,6 +1170,9 @@ export type ActionFormScreenDefinition = {
1092
1170
  readonly detailFor?: string;
1093
1171
  readonly description?: string;
1094
1172
  readonly agent?: AgentHandlerHints;
1173
+ /** Screen has no nav entry by design (opened via link or navved by the
1174
+ * app); exempts it from the nav-area boot check. */
1175
+ readonly dormant?: boolean;
1095
1176
  /** Derived by buildAppSchema — see EntityEditScreenDefinition.urlPrefillFields. */
1096
1177
  readonly urlPrefillFields?: readonly string[];
1097
1178
  /** Write-Handler-QN der bei Submit gerufen wird. Form-Object landet
@@ -1104,6 +1185,9 @@ export type ActionFormScreenDefinition = {
1104
1185
  /** Layout analog zu EntityEditScreen: sections mit fields aus dem
1105
1186
  * fields-Map oben. */
1106
1187
  readonly layout: EditLayout;
1188
+ /** Per-field label i18n key override, same type/semantics as
1189
+ * `EntityEditScreenDefinition.fieldLabels`. Falls back to the convention when absent. */
1190
+ readonly fieldLabels?: Readonly<Record<string, string>>;
1107
1191
  /** i18n-key für den Submit-Button. Default: i18n-Default des
1108
1192
  * Renderers (typischerweise "actions.submit"). */
1109
1193
  readonly submitLabel?: string;
@@ -1223,6 +1307,9 @@ export type SecretMintScreenDefinition = {
1223
1307
  readonly detailFor?: string;
1224
1308
  readonly description?: string;
1225
1309
  readonly agent?: AgentHandlerHints;
1310
+ /** Screen has no nav entry by design (opened via link or navved by the
1311
+ * app); exempts it from the nav-area boot check. */
1312
+ readonly dormant?: boolean;
1226
1313
  /** Derived by buildAppSchema — see EntityEditScreenDefinition.urlPrefillFields. */
1227
1314
  readonly urlPrefillFields?: readonly string[];
1228
1315
  /** Write-handler QN dispatched on submit. */
@@ -1276,7 +1363,8 @@ export type CustomScreenDefinition = {
1276
1363
  * positive (kumiko-framework#2034). Only set this on screens the
1277
1364
  * feature itself never navs — a screen the feature DOES nav still needs
1278
1365
  * its client plugin mounted by every consumer, and should keep
1279
- * triggering the diagnostic if it's missing. */
1366
+ * triggering the diagnostic if it's missing. Also gates the
1367
+ * boot-validator's nav-area check, same as `dormant` on other screens. */
1280
1368
  readonly dormant?: boolean;
1281
1369
  };
1282
1370
 
@@ -1320,6 +1408,9 @@ export type ConfigEditScreenDefinition = {
1320
1408
  readonly detailFor?: string;
1321
1409
  readonly description?: string;
1322
1410
  readonly agent?: AgentHandlerHints;
1411
+ /** Screen has no nav entry by design (opened via link or navved by the
1412
+ * app); exempts it from the nav-area boot check. */
1413
+ readonly dormant?: boolean;
1323
1414
  /** scope für config:write:set Calls. Muss zur Scope-Deklaration der
1324
1415
  * in `configKeys` referenzierten Keys passen — Boot-Validator
1325
1416
  * prüft das gegen die Registry. */
@@ -1364,6 +1455,9 @@ export type SecretsEditScreenDefinition = {
1364
1455
  readonly detailFor?: string;
1365
1456
  readonly description?: string;
1366
1457
  readonly agent?: AgentHandlerHints;
1458
+ /** Screen has no nav entry by design (opened via link or navved by the
1459
+ * app); exempts it from the nav-area boot check. */
1460
+ readonly dormant?: boolean;
1367
1461
  /** field id -> qualified secret name (`<feature>:secret:<kebab>`). */
1368
1462
  readonly secretKeys: Readonly<Record<string, string>>;
1369
1463
  /** field id -> i18n key for the label. */
package/src/step.ts CHANGED
@@ -262,13 +262,14 @@ export type StepNamespace = {
262
262
  readonly where: StepResolver<WhereObject>;
263
263
  }) => StepInstance;
264
264
  // Read sub-namespace — thin wrapper on selectMany/fetchOne (bun-db).
265
- // Caller-owned tenant-filter (does NOT auto-inject like ctx.queryProjection does).
265
+ // Tenant-filtered like ctx.db; unsafeAllTenants needs escapeHatch on the handler (or systemScope).
266
266
  readonly read: {
267
267
  readonly findOne: (
268
268
  name: string,
269
269
  opts: {
270
270
  readonly table: unknown;
271
271
  readonly where: StepResolver<WhereObject | undefined>;
272
+ readonly unsafeAllTenants?: { readonly reason: string };
272
273
  },
273
274
  ) => StepInstance;
274
275
  readonly findMany: (
@@ -277,6 +278,7 @@ export type StepNamespace = {
277
278
  readonly table: unknown;
278
279
  readonly where?: StepResolver<WhereObject | undefined>;
279
280
  readonly limit?: number;
281
+ readonly unsafeAllTenants?: { readonly reason: string };
280
282
  },
281
283
  ) => StepInstance;
282
284
  };
@@ -53,15 +53,6 @@ export type TenantDbMode = "tenant" | "system";
53
53
  export type TenantDb = {
54
54
  readonly tenantId: TenantId;
55
55
  readonly mode: TenantDbMode;
56
- /**
57
- * Underlying DbRunner. Framework-internal use (event-store, migrations) —
58
- * bypasses tenant-filter. Feature code uses the typed helpers above so the
59
- * automatic scoping stays intact.
60
- * @deprecated Use `ctx.db.unsafeRaw(reason)` / `db.global(table)` (method-
61
- * form) instead — both make the cross-tenant intent an explicit, named
62
- * declaration instead of a silent unfiltered escape hatch. Removal fw#2860.
63
- */
64
- readonly raw: DbRunner;
65
56
  /**
66
57
  * Unfiltered DbRunner escape hatch for handlers/hooks that declare `escapeHatch: { reason }`.
67
58
  * Throws `AccessDeniedError` when ungranted, or `Error` when `reason` is empty.