@cosmicdrift/kumiko-types 0.270.0 → 0.273.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.273.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
 
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
@@ -377,6 +377,32 @@ export type ToolbarAction =
377
377
  readonly style?: "primary" | "secondary" | "danger";
378
378
  };
379
379
 
380
+ // relatedList-only extension of ToolbarAction:
381
+ // a relatedList section's toolbar renders inside a projectionDetail, which
382
+ // has a record to evaluate against — a plain entityList/projectionList
383
+ // toolbar does not, so `visible`/`params` live here instead of on the base
384
+ // union. `visible` uses the same FieldCondition as header actions/
385
+ // RowAction; `params` reuses RowActionNavigate's RowFieldExtractor, applied
386
+ // to the enclosing record instead of a clicked row. Every field is optional,
387
+ // so a plain ToolbarAction is already a valid RelatedListToolbarAction —
388
+ // callers pass either type without a cast.
389
+ export type RelatedListToolbarAction =
390
+ | (Extract<ToolbarAction, { readonly kind: "navigate" }> & {
391
+ /** Conditional visibility, evaluated against the relatedList's parent
392
+ * record (the "Akte"). */
393
+ readonly visible?: FieldCondition;
394
+ /** Declarative URL search params extracted from the parent record,
395
+ * prefilling the target screen. Replaces the implicit
396
+ * `{ [parentParam]: parentId }` default when set. */
397
+ readonly params?: RowFieldExtractor;
398
+ })
399
+ | (Extract<ToolbarAction, { readonly kind: "writeHandler" }> & {
400
+ readonly visible?: FieldCondition;
401
+ })
402
+ | (Extract<ToolbarAction, { readonly kind: "drawer" }> & {
403
+ readonly visible?: FieldCondition;
404
+ });
405
+
380
406
  export type EntityListScreenDefinition = {
381
407
  readonly id: string;
382
408
  readonly type: "entityList";
@@ -384,6 +410,9 @@ export type EntityListScreenDefinition = {
384
410
  readonly detailFor?: string;
385
411
  readonly description?: string;
386
412
  readonly agent?: AgentHandlerHints;
413
+ /** Screen has no nav entry by design (opened via link or navved by the
414
+ * app); exempts it from the nav-area boot check. */
415
+ readonly dormant?: boolean;
387
416
  readonly entity: string;
388
417
  readonly columns: readonly ListColumnSpec[];
389
418
  // Row renderer (Desktop) — when omitted, renderer draws the default table
@@ -455,6 +484,17 @@ export type ListFacetSpec =
455
484
  readonly label: string;
456
485
  readonly trueLabel: string;
457
486
  readonly falseLabel: string;
487
+ }
488
+ | {
489
+ readonly field: string;
490
+ readonly type: "reference";
491
+ readonly label: string;
492
+ /** Entity name (same feature) or `feature:entity` (cross-feature), same
493
+ * convention as `ListColumnSpec.refEntity`. Options load at render time. */
494
+ readonly entity: string;
495
+ /** Row field on the referenced entity shown as the option label
496
+ * (default "id"). */
497
+ readonly labelField?: string;
458
498
  };
459
499
 
460
500
  export type ProjectionListScreenDefinition = {
@@ -464,6 +504,9 @@ export type ProjectionListScreenDefinition = {
464
504
  readonly detailFor?: string;
465
505
  readonly description?: string;
466
506
  readonly agent?: AgentHandlerHints;
507
+ /** Screen has no nav entry by design (opened via link or navved by the
508
+ * app); exempts it from the nav-area boot check. */
509
+ readonly dormant?: boolean;
467
510
  readonly query: string;
468
511
  readonly columns: readonly ListColumnSpec[];
469
512
  readonly rowRenderer?: PlatformComponent;
@@ -558,6 +601,9 @@ export type ProjectionDetailScreenDefinition = {
558
601
  readonly detailFor?: string;
559
602
  readonly description?: string;
560
603
  readonly agent?: AgentHandlerHints;
604
+ /** Screen has no nav entry by design (opened via link or navved by the
605
+ * app); exempts it from the nav-area boot check. */
606
+ readonly dormant?: boolean;
561
607
  readonly query: string;
562
608
  /** Query-payload key for the row-id. Default "id". */
563
609
  readonly idParam?: string;
@@ -763,6 +809,9 @@ export type DashboardScreenDefinition = {
763
809
  readonly detailFor?: string;
764
810
  readonly description?: string;
765
811
  readonly agent?: AgentHandlerHints;
812
+ /** Screen has no nav entry by design (opened via link or navved by the
813
+ * app); exempts it from the nav-area boot check. */
814
+ readonly dormant?: boolean;
766
815
  readonly panels: readonly DashboardPanelDefinition[];
767
816
  readonly filter?: DashboardFilterDefinition;
768
817
  readonly slots?: ScreenSlots;
@@ -835,7 +884,17 @@ export type EditFieldsSection = {
835
884
  * (subtitle-only section). */
836
885
  readonly description?: string;
837
886
  readonly columns?: number;
887
+ /** Mutually exclusive with `groups` — pass `[]` when using `groups`; the
888
+ * boot-validator rejects both non-empty or both empty. */
838
889
  readonly fields: readonly EditFieldSpec[];
890
+ /** Splits the section into multiple titled cards instead of one flat grid.
891
+ * Mutually exclusive with `fields`; fields named here still need to exist. */
892
+ readonly groups?: readonly {
893
+ readonly title: string;
894
+ readonly fields: readonly EditFieldSpec[];
895
+ /** Default 2. */
896
+ readonly columns?: number;
897
+ }[];
839
898
  /** Rendered left of the section title, `text-muted-foreground` — closed
840
899
  * IconKey vocabulary into the ICONS registry (renderer-web), analogous
841
900
  * to EditFieldSpec.icon. No title → no icon, and no heuristic derives
@@ -889,6 +948,15 @@ export type EditRelatedListSection = {
889
948
  readonly query: string;
890
949
  /** Query-payload key the parent record's id is passed under. Default "id". */
891
950
  readonly parentParam?: string;
951
+ /** Server-side WHERE clause pinning this section to the parent record —
952
+ * sent as `payload.filter: { field, op: "eq", value: parentId }`, kept
953
+ * out of the user-facet `filters` array so it can't be cleared by facet
954
+ * interaction. Lets a tab reuse the generic `<entity>:list` query instead
955
+ * of a bespoke child-rows handler. Mutually exclusive with `parentParam`
956
+ * (the boot-validator rejects both). `field` must be a real field on the
957
+ * entity behind `query`, and that query's Zod schema must accept
958
+ * `filter` (same requirement `filter`/`facets` already have). */
959
+ readonly parentFilter?: { readonly field: string };
892
960
  readonly columns: readonly ListColumnSpec[];
893
961
  readonly pageSize?: number;
894
962
  /** Initial sort on mount, applied client-side over the already-loaded rows
@@ -921,6 +989,11 @@ export type EditRelatedListSection = {
921
989
  * writeHandler action re-runs this section's own query, same as a
922
990
  * projectionList row action re-running its list query. */
923
991
  readonly rowActions?: readonly RowAction[];
992
+ /** Toolbar actions above the table — same type and dispatch semantics as
993
+ * `entityList`/`projectionList`'s `toolbarActions` ("+ Anlegen" etc.),
994
+ * plus `visible`/`params` evaluated against the parent record (see
995
+ * RelatedListToolbarAction). */
996
+ readonly toolbarActions?: readonly RelatedListToolbarAction[];
924
997
  /** Record field rendered as a count badge in the tab label when the
925
998
  * enclosing `EditLayout.mode` is "tabs" (e.g. an open-items counter).
926
999
  * Ignored outside tabs mode or when the field's value is not a finite
@@ -995,6 +1068,9 @@ export type EntityEditScreenDefinition = {
995
1068
  readonly detailFor?: string;
996
1069
  readonly description?: string;
997
1070
  readonly agent?: AgentHandlerHints;
1071
+ /** Screen has no nav entry by design (opened via link or navved by the
1072
+ * app); exempts it from the nav-area boot check. */
1073
+ readonly dormant?: boolean;
998
1074
  /** Derived by buildAppSchema from the navigate `params` targeting this
999
1075
  * screen — the only URL query keys the create form prefills. An authored
1000
1076
  * value is overwritten. */
@@ -1092,6 +1168,9 @@ export type ActionFormScreenDefinition = {
1092
1168
  readonly detailFor?: string;
1093
1169
  readonly description?: string;
1094
1170
  readonly agent?: AgentHandlerHints;
1171
+ /** Screen has no nav entry by design (opened via link or navved by the
1172
+ * app); exempts it from the nav-area boot check. */
1173
+ readonly dormant?: boolean;
1095
1174
  /** Derived by buildAppSchema — see EntityEditScreenDefinition.urlPrefillFields. */
1096
1175
  readonly urlPrefillFields?: readonly string[];
1097
1176
  /** Write-Handler-QN der bei Submit gerufen wird. Form-Object landet
@@ -1104,6 +1183,9 @@ export type ActionFormScreenDefinition = {
1104
1183
  /** Layout analog zu EntityEditScreen: sections mit fields aus dem
1105
1184
  * fields-Map oben. */
1106
1185
  readonly layout: EditLayout;
1186
+ /** Per-field label i18n key override, same type/semantics as
1187
+ * `EntityEditScreenDefinition.fieldLabels`. Falls back to the convention when absent. */
1188
+ readonly fieldLabels?: Readonly<Record<string, string>>;
1107
1189
  /** i18n-key für den Submit-Button. Default: i18n-Default des
1108
1190
  * Renderers (typischerweise "actions.submit"). */
1109
1191
  readonly submitLabel?: string;
@@ -1223,6 +1305,9 @@ export type SecretMintScreenDefinition = {
1223
1305
  readonly detailFor?: string;
1224
1306
  readonly description?: string;
1225
1307
  readonly agent?: AgentHandlerHints;
1308
+ /** Screen has no nav entry by design (opened via link or navved by the
1309
+ * app); exempts it from the nav-area boot check. */
1310
+ readonly dormant?: boolean;
1226
1311
  /** Derived by buildAppSchema — see EntityEditScreenDefinition.urlPrefillFields. */
1227
1312
  readonly urlPrefillFields?: readonly string[];
1228
1313
  /** Write-handler QN dispatched on submit. */
@@ -1276,7 +1361,8 @@ export type CustomScreenDefinition = {
1276
1361
  * positive (kumiko-framework#2034). Only set this on screens the
1277
1362
  * feature itself never navs — a screen the feature DOES nav still needs
1278
1363
  * its client plugin mounted by every consumer, and should keep
1279
- * triggering the diagnostic if it's missing. */
1364
+ * triggering the diagnostic if it's missing. Also gates the
1365
+ * boot-validator's nav-area check, same as `dormant` on other screens. */
1280
1366
  readonly dormant?: boolean;
1281
1367
  };
1282
1368
 
@@ -1320,6 +1406,9 @@ export type ConfigEditScreenDefinition = {
1320
1406
  readonly detailFor?: string;
1321
1407
  readonly description?: string;
1322
1408
  readonly agent?: AgentHandlerHints;
1409
+ /** Screen has no nav entry by design (opened via link or navved by the
1410
+ * app); exempts it from the nav-area boot check. */
1411
+ readonly dormant?: boolean;
1323
1412
  /** scope für config:write:set Calls. Muss zur Scope-Deklaration der
1324
1413
  * in `configKeys` referenzierten Keys passen — Boot-Validator
1325
1414
  * prüft das gegen die Registry. */
@@ -1364,6 +1453,9 @@ export type SecretsEditScreenDefinition = {
1364
1453
  readonly detailFor?: string;
1365
1454
  readonly description?: string;
1366
1455
  readonly agent?: AgentHandlerHints;
1456
+ /** Screen has no nav entry by design (opened via link or navved by the
1457
+ * app); exempts it from the nav-area boot check. */
1458
+ readonly dormant?: boolean;
1367
1459
  /** field id -> qualified secret name (`<feature>:secret:<kebab>`). */
1368
1460
  readonly secretKeys: Readonly<Record<string, string>>;
1369
1461
  /** 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.