@cosmicdrift/kumiko-framework 0.355.0 → 0.357.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.
@@ -25,10 +25,13 @@ export type ExecutorContext = {
25
25
  readonly entityCache?: EntityCache;
26
26
  readonly searchAdapter?: SearchAdapter;
27
27
  readonly softDelete: boolean;
28
+ readonly streamTenantCreateFailure: (db: TenantDb, payload: Record<string, unknown>, requestedStreamTenantId: TenantId | undefined) => WriteFailure | undefined;
28
29
  readonly streamTenantFor: (user: SessionUser, requestedStreamTenantId?: TenantId) => TenantId;
29
30
  readonly streamTenantOverrideFailure: (db: TenantDb, row: Record<string, unknown>, requestedStreamTenantId: TenantId | undefined) => WriteFailure | undefined;
30
31
  readonly idFilter: (id: EntityId) => WhereObject;
31
- readonly loadById: (id: EntityId, db: TenantDb) => Promise<Record<string, unknown> | null>;
32
+ readonly loadById: (id: EntityId, db: TenantDb, options?: {
33
+ readonly includeDeleted?: boolean;
34
+ }) => Promise<Record<string, unknown> | null>;
32
35
  readonly assertStreamWritable: (db: TenantDb, id: EntityId, tenantId: TenantId) => Promise<void>;
33
36
  readonly loadWithOwnership: (db: TenantDb, idWhere: WhereObject, ownership: {
34
37
  kind: "pass";
@@ -118,14 +118,15 @@ export function buildExecutorContext(table, entity, options) {
118
118
  // there. Everything else stays on the caller's tenant (byte-identical to the
119
119
  // old hardcoded user.tenantId). Single source of truth for the stream key.
120
120
  const streamTenantFor = (user, requestedStreamTenantId) => entity.systemStream ? SYSTEM_TENANT_ID : (requestedStreamTenantId ?? user.tenantId);
121
- // Only a system-mode db (r.systemScope() / acknowledged cross-tenant) may address another
122
- // tenant's stream, and only the tenant of the row it just loaded.
121
+ // Only a system-mode db (r.systemScope() / acknowledged cross-tenant) or a tenant-mode db bound
122
+ // to exactly that stream tenant (ctx.forTenant) may address another tenant's stream, and only
123
+ // the tenant of the row it just loaded.
123
124
  const streamTenantOverrideFailure = (db, row, requestedStreamTenantId) => {
124
125
  if (requestedStreamTenantId === undefined)
125
126
  return undefined;
126
- if (db.mode !== "system") {
127
+ if (db.mode !== "system" && db.tenantId !== requestedStreamTenantId) {
127
128
  return writeFailure(new AccessDeniedError({
128
- message: `${entityName}: streamTenantId requires a system-mode db`,
129
+ message: `${entityName}: streamTenantId requires a system-mode db or a db bound to that tenant`,
129
130
  }));
130
131
  }
131
132
  if (row["tenantId"] !== requestedStreamTenantId) {
@@ -135,6 +136,8 @@ export function buildExecutorContext(table, entity, options) {
135
136
  }
136
137
  return undefined;
137
138
  };
139
+ // Create has no loaded row: the payload's own tenantId (if any) must agree with the target stream.
140
+ const streamTenantCreateFailure = (db, payload, requestedStreamTenantId) => streamTenantOverrideFailure(db, { tenantId: payload["tenantId"] ?? requestedStreamTenantId }, requestedStreamTenantId);
138
141
  // idType default (undefined) is now "uuid" — the ES-pivot made UUID the
139
142
  // only valid aggregate-id type. Explicit `idType: "serial"` is the only
140
143
  // shape that's incompatible with the event-store and still rejected.
@@ -269,8 +272,9 @@ export function buildExecutorContext(table, entity, options) {
269
272
  filter["isDeleted"] = false;
270
273
  return filter;
271
274
  }
272
- async function loadById(id, db) {
273
- const row = await db.fetchOne(table, idFilter(id));
275
+ async function loadById(id, db, options) {
276
+ // Only the soft-delete predicate is relaxed; the tenant filter of db.fetchOne stays.
277
+ const row = await db.fetchOne(table, options?.includeDeleted ? { id } : idFilter(id));
274
278
  if (!row)
275
279
  return null;
276
280
  return await decryptForRead(rehydrateCompoundTypes(row, entity));
@@ -416,6 +420,7 @@ export function buildExecutorContext(table, entity, options) {
416
420
  softDelete,
417
421
  streamTenantFor,
418
422
  streamTenantOverrideFailure,
423
+ streamTenantCreateFailure,
419
424
  idFilter,
420
425
  loadById,
421
426
  assertStreamWritable,
@@ -118,7 +118,7 @@ async function runPreSave(preSave, changes, previous, isNew, entityName, action,
118
118
  }
119
119
  }
120
120
  export function createWriteVerbs(ctx) {
121
- const { table, entity, entityName, entityCache, softDelete, streamTenantFor, streamTenantOverrideFailure, encryptForStorage, decryptForRead, applyDefaults, stripSensitive, loadById, assertStreamWritable, loadExpectSnapshot, } = ctx;
121
+ const { table, entity, entityName, entityCache, softDelete, streamTenantFor, streamTenantOverrideFailure, streamTenantCreateFailure, encryptForStorage, decryptForRead, applyDefaults, stripSensitive, loadById, assertStreamWritable, loadExpectSnapshot, } = ctx;
122
122
  const entityInstructionFieldNames = instructionFieldNames(entity);
123
123
  const eventVersion = entity.eventVersion ?? 1;
124
124
  let insertSchema;
@@ -163,6 +163,10 @@ export function createWriteVerbs(ctx) {
163
163
  "SYSTEM_TENANT_ID or omitted.",
164
164
  });
165
165
  }
166
+ const streamTenantId = options?.streamTenantId;
167
+ const streamTenantMismatch = streamTenantCreateFailure(db, payload, streamTenantId);
168
+ if (streamTenantMismatch)
169
+ return streamTenantMismatch;
166
170
  const runner = tenantDbRunner(db);
167
171
  // Respect an explicit id in the payload (seed pattern, SCIM import). Without
168
172
  // one the framework mints a fresh UUIDv7 via generateId. Strip it out of the
@@ -179,7 +183,7 @@ export function createWriteVerbs(ctx) {
179
183
  assertInstructionFieldWriteAllowed(entityName, "create", Object.keys(data), entityInstructionFieldNames);
180
184
  // H.2 — entity-level write-ownership on create. No oldRow exists, so
181
185
  // only the new row is checked. No Straddle concern for creates.
182
- if (!userCanCreateFieldRow(user, entity.access?.write, data)) {
186
+ if (!userCanCreateFieldRow(ownershipSubject(user, streamTenantId), entity.access?.write, data)) {
183
187
  return writeFailure(new UnprocessableError("ownership_denied", {
184
188
  i18nKey: "errors.ownershipDenied",
185
189
  details: { scope: "entity", entityName, action: "create", userId: user.id },
@@ -195,7 +199,7 @@ export function createWriteVerbs(ctx) {
195
199
  // field is still evaluated against the full post-hook row (`data`) —
196
200
  // an ownership rule can reference a column only a hook populates
197
201
  // (kumiko-framework#1672).
198
- const fieldDeniedCreate = checkWriteFieldOwnership(entity, applyDefaults(payloadWithoutId), user, undefined, data);
202
+ const fieldDeniedCreate = checkWriteFieldOwnership(entity, applyDefaults(payloadWithoutId), ownershipSubject(user, streamTenantId), undefined, data);
199
203
  if (fieldDeniedCreate) {
200
204
  return writeFailure(new UnprocessableError("ownership_denied", {
201
205
  i18nKey: "errors.ownershipDenied",
@@ -219,6 +223,7 @@ export function createWriteVerbs(ctx) {
219
223
  const flatCreateData = flattenCompoundTypes(data, entity);
220
224
  const flatData = await encryptForStorage(flatCreateData, user, {
221
225
  subjectSource: { ...flatCreateData, id: aggregateId },
226
+ keyTenantId: streamTenantId,
222
227
  });
223
228
  // 1. Append event (same TX as the projection write — both must succeed
224
229
  // or both roll back; the dispatcher wraps both in one transaction).
@@ -246,7 +251,7 @@ export function createWriteVerbs(ctx) {
246
251
  event = await runInSavepointIfSupported(runner, async (sp) => append(sp, {
247
252
  aggregateId,
248
253
  aggregateType: entityName,
249
- tenantId: streamTenantFor(user),
254
+ tenantId: streamTenantFor(user, streamTenantId),
250
255
  expectedVersion: 0,
251
256
  type: entityEventName(entityName, "created"),
252
257
  eventVersion,
@@ -258,7 +263,7 @@ export function createWriteVerbs(ctx) {
258
263
  if (e instanceof EventStoreVersionConflict) {
259
264
  let currentVersion = -1;
260
265
  try {
261
- currentVersion = await getStreamVersion(runner, aggregateId, streamTenantFor(user));
266
+ currentVersion = await getStreamVersion(runner, aggregateId, streamTenantFor(user, streamTenantId));
262
267
  }
263
268
  catch {
264
269
  // Lookup failure — keep the sentinel.
@@ -308,7 +313,7 @@ export function createWriteVerbs(ctx) {
308
313
  // Compound-Types in einem Pass.
309
314
  const projection = await decryptForRead(rehydrateCompoundTypes(row, entity));
310
315
  if (entityCache && entityName) {
311
- await entityCache.del(user.tenantId, entityName, aggregateId);
316
+ await entityCache.del(streamTenantId ?? user.tenantId, entityName, aggregateId);
312
317
  }
313
318
  // The echoed event is the one projections see (and the dispatcher later
314
319
  // skips), so build it before projecting.
@@ -338,7 +343,9 @@ export function createWriteVerbs(ctx) {
338
343
  "be SYSTEM_TENANT_ID or omitted.",
339
344
  });
340
345
  }
341
- const previous = await loadById(payload.id, db);
346
+ const previous = await loadById(payload.id, db, {
347
+ includeDeleted: updateOptions?.includeDeleted === true,
348
+ });
342
349
  if (!previous)
343
350
  return writeFailure(new NotFoundError(entityName, payload.id));
344
351
  const streamTenantId = updateOptions?.streamTenantId;
@@ -0,0 +1,10 @@
1
+ import type { FeatureDefinition } from "../types/index.js";
2
+ export type CardMetaOverflow = {
3
+ readonly feature: string;
4
+ readonly screen: string;
5
+ readonly location?: string;
6
+ readonly count: number;
7
+ readonly fields: readonly string[];
8
+ };
9
+ export declare function collectCardMetaOverflow(features: readonly FeatureDefinition[]): readonly CardMetaOverflow[];
10
+ export declare function validateCardMetaOverflow(features: readonly FeatureDefinition[]): void;
@@ -0,0 +1,95 @@
1
+ import { CARD_META_MAX, cardColumnRoles } from "../../ui-types/card-column-roles.js";
2
+ import { LIST_ROW_META_COLUMNS } from "../../ui-types/list-row-meta.js";
3
+ import { parseRefTarget } from "../parse-ref-target.js";
4
+ import { normalizeListColumn } from "../screen-helpers.js";
5
+ // Mirrors the type resolution of headless computeListViewModel; only the
6
+ // "select" outcome matters for the card status role.
7
+ function resolveColumnType(spec, storedType, entity) {
8
+ const col = normalizeListColumn(spec);
9
+ if (col.refEntity !== undefined && (storedType === undefined || storedType === "text")) {
10
+ return "reference";
11
+ }
12
+ if (storedType !== undefined)
13
+ return storedType;
14
+ const derived = entity?.derivedFields?.[col.field];
15
+ if (derived !== undefined)
16
+ return derived.valueType;
17
+ return LIST_ROW_META_COLUMNS[col.field] ?? "text";
18
+ }
19
+ function countOverflow(feature, screen, location, columns, storedTypeOf, entity) {
20
+ const cardColumns = columns.map((spec) => {
21
+ const col = normalizeListColumn(spec);
22
+ return {
23
+ field: col.field,
24
+ type: resolveColumnType(spec, storedTypeOf(col.field), entity),
25
+ renderer: col.renderer,
26
+ hideOnNarrow: col.hideOnNarrow,
27
+ };
28
+ });
29
+ const { meta } = cardColumnRoles(cardColumns);
30
+ if (meta.length <= CARD_META_MAX)
31
+ return undefined;
32
+ return {
33
+ feature: feature.name,
34
+ screen,
35
+ ...(location !== undefined && { location }),
36
+ count: meta.length,
37
+ fields: meta.map((col) => col.field),
38
+ };
39
+ }
40
+ function relatedListOverflow(features, feature, screen, location, section) {
41
+ let source;
42
+ if (section.entity !== undefined) {
43
+ const target = parseRefTarget(section.entity, feature.name);
44
+ source = features.find((f) => f.name === target.featureName)?.entities?.[target.entityName];
45
+ }
46
+ const synthesizedTypes = new Map(section.columns.map((spec) => {
47
+ const col = normalizeListColumn(spec);
48
+ return [col.field, col.valueType ?? "text"];
49
+ }));
50
+ return countOverflow(feature, screen, location, section.columns, (field) => source?.fields[field]?.type ?? synthesizedTypes.get(field), source);
51
+ }
52
+ // Every declaration whose columns the narrow layout draws as a DataTable card.
53
+ // Dashboard list panels have their own table and are not covered.
54
+ export function collectCardMetaOverflow(features) {
55
+ const found = [];
56
+ const add = (overflow) => {
57
+ if (overflow !== undefined)
58
+ found.push(overflow);
59
+ };
60
+ for (const feature of features) {
61
+ for (const screen of Object.values(feature.screens)) {
62
+ if (screen.type === "entityList") {
63
+ const entity = feature.entities?.[screen.entity];
64
+ add(countOverflow(feature, screen.id, undefined, screen.columns, (field) => entity?.fields[field]?.type, entity));
65
+ if (screen.expandableRow !== undefined) {
66
+ add(relatedListOverflow(features, feature, screen.id, `expandableRow "${screen.expandableRow.title}"`, screen.expandableRow));
67
+ }
68
+ }
69
+ else if (screen.type === "projectionList") {
70
+ // The projection shim types every column "text"; only refEntity changes it.
71
+ add(countOverflow(feature, screen.id, undefined, screen.columns, () => "text", undefined));
72
+ }
73
+ else if (screen.type === "projectionDetail") {
74
+ const isTabs = screen.layout.mode === "tabs";
75
+ for (const section of screen.layout.sections) {
76
+ if (!("kind" in section) || section.kind !== "relatedList")
77
+ continue;
78
+ const location = isTabs
79
+ ? `tab "${section.id ?? section.title}" section "${section.title}"`
80
+ : `section "${section.title}"`;
81
+ add(relatedListOverflow(features, feature, screen.id, location, section));
82
+ }
83
+ }
84
+ }
85
+ }
86
+ return found;
87
+ }
88
+ export function validateCardMetaOverflow(features) {
89
+ const overflows = collectCardMetaOverflow(features);
90
+ // skip: nothing overflows, so there is nothing to report
91
+ if (overflows.length === 0)
92
+ return;
93
+ const lines = overflows.map((o) => ` - Feature "${o.feature}" screen "${o.screen}"${o.location !== undefined ? ` ${o.location}` : ""}: ${o.count} meta columns (${o.fields.join(", ")})`);
94
+ throw new Error(`List cards show at most ${CARD_META_MAX} meta columns (all columns except title, status select and hideOnNarrow ones); more would be cut off on narrow screens:\n${lines.join("\n")}\nReduce each to ${CARD_META_MAX} columns or mark the others hideOnNarrow: true.`);
95
+ }
@@ -710,11 +710,12 @@ function assertNoSelectOptionsQuery(field, path, featureName) {
710
710
  export function validateEntitySelectAvailabilityQuery(feature, queryHandlers) {
711
711
  for (const [entityName, entity] of Object.entries(feature.entities ?? {})) {
712
712
  for (const [fieldName, field] of Object.entries(entity.fields)) {
713
- if (field.type !== "select" || field.optionsAvailabilityQuery === undefined)
713
+ if ((field.type !== "select" && field.type !== "multiSelect") ||
714
+ field.optionsAvailabilityQuery === undefined)
714
715
  continue;
715
716
  const query = field.optionsAvailabilityQuery;
716
717
  if (query.length === 0 || !queryHandlers.has(query)) {
717
- throw new Error(`[Feature ${feature.name}] Select field "${fieldName}" on entity "${entityName}" ` +
718
+ throw new Error(`[Feature ${feature.name}] ${field.type === "select" ? "Select" : "MultiSelect"} field "${fieldName}" on entity "${entityName}" ` +
718
719
  `declares optionsAvailabilityQuery "${query}" which is not a registered query-handler. ` +
719
720
  `Check the QN spelling (expected "<feature>:query:<short>") and that the handler ` +
720
721
  `is declared via r.queryHandler(...).`);
@@ -1,5 +1,6 @@
1
1
  import type { EnvelopeCipher } from "../../secrets/envelope-cipher.js";
2
2
  import type { FeatureDefinition } from "../types/index.js";
3
+ export { type CardMetaOverflow, collectCardMetaOverflow } from "./card-meta-overflow.js";
3
4
  export { validateAppCustomScreenWriteQns } from "./custom-screen-write-qns.js";
4
5
  export { collectWriteHandlerQns } from "./nav.js";
5
6
  export { SECURITY_BASELINE_FEATURE_NAMES } from "./security-baseline.js";
@@ -7,6 +7,7 @@ import { warnOnUniqueAccessRoles } from "./access-roles.js";
7
7
  import { validateActionWiring, validateFieldWiring } from "./action-wiring.js";
8
8
  import { validateApiExposureMatching, validateExtensionUsages } from "./api-ext.js";
9
9
  import { validateFeatureBootChecks } from "./boot-check.js";
10
+ import { validateCardMetaOverflow } from "./card-meta-overflow.js";
10
11
  import { validateCircularDeps, validateConfigKeyAllowPerRequest, validateConfigKeyBacking, validateConfigKeyBounds, validateConfigKeyComputed, validateConfigKeyOptionsQuery, validateConfigKeyPiiEncrypted, validateConfigKeyRequired, validateConfigReads, warnOnToggleableDependencies, } from "./config-deps.js";
11
12
  import { collectDetailForScreens } from "./detail-screens.js";
12
13
  import { validateDerivedFieldCollisions, validateEmbeddedFields, validateEncryptedFields, validateEntityIndexes, validateEntitySelectAvailabilityQuery, validateEntitySelectConditionalOptions, validateEntitySelectOptionsQuery, validateExtendSchemaCollisions, validateExtensionPreSaveWiring, validateFileFields, validateHandlerAccess, validateImageVariants, validateLocatedTimestamps, validateMultiSelectFields, validateMultiStreamProjections, validateReferenceFields, validateSearchableFieldReadAccess, validateTransitions, } from "./entity-handler.js";
@@ -29,10 +30,11 @@ import { validateExtensionSelectorPanels } from "./selector-owner-panels.js";
29
30
  import { validateTransferGraph } from "./transfer-graph.js";
30
31
  import { validateSubmitPrefilledHiddenFields } from "./url-prefill-fields.js";
31
32
  import { collectWorkspaceQns, resolveNavAllowlist, validateDefaultWorkspaceUniqueness, validateWorkspaces, } from "./workspaces.js";
32
- export { validateAppCustomScreenWriteQns } from "./custom-screen-write-qns.js";
33
33
  // Re-export: wird von run-dev-app.ts benötigt um Write-Handler-QNs
34
34
  // an den Codegen zu übergeben. Nicht Teil von validateBoot, aber
35
35
  // dieselbe Extraktionslogik.
36
+ export { collectCardMetaOverflow } from "./card-meta-overflow.js";
37
+ export { validateAppCustomScreenWriteQns } from "./custom-screen-write-qns.js";
36
38
  export { collectWriteHandlerQns } from "./nav.js";
37
39
  export { SECURITY_BASELINE_FEATURE_NAMES } from "./security-baseline.js";
38
40
  export { MAX_TRANSFER_DEPTH } from "./transfer-graph.js";
@@ -217,6 +219,7 @@ export function validateBoot(rawFeatures, options) {
217
219
  validateI18nSurfaceKeys(features);
218
220
  validateEntityListScreens(features);
219
221
  validateSubmitPrefilledHiddenFields(features);
222
+ validateCardMetaOverflow(features);
220
223
  // Must run before validateProjectionListScreens: an unresolvable query
221
224
  // there is silently treated as "capability absent" and surfaces as a
222
225
  // misleading "no search parameter in its Zod schema" error instead of
@@ -45,6 +45,9 @@ function checkDashboardQueryRefs(queryHandlers, featureName, screenId, screen) {
45
45
  }
46
46
  function checkFormFieldQueryRefs(queryHandlers, featureName, screenId, screenType, fields) {
47
47
  for (const [fieldName, field] of Object.entries(fields)) {
48
+ if (field.type === "multiSelect" && field.optionsAvailabilityQuery !== undefined) {
49
+ checkQueryRef(queryHandlers, field.optionsAvailabilityQuery, () => `[Feature ${featureName}] Screen "${screenId}" (${screenType}) multiSelect field "${fieldName}" optionsAvailabilityQuery`);
50
+ }
48
51
  if (field.type !== "select")
49
52
  continue;
50
53
  if (field.optionsQuery !== undefined) {
@@ -550,6 +550,11 @@ function validateFormSelectOptions(featureName, screenId, context, fieldName, fd
550
550
  throw new Error(`${where} has optionsQueryPayload without optionsQuery`);
551
551
  }
552
552
  }
553
+ else if (fdef.type === "multiSelect" && fdef.optionsAvailabilityQuery !== undefined) {
554
+ if (fdef.optionsAvailabilityQuery.length === 0) {
555
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (${context}) multiSelect field "${fieldName}" has an empty optionsAvailabilityQuery`);
556
+ }
557
+ }
553
558
  }
554
559
  // Fail-closed currency-source gate (fw#2839), the runtime half of the
555
560
  // narrowed `FormFieldDefinition` — an untyped JS consumer has no compiler to
@@ -1 +1 @@
1
- export { collectWriteHandlerQns, MAX_TRANSFER_DEPTH, SECURITY_BASELINE_FEATURE_NAMES, type ValidateBootOptions, validateAppCustomScreenWriteQns, validateBoot, } from "./boot-validator/index.js";
1
+ export { type CardMetaOverflow, collectCardMetaOverflow, collectWriteHandlerQns, MAX_TRANSFER_DEPTH, SECURITY_BASELINE_FEATURE_NAMES, type ValidateBootOptions, validateAppCustomScreenWriteQns, validateBoot, } from "./boot-validator/index.js";
@@ -1 +1 @@
1
- export { collectWriteHandlerQns, MAX_TRANSFER_DEPTH, SECURITY_BASELINE_FEATURE_NAMES, validateAppCustomScreenWriteQns, validateBoot, } from "./boot-validator/index.js";
1
+ export { collectCardMetaOverflow, collectWriteHandlerQns, MAX_TRANSFER_DEPTH, SECURITY_BASELINE_FEATURE_NAMES, validateAppCustomScreenWriteQns, validateBoot, } from "./boot-validator/index.js";
@@ -3,7 +3,7 @@ export { hasAccess } from "./access.js";
3
3
  export { isPrincipalStatusPlugin, isTenantLifecycleStatusPlugin, type PrincipalProfile, type PrincipalStatus, type PrincipalStatusPlugin, TENANT_TEARDOWN_STATUSES, type TenantLifecycleStatusPlugin, } from "./active-membership.js";
4
4
  export { PII_DIRECT_NAME_HINTS, PII_USER_OWNED_NAME_HINTS, PII_USER_REFERENCE_NAME_HINTS, } from "./boot-validator/entity-handler.js";
5
5
  export { entityHasAnonymizableSubjectField } from "./boot-validator/pii-retention.js";
6
- export { collectWriteHandlerQns, MAX_TRANSFER_DEPTH, SECURITY_BASELINE_FEATURE_NAMES, type ValidateBootOptions, validateAppCustomScreenWriteQns, validateBoot, } from "./boot-validator.js";
6
+ export { type CardMetaOverflow, collectCardMetaOverflow, collectWriteHandlerQns, MAX_TRANSFER_DEPTH, SECURITY_BASELINE_FEATURE_NAMES, type ValidateBootOptions, validateAppCustomScreenWriteQns, validateBoot, } from "./boot-validator.js";
7
7
  export { type BuildAppSchemaOptions, buildAppSchema } from "./build-app-schema.js";
8
8
  export type { ConfigFeatureSchema } from "./build-config-feature-schema.js";
9
9
  export { buildConfigFeatureSchema, SETTINGS_HUB_FEATURE, SETTINGS_HUB_WORKSPACE, } from "./build-config-feature-schema.js";
@@ -80,7 +80,7 @@ export { ANONYMOUS_ROLE, ANONYMOUS_USER_ID, createAnonymousUser, createSystemUse
80
80
  export { type EffectiveFeaturesResolver, findTierResolverUsage, isTierResolverPlugin, TENANT_TIER_RESOLVER_EXT, type TierResolverPlugin, type TrialGate, } from "./tier-resolver-extension.js";
81
81
  export { findOverDeepTransferChain, resolveTransferAdjacency as resolveEntityTransferAdjacency, type TransferAdjacency, type TransferEdge, type TransferEdgeLink, } from "./transfer-adjacency.js";
82
82
  export { isSystemTenant, isUuid, parseTenantId, SYSTEM_TENANT_ID } from "./types/identifiers.js";
83
- export type { AccessRule, ActionFormScreenDefinition, ActiveMembership, ActiveMembershipRejection, ActiveMembershipResult, AgentExposure, AgentHandlerHints, AgentRisk, AppContext, AppendEventArgs, AppendEventFn, AuthClaimsContext, AuthClaimsFn, AuthClaimsHookDef, BelongsToRelation, BigIntFieldDef, BooleanFieldDef, CamelToKebab, ChatSendFailureCode, ClaimKeyDefinition, ClaimKeyHandle, ClaimKeyJsType, ClaimKeyType, ConcurrencyMode, ConfigAccessor, ConfigAccessorFactory, ConfigBacking, ConfigCascade, ConfigCascadeLevel, ConfigDefinition, ConfigEditScreenDefinition, ConfigKeyAccess, ConfigKeyDefinition, ConfigKeyHandle, ConfigKeyType, ConfigMask, ConfigResolver, ConfigScope, ConfigSecretsReader, ConfigSeedDef, ConfigStoredRow, ConfigStoredRowWithSource, ConfigValidateContext, ConfigValidateFn, ConfigValue, ConfigValueSource, ConfigValueWithSource, ContentCollectionDefinition, CreateSeedOptions, CreateTenantSeedOptions, CreateUserSeedOptions, CustomScreenDefinition, CustomScreenRoute, DashboardChartPanel, DashboardCustomPanel, DashboardDateParam, DashboardFeedPanel, DashboardFilterDefinition, DashboardI18nText, DashboardListPanel, DashboardMoneyParam, DashboardPanelDefinition, DashboardPanelVisibility, DashboardProgressListPanel, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DashboardText, DashboardTextParam, DashboardValueFormat, DateFieldDef, DecimalFieldDef, DeleteContext, DeliveryErrorCode, DeliveryFailureCode, DeliverySkipReason, DeriveContext, DerivedFieldDef, DerivedFieldsMap, DerivedValueType, EditExtensionSection, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EntityDefinition, EntityEditScreenDefinition, EntityId, EntityListExpandableRow, EntityListScreenDefinition, EntityRef, EntityRelations, EscapeHatchAuditSink, EscapeHatchDeclaration, EscapeHatchKind, EscapeHatchReporter, EscapeHatchTarget, EscapeHatchUseEvent, EventDef, ExtensionOptionsArgs, ExtensionOptionsFor, FeatureDefinition, FeatureRegistrar, FieldAccess, FieldCondition, FieldDefinition, FieldFormatRegistry, FieldIconKey, FieldRenderer, FileFieldDef, FilesFieldDef, Findability, FormatSpec, FormWidth, HandlerContext, HasManyRelation, HookMap, ImageFieldDef, ImagesFieldDef, JobBackoff, JobBackoffStrategy, JobContext, JobDefinition, JobHandlerFn, JobTrigger, JobTriggerEvent, JsonbFieldDef, KumikoEntityTypeMap, KumikoEventTypeMap, KumikoExtensionOptionsMap, KumikoHandlerPayloadMap, KumikoHandlerResultMap, LifecycleHookType, ListColumnSpec, ListFacetSpec, ListPaginationMode, ListSortDir, ListSortSpec, LongTextFindability, ManyToManyRelation, MemberReader, MetricNavigate, MetricSpec, MspErrorMode, MspErrorPolicy, MultiSelectFieldDef, MultiStreamApplyFn, MultiStreamProjectionDefinition, NameOrRef, NavDefinition, NavIconKey, NotificationDataFn, NotificationDefinition, NotificationRecipientFn, NotificationTemplateFn, NotifyDelivery, NotifyDeliveryStatus, NotifyFactory, NotifyFn, NotifyJobDispatcher, NotifyOptions, NotifyPriority, NotifyResult, NumberFieldDef, OnDeleteStrategy, OpenToAllAccessRule, OpenToAllDeclaration, OpenToAllPersonalData, ParentRefDef, PersonalAnnotations, PersonalAnnotationsLongText, PersonalAnnotationsNoFind, PersonalSubject, PlatformComponent, PluralForms, PostDeleteHookFn, PostSaveHookFn, PreDeleteHookFn, PreQueryHookFn, PreSaveHookFn, ProjectionDefinition, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, ProjectionTable, QualifiedEventName, QueryEvent, QueryHandlerDef, QueryHandlerFn, RateLimitPer, RecordHeaderSpec, RecordHeaderSubtitlePart, ReferenceDataDef, Registry, RelationDefinition, ResolvedPiiFlags, RetentionDef, RoleAccessPersonalData, RoleAccessRule, RowAction, RowActionNavigate, RowActionNavigateBase, RowActionWriteHandler, RowFieldExtractor, SaveContext, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenNavSugar, ScreenSlots, SecretKeyDefinition, SecretKeyHandle, SecretMintConfirmStep, SecretMintScreenDefinition, SecretNamespaceDefinition, SecretNamespaceHandle, SecretNamespaceOptions, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, SelectFieldDef, SessionUser, SessionUserOrigin, StreamHandlerDef, StreamHandlerFn, Subscribe, TargetRef, TenantId, TextFieldDef, ToolbarAction, TranslationEntry, TranslationKeys, TranslationsDef, TranslationValue, TreeAction, TreeActionDef, TreeActionsHandle, TreeChildrenSubscribe, TreeNode, TreeNodeState, UnitKey, UnsafeAppendEventFn, ValidationError, ValidationHookFn, WorkspaceDefinition, WriteEvent, WriteHandlerDef, WriteHandlerFn, WriteResult, } from "./types/index.js";
83
+ export type { AccessRule, ActionFormScreenDefinition, ActiveMembership, ActiveMembershipRejection, ActiveMembershipResult, AgentExposure, AgentHandlerHints, AgentRisk, AppContext, AppendEventArgs, AppendEventFn, AuthClaimsContext, AuthClaimsFn, AuthClaimsHookDef, BelongsToRelation, BigIntFieldDef, BooleanFieldDef, CamelToKebab, ChatSendFailureCode, ClaimKeyDefinition, ClaimKeyHandle, ClaimKeyJsType, ClaimKeyType, ConcurrencyMode, ConfigAccessor, ConfigAccessorFactory, ConfigBacking, ConfigCascade, ConfigCascadeLevel, ConfigDefinition, ConfigEditScreenDefinition, ConfigKeyAccess, ConfigKeyDefinition, ConfigKeyHandle, ConfigKeyType, ConfigMask, ConfigResolver, ConfigScope, ConfigSecretsReader, ConfigSeedDef, ConfigStoredRow, ConfigStoredRowWithSource, ConfigValidateContext, ConfigValidateFn, ConfigValue, ConfigValueSource, ConfigValueWithSource, ContentCollectionDefinition, CreateSeedOptions, CreateTenantSeedOptions, CreateUserSeedOptions, CustomScreenDefinition, CustomScreenRoute, DashboardChartPanel, DashboardCustomPanel, DashboardDateParam, DashboardFeedPanel, DashboardFilterDefinition, DashboardI18nText, DashboardListPanel, DashboardMoneyParam, DashboardPanelDefinition, DashboardPanelVisibility, DashboardProgressListPanel, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DashboardText, DashboardTextParam, DashboardValueFormat, DateFieldDef, DecimalFieldDef, DeleteContext, DeliveryErrorCode, DeliveryFailureCode, DeliverySkipReason, DeriveContext, DerivedFieldDef, DerivedFieldsMap, DerivedValueType, EditExtensionSection, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EntityDefinition, EntityEditAction, EntityEditActionPlacement, EntityEditScreenDefinition, EntityId, EntityListExpandableRow, EntityListScreenDefinition, EntityRef, EntityRelations, EscapeHatchAuditSink, EscapeHatchDeclaration, EscapeHatchKind, EscapeHatchReporter, EscapeHatchTarget, EscapeHatchUseEvent, EventDef, ExtensionOptionsArgs, ExtensionOptionsFor, FeatureDefinition, FeatureRegistrar, FieldAccess, FieldCondition, FieldDefinition, FieldFormatRegistry, FieldIconKey, FieldRenderer, FileFieldDef, FilesFieldDef, Findability, FormatSpec, FormWidth, HandlerContext, HasManyRelation, HookMap, ImageFieldDef, ImagesFieldDef, JobBackoff, JobBackoffStrategy, JobContext, JobDefinition, JobHandlerFn, JobTrigger, JobTriggerEvent, JsonbFieldDef, KumikoEntityTypeMap, KumikoEventTypeMap, KumikoExtensionOptionsMap, KumikoHandlerPayloadMap, KumikoHandlerResultMap, LifecycleHookType, ListColumnSpec, ListFacetSpec, ListPaginationMode, ListSortDir, ListSortSpec, LongTextFindability, ManyToManyRelation, MemberReader, MetricNavigate, MetricSpec, MspErrorMode, MspErrorPolicy, MultiSelectFieldDef, MultiStreamApplyFn, MultiStreamProjectionDefinition, NameOrRef, NavDefinition, NavIconKey, NotificationDataFn, NotificationDefinition, NotificationRecipientFn, NotificationTemplateFn, NotifyDelivery, NotifyDeliveryStatus, NotifyFactory, NotifyFn, NotifyJobDispatcher, NotifyOptions, NotifyPriority, NotifyResult, NumberFieldDef, OnDeleteStrategy, OpenToAllAccessRule, OpenToAllDeclaration, OpenToAllPersonalData, ParentRefDef, PersonalAnnotations, PersonalAnnotationsLongText, PersonalAnnotationsNoFind, PersonalSubject, PlatformComponent, PluralForms, PostDeleteHookFn, PostSaveHookFn, PreDeleteHookFn, PreQueryHookFn, PreSaveHookFn, ProjectionDefinition, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, ProjectionTable, QualifiedEventName, QueryEvent, QueryHandlerDef, QueryHandlerFn, RateLimitPer, RecordHeaderSpec, RecordHeaderSubtitlePart, ReferenceDataDef, Registry, RelationDefinition, ResolvedPiiFlags, RetentionDef, RoleAccessPersonalData, RoleAccessRule, RowAction, RowActionNavigate, RowActionNavigateBase, RowActionWriteHandler, RowFieldExtractor, SaveContext, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenNavSugar, ScreenSlots, SecretKeyDefinition, SecretKeyHandle, SecretMintConfirmStep, SecretMintScreenDefinition, SecretNamespaceDefinition, SecretNamespaceHandle, SecretNamespaceOptions, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, SelectFieldDef, SessionUser, SessionUserOrigin, StreamHandlerDef, StreamHandlerFn, Subscribe, TargetRef, TenantId, TenantWriteTarget, TextFieldDef, ToolbarAction, TranslationEntry, TranslationKeys, TranslationsDef, TranslationValue, TreeAction, TreeActionDef, TreeActionsHandle, TreeChildrenSubscribe, TreeNode, TreeNodeState, UnitKey, UnsafeAppendEventFn, ValidationError, ValidationHookFn, WorkspaceDefinition, WriteEvent, WriteHandlerDef, WriteHandlerFn, WriteResult, } from "./types/index.js";
84
84
  export { DEFAULT_CURRENCIES, DEFAULT_LOCALES, DELIVERY_FAILURE_CODES, DELIVERY_SKIP_REASONS, HookPhases, HTTP_ERROR_CODE_PATTERN, isAgentVisibleScreen, isDeliveryErrorCode, isOpenToAllGranted, resolveAgentExposure, } from "./types/index.js";
85
85
  export type { AwaitedEventType, PipelineBuildCtx, PipelineCtx, PipelineDef, StepBuilder, StepDef, StepFailureStrategy, StepInstance, StepKind, StepNamespace, StepResolver, } from "./types/step.js";
86
86
  export { runValidation } from "./validation.js";
@@ -4,7 +4,7 @@ export { hasAccess } from "./access.js";
4
4
  export { isPrincipalStatusPlugin, isTenantLifecycleStatusPlugin, TENANT_TEARDOWN_STATUSES, } from "./active-membership.js";
5
5
  export { PII_DIRECT_NAME_HINTS, PII_USER_OWNED_NAME_HINTS, PII_USER_REFERENCE_NAME_HINTS, } from "./boot-validator/entity-handler.js";
6
6
  export { entityHasAnonymizableSubjectField } from "./boot-validator/pii-retention.js";
7
- export { collectWriteHandlerQns, MAX_TRANSFER_DEPTH, SECURITY_BASELINE_FEATURE_NAMES, validateAppCustomScreenWriteQns, validateBoot, } from "./boot-validator.js";
7
+ export { collectCardMetaOverflow, collectWriteHandlerQns, MAX_TRANSFER_DEPTH, SECURITY_BASELINE_FEATURE_NAMES, validateAppCustomScreenWriteQns, validateBoot, } from "./boot-validator.js";
8
8
  export { buildAppSchema } from "./build-app-schema.js";
9
9
  export { buildConfigFeatureSchema, SETTINGS_HUB_FEATURE, SETTINGS_HUB_WORKSPACE, } from "./build-config-feature-schema.js";
10
10
  export { buildTarget } from "./build-target.js";
@@ -7,7 +7,7 @@ export type { ExtensionOptionsArgs, ExtensionOptionsFor, KumikoExtensionOptionsM
7
7
  export type { BootCheckContext, BootCheckFn, FeatureDefinition, FeatureMetricDef, FeatureMetricType, FeatureRegistrar, MetricOptions, Registry, SecretKeyDefinition, SecretKeyHandle, SecretNamespaceDefinition, SecretNamespaceHandle, SecretNamespaceOptions, SecretOptions, StoreTableDef, StoreTableEntry, StoreTableOptions, UiHintOption, UiHints, } from "@cosmicdrift/kumiko-types/feature";
8
8
  export type { FieldIconKey } from "@cosmicdrift/kumiko-types/field-icon";
9
9
  export type { AnyFileFieldDef, BigIntFieldDef, BooleanFieldDef, ClientDerivedFieldDef, DateFieldDef, DecimalFieldDef, DefaultCurrency, DeriveContext, DerivedFieldDef, DerivedFieldsMap, DerivedValueType, EmbeddedDerivedCellDef, EmbeddedFieldDef, EmbeddedSubFieldDef, EntityDefinition, EntityEventMigration, EntityIndexDef, FieldAccess, FieldDefinition, FieldsMap, FileFieldDef, FilesFieldDef, Findability, ImageFieldDef, ImagesFieldDef, JsonbFieldDef, LocatedTimestampFieldDef, LongTextFieldDef, LongTextFindability, MoneyCurrencySource, MoneyFieldDef, MultiSelectFieldDef, NumberFieldDef, OptionsQueryPayload, OptionsQueryPayloadValue, ParentRefDef, PersonalAnnotations, PersonalAnnotationsLongText, PersonalAnnotationsNoFind, PersonalSubject, ReferenceFieldDef, ResolvedPiiFlags, RetentionDef, SelectFieldDef, SelectOptionTone, TextFieldDef, TimestampFieldDef, TransitionMap, TzFieldDef, } from "@cosmicdrift/kumiko-types/fields";
10
- export type { AccessRule, ActiveMembership, ActiveMembershipRejection, ActiveMembershipResult, AgentExposure, AgentHandlerHints, AgentRisk, AggregateStreamHandle, AppContext, AppendEventArgs, AppendEventFn, AuthClaimsContext, AuthClaimsFn, AuthClaimsHookDef, CamelToKebab, ChatSendFailureCode, ClaimKeyDefinition, ClaimKeyHandle, ClaimKeyJsType, ClaimKeyType, DeclarativeEventMigration, DeliveryErrorCode, DeliveryFailureCode, DeliverySkipReason, DispatchWriteRef, EntityRef, EscapeHatchAuditSink, EscapeHatchDeclaration, EscapeHatchKind, EscapeHatchReporter, EscapeHatchTarget, EscapeHatchUseEvent, EventDef, EventMigrationDef, EventPiiFields, EventPiiStance, EventUpcastCtx, EventUpcastFn, FetchForWritingArgs, HandlerContext, HandlerRef, JobContext, JobRunnerRef, JobTriggerEvent, MemberReader, NameOrRef, NotifyDelivery, NotifyDeliveryStatus, NotifyFactory, NotifyFn, NotifyJobDispatcher, NotifyOptions, NotifyPriority, NotifyResult, OpenToAllAccessRule, OpenToAllDeclaration, OpenToAllPersonalData, PayloadRateLimitOption, QualifiedEventName, QueryEvent, QueryHandlerDef, QueryHandlerFn, RateLimitDeclaration, RateLimitDisabled, RateLimitOption, RateLimitPer, RoleAccessPersonalData, RoleAccessRule, SessionUser, SessionUserOrigin, StreamHandlerDef, StreamHandlerFn, TryAppendEventFn, TryAppendEventResult, UnsafeAppendEventFn, WriteEvent, WriteHandlerDef, WriteHandlerFn, WriteResult, } from "@cosmicdrift/kumiko-types/handlers";
10
+ export type { AccessRule, ActiveMembership, ActiveMembershipRejection, ActiveMembershipResult, AgentExposure, AgentHandlerHints, AgentRisk, AggregateStreamHandle, AppContext, AppendEventArgs, AppendEventFn, AuthClaimsContext, AuthClaimsFn, AuthClaimsHookDef, CamelToKebab, ChatSendFailureCode, ClaimKeyDefinition, ClaimKeyHandle, ClaimKeyJsType, ClaimKeyType, DeclarativeEventMigration, DeliveryErrorCode, DeliveryFailureCode, DeliverySkipReason, DispatchWriteRef, EntityRef, EscapeHatchAuditSink, EscapeHatchDeclaration, EscapeHatchKind, EscapeHatchReporter, EscapeHatchTarget, EscapeHatchUseEvent, EventDef, EventMigrationDef, EventPiiFields, EventPiiStance, EventUpcastCtx, EventUpcastFn, FetchForWritingArgs, HandlerContext, HandlerRef, JobContext, JobRunnerRef, JobTriggerEvent, MemberReader, NameOrRef, NotifyDelivery, NotifyDeliveryStatus, NotifyFactory, NotifyFn, NotifyJobDispatcher, NotifyOptions, NotifyPriority, NotifyResult, OpenToAllAccessRule, OpenToAllDeclaration, OpenToAllPersonalData, PayloadRateLimitOption, QualifiedEventName, QueryEvent, QueryHandlerDef, QueryHandlerFn, RateLimitDeclaration, RateLimitDisabled, RateLimitOption, RateLimitPer, RoleAccessPersonalData, RoleAccessRule, SessionUser, SessionUserOrigin, StreamHandlerDef, StreamHandlerFn, TenantWriteTarget, TryAppendEventFn, TryAppendEventResult, UnsafeAppendEventFn, WriteEvent, WriteHandlerDef, WriteHandlerFn, WriteResult, } from "@cosmicdrift/kumiko-types/handlers";
11
11
  export { DELIVERY_FAILURE_CODES, DELIVERY_SKIP_REASONS, HTTP_ERROR_CODE_PATTERN, isDeliveryErrorCode, isOpenToAllGranted, isRateLimitDisabled, isUiAccessGranted, } from "@cosmicdrift/kumiko-types/handlers";
12
12
  export type { DeleteContext, EntityHookMap, HookMap, HookOwner, HookPhase, LifecycleHookFn, LifecycleResult, OwnedFn, PhasedHook, PostDeleteBatchHookFn, PostDeleteHookFn, PostQueryHookFn, PostSaveBatchHookFn, PostSaveHookFn, PreDeleteHookFn, PreQueryHookFn, PreSaveHookFn, SaveContext, SearchPayloadContributorFn, ValidationError, ValidationHookFn, } from "@cosmicdrift/kumiko-types/hooks";
13
13
  export type { HttpRouteDefinition, HttpRouteHandler, HttpRouteHandlerDeps, HttpRouteMethod, } from "@cosmicdrift/kumiko-types/http-route";
@@ -17,7 +17,7 @@ export type { ContentCollectionDefinition, NavDefinition, NavIconKey, } from "@c
17
17
  export type { FromRule, FromRuleKind, OwnershipClause, OwnershipMap, OwnershipRef, OwnershipRule, SqlFragment, WhereRule, WhereRuleContext, } from "@cosmicdrift/kumiko-types/ownership";
18
18
  export type { EntityProjectionExtension, MspErrorMode, MspErrorPolicy, MultiStreamApplyFn, MultiStreamProjectionDefinition, ProjectionDefinition, ProjectionRowIdOf, ProjectionTable, SingleStreamApplyFn, } from "@cosmicdrift/kumiko-types/projection";
19
19
  export type { BelongsToRelation, EntityRelations, HasManyRelation, ManyToManyRelation, RelationDefinition, } from "@cosmicdrift/kumiko-types/relations";
20
- export type { ActionFormScreenDefinition, ConfigEditScreenDefinition, CustomScreenDefinition, CustomScreenRoute, DashboardChartMarkerKind, DashboardChartPanel, DashboardChartRangeOption, DashboardChartRanges, DashboardCustomPanel, DashboardDateParam, DashboardFeedPanel, DashboardFilterDefinition, DashboardI18nText, DashboardListPanel, DashboardMoneyParam, DashboardPanelDefinition, DashboardPanelGate, DashboardPanelVisibility, DashboardProgressListPanel, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DashboardText, DashboardTextParam, DashboardValueFormat, EditExtensionSection, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EditWriteFormSection, EntityEditScreenDefinition, EntityListExpandableRow, EntityListScreenDefinition, FieldCondition, FieldFormatRegistry, FieldRenderer, FormatSpec, FormWidth, ListColumnSpec, ListFacetSpec, ListPaginationMode, ListSortDir, ListSortSpec, MetricNavigate, MetricSpec, PlatformComponent, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, RecordHeaderSpec, RecordHeaderSubtitlePart, RowAction, RowActionNavigate, RowActionNavigateBase, RowActionWriteHandler, RowFieldExtractor, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenNavSugar, ScreenSlots, SecretMintConfirmStep, SecretMintScreenDefinition, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, ToolbarAction, UnitKey, } from "@cosmicdrift/kumiko-types/screen";
20
+ export type { ActionFormScreenDefinition, ConfigEditScreenDefinition, CustomScreenDefinition, CustomScreenRoute, DashboardChartMarkerKind, DashboardChartPanel, DashboardChartRangeOption, DashboardChartRanges, DashboardCustomPanel, DashboardDateParam, DashboardFeedPanel, DashboardFilterDefinition, DashboardI18nText, DashboardListPanel, DashboardMoneyParam, DashboardPanelDefinition, DashboardPanelGate, DashboardPanelVisibility, DashboardProgressListPanel, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DashboardText, DashboardTextParam, DashboardValueFormat, EditExtensionSection, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EditWriteFormSection, EntityEditAction, EntityEditActionPlacement, EntityEditScreenDefinition, EntityListExpandableRow, EntityListScreenDefinition, FieldCondition, FieldFormatRegistry, FieldRenderer, FormatSpec, FormWidth, ListColumnSpec, ListFacetSpec, ListPaginationMode, ListSortDir, ListSortSpec, MetricNavigate, MetricSpec, PlatformComponent, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, RecordHeaderSpec, RecordHeaderSubtitlePart, RowAction, RowActionNavigate, RowActionNavigateBase, RowActionWriteHandler, RowFieldExtractor, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenNavSugar, ScreenSlots, SecretMintConfirmStep, SecretMintScreenDefinition, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, ToolbarAction, UnitKey, } from "@cosmicdrift/kumiko-types/screen";
21
21
  export { metricField, relatedListGroupHeaderLabel, relatedListGroupKey, } from "@cosmicdrift/kumiko-types/screen";
22
22
  export type { TargetRef } from "@cosmicdrift/kumiko-types/target-ref";
23
23
  export type { Subscribe, TreeAction, TreeActionDef, TreeActionsHandle, TreeChildrenSubscribe, TreeNode, TreeNodeState, } from "@cosmicdrift/kumiko-types/tree-node";
@@ -4,6 +4,7 @@ export declare const fileRefEntity: import("@cosmicdrift/kumiko-types/fields").E
4
4
  readonly description?: string;
5
5
  readonly maxLength?: number;
6
6
  readonly minLength?: number;
7
+ readonly autoComplete?: string;
7
8
  readonly required?: boolean;
8
9
  readonly searchable?: boolean;
9
10
  readonly sortable?: boolean;
@@ -26,6 +27,7 @@ export declare const fileRefEntity: import("@cosmicdrift/kumiko-types/fields").E
26
27
  readonly description?: string;
27
28
  readonly maxLength?: number;
28
29
  readonly minLength?: number;
30
+ readonly autoComplete?: string;
29
31
  readonly required?: boolean;
30
32
  readonly searchable?: boolean;
31
33
  readonly sortable?: boolean;
@@ -48,6 +50,7 @@ export declare const fileRefEntity: import("@cosmicdrift/kumiko-types/fields").E
48
50
  readonly description?: string;
49
51
  readonly maxLength?: number;
50
52
  readonly minLength?: number;
53
+ readonly autoComplete?: string;
51
54
  readonly required?: boolean;
52
55
  readonly searchable?: boolean;
53
56
  readonly sortable?: boolean;
@@ -82,6 +85,7 @@ export declare const fileRefEntity: import("@cosmicdrift/kumiko-types/fields").E
82
85
  readonly description?: string;
83
86
  readonly maxLength?: number;
84
87
  readonly minLength?: number;
88
+ readonly autoComplete?: string;
85
89
  readonly required?: boolean;
86
90
  readonly searchable?: boolean;
87
91
  readonly sortable?: boolean;
@@ -104,6 +108,7 @@ export declare const fileRefEntity: import("@cosmicdrift/kumiko-types/fields").E
104
108
  readonly description?: string;
105
109
  readonly maxLength?: number;
106
110
  readonly minLength?: number;
111
+ readonly autoComplete?: string;
107
112
  readonly required?: boolean;
108
113
  readonly searchable?: boolean;
109
114
  readonly sortable?: boolean;
@@ -126,6 +131,7 @@ export declare const fileRefEntity: import("@cosmicdrift/kumiko-types/fields").E
126
131
  readonly description?: string;
127
132
  readonly maxLength?: number;
128
133
  readonly minLength?: number;
134
+ readonly autoComplete?: string;
129
135
  readonly required?: boolean;
130
136
  readonly searchable?: boolean;
131
137
  readonly sortable?: boolean;
@@ -4,6 +4,7 @@ export declare const fileRefsTable: import("../db/table-builder.js").EntityTable
4
4
  readonly description?: string;
5
5
  readonly maxLength?: number;
6
6
  readonly minLength?: number;
7
+ readonly autoComplete?: string;
7
8
  readonly required?: boolean;
8
9
  readonly searchable?: boolean;
9
10
  readonly sortable?: boolean;
@@ -26,6 +27,7 @@ export declare const fileRefsTable: import("../db/table-builder.js").EntityTable
26
27
  readonly description?: string;
27
28
  readonly maxLength?: number;
28
29
  readonly minLength?: number;
30
+ readonly autoComplete?: string;
29
31
  readonly required?: boolean;
30
32
  readonly searchable?: boolean;
31
33
  readonly sortable?: boolean;
@@ -48,6 +50,7 @@ export declare const fileRefsTable: import("../db/table-builder.js").EntityTable
48
50
  readonly description?: string;
49
51
  readonly maxLength?: number;
50
52
  readonly minLength?: number;
53
+ readonly autoComplete?: string;
51
54
  readonly required?: boolean;
52
55
  readonly searchable?: boolean;
53
56
  readonly sortable?: boolean;
@@ -82,6 +85,7 @@ export declare const fileRefsTable: import("../db/table-builder.js").EntityTable
82
85
  readonly description?: string;
83
86
  readonly maxLength?: number;
84
87
  readonly minLength?: number;
88
+ readonly autoComplete?: string;
85
89
  readonly required?: boolean;
86
90
  readonly searchable?: boolean;
87
91
  readonly sortable?: boolean;
@@ -104,6 +108,7 @@ export declare const fileRefsTable: import("../db/table-builder.js").EntityTable
104
108
  readonly description?: string;
105
109
  readonly maxLength?: number;
106
110
  readonly minLength?: number;
111
+ readonly autoComplete?: string;
107
112
  readonly required?: boolean;
108
113
  readonly searchable?: boolean;
109
114
  readonly sortable?: boolean;
@@ -126,6 +131,7 @@ export declare const fileRefsTable: import("../db/table-builder.js").EntityTable
126
131
  readonly description?: string;
127
132
  readonly maxLength?: number;
128
133
  readonly minLength?: number;
134
+ readonly autoComplete?: string;
129
135
  readonly required?: boolean;
130
136
  readonly searchable?: boolean;
131
137
  readonly sortable?: boolean;
@@ -283,6 +283,7 @@ export function requiredKeysFromScreen(featureName, screen, options = {}) {
283
283
  pushKey(out, mint.reveal.title);
284
284
  pushKey(out, mint.reveal.warning);
285
285
  pushKey(out, mint.reveal.confirmLabel);
286
+ pushKey(out, mint.reveal.acknowledge);
286
287
  for (const revealField of mint.reveal.fields) {
287
288
  pushKey(out, revealField.label);
288
289
  }
@@ -869,7 +869,6 @@ export function createJobRunner(options) {
869
869
  }
870
870
  return dispatchWriteRef.queryAs(user, qn, payload, jobOrigin);
871
871
  };
872
- // Only writeAs is gated for jobs; queryAs stays ungated (fw#2859).
873
872
  const gatedIdentitySwitch = createGatedIdentitySwitch(`job "${jobName}"`, jobSystemUser, isSystemJob || identitySwitchHatch !== undefined, {
874
873
  queryAs: ungatedQueryAs,
875
874
  writeAs: async (user, qn, payload) => {
@@ -909,7 +908,7 @@ export function createJobRunner(options) {
909
908
  return dispatchWriteRef.write(jobSystemUser, qn, payload, jobOrigin);
910
909
  },
911
910
  writeAs: gatedIdentitySwitch.writeAs,
912
- queryAs: ungatedQueryAs,
911
+ queryAs: gatedIdentitySwitch.queryAs,
913
912
  queryAsMember: (userId, qn, payload) => {
914
913
  if (!dispatchWriteRef) {
915
914
  throw new Error("JobContext.queryAsMember called before dispatcher attached — call attachDispatcher() first");
@@ -3,9 +3,10 @@ import { requestContext, runWithOrigin } from "../api/request-context.js";
3
3
  import { runInSavepoint, selectMany } from "../db/query.js";
4
4
  import { createTenantDb, createUncheckedSystemDb, hasTenantColumn, runInOwnTransaction, } from "../db/tenant-db.js";
5
5
  import { createDerivativesContext } from "../derivatives/derivatives-context.js";
6
+ import { mayOverrideTenant } from "../engine/cross-tenant.js";
6
7
  import { createSystemUser } from "../engine/system-user.js";
7
8
  import { isRateLimitDisabled, resolveAgentExposure } from "../engine/types/index.js";
8
- import { AccessDeniedError, FeatureDisabledError, InternalError, memberResolutionReadOnlyDenied, RateLimitError, RateLimitUnavailableError, VersionConflictError, } from "../errors/index.js";
9
+ import { AccessDeniedError, FeatureDisabledError, FrameworkReasons, InternalError, memberResolutionReadOnlyDenied, RateLimitError, RateLimitUnavailableError, VersionConflictError, } from "../errors/index.js";
9
10
  import { archiveStream as archiveStreamHelper, isStreamArchived, restoreStream as restoreStreamHelper, } from "../event-store/archive.js";
10
11
  import { IdempotentAppendConflictError as EventStoreIdempotentAppendConflictError, VersionConflictError as EventStoreVersionConflictError, } from "../event-store/errors.js";
11
12
  import { getStreamVersion, loadAggregate, loadAggregateAsOf, } from "../event-store/event-store.js";
@@ -178,6 +179,7 @@ export function applyMemberResolutionReadOnly(handlerContext) {
178
179
  restoreStream: denyMemberResolutionWrite,
179
180
  snapshotAggregate: denyMemberResolutionWrite,
180
181
  queryAsMember: denyMemberResolutionWrite,
182
+ forTenant: undefined,
181
183
  resolveActiveMembership: denyMemberResolutionWrite,
182
184
  scheduleAfterCommit: () => {
183
185
  throw memberResolutionReadOnlyDenied();
@@ -226,9 +228,11 @@ export async function buildHandlerContext(ctx, type, user, origin, tx, afterComm
226
228
  });
227
229
  const identitySwitchGrantReason = identitySwitchEscapeHatch?.reason ??
228
230
  (isSystem ? `r.systemScope() feature "${featureName ?? "unknown"}"` : undefined);
229
- const buildTenantScopedDb = (source, signal) => createTenantDb(source, user.tenantId, isSystem ? "system" : "tenant", context.tracer, context.meter, signal, {
230
- globalWrites: escapeHatchFor(writeEscapeHatch, "globalWrites"),
231
- unsafeRaw: escapeHatchFor(handlerEscapeHatch, "unsafeRaw"),
231
+ const buildTenantScopedDb = (source, signal, foreignTenantId) => createTenantDb(source, foreignTenantId ?? user.tenantId, isSystem && foreignTenantId === undefined ? "system" : "tenant", context.tracer, context.meter, signal, {
232
+ ...(foreignTenantId === undefined && {
233
+ globalWrites: escapeHatchFor(writeEscapeHatch, "globalWrites"),
234
+ unsafeRaw: escapeHatchFor(handlerEscapeHatch, "unsafeRaw"),
235
+ }),
232
236
  report: reportEscapeHatch,
233
237
  memberReadOnly: isMemberResolutionPrincipal(user),
234
238
  personalDataGate: buildPersonalDataGate(registry, origin),
@@ -309,6 +313,26 @@ export async function buildHandlerContext(ctx, type, user, origin, tx, afterComm
309
313
  });
310
314
  }
311
315
  : undefined;
316
+ // Write handlers only, and not r.systemScope() ones (they have ctx.systemDb). The gate is the
317
+ // dispatcher's tenantIdOverride predicate, so no caller reaches more than it could via
318
+ // tenantIdOverride. The foreign db carries no unsafeRaw/globalWrites grant and is tenant mode
319
+ // bound to the target; the executor accepts streamTenantId only on a db bound to that tenant.
320
+ const forTenant = !isSystem && dbSource && registry.getWriteHandler(type) !== undefined
321
+ ? (targetTenantId) => {
322
+ if (targetTenantId === user.tenantId && exposedDb) {
323
+ return { db: exposedDb, streamTenantId: undefined };
324
+ }
325
+ if (!mayOverrideTenant(user)) {
326
+ throw new AccessDeniedError({
327
+ details: { reason: FrameworkReasons.tenantOverrideRequiresSystemAdmin },
328
+ });
329
+ }
330
+ return {
331
+ db: buildTenantScopedDb(dbSource, reqCtx?.signal, targetTenantId),
332
+ streamTenantId: targetTenantId,
333
+ };
334
+ }
335
+ : undefined;
312
336
  // ctx.files resolved per-tenant through file-foundation (lazy — the
313
337
  // provider is only resolved when a handle actually does I/O). Boot wires
314
338
  // _fileProviderResolver when a file-provider plugin is mounted; falls back
@@ -721,6 +745,7 @@ export async function buildHandlerContext(ctx, type, user, origin, tx, afterComm
721
745
  notify,
722
746
  ...(config && { config }),
723
747
  ...(configFor && { configFor }),
748
+ ...(forTenant && { forTenant }),
724
749
  ...(files && { files }),
725
750
  ...(derivatives && { derivatives }),
726
751
  // preSave hooks need `changes`/`previous`/`isNew`, which only exist once
@@ -37,15 +37,19 @@ import { InternalError } from "../errors/index.js";
37
37
  // call per tenant instead of one round-trip per event; handler stays the
38
38
  // per-event fallback the dispatcher falls back to when a batch throws.
39
39
  export const SEARCH_CONSUMER_NAME = "system:consumer:search";
40
+ // An executor update with includeDeleted leaves the row deleted; indexing it would make it findable again.
41
+ function isUpdateOfSoftDeletedRow(event, verb) {
42
+ return (verb === "updated" && reconstructStateForSearch(event.payload, "updated")["isDeleted"] === true);
43
+ }
40
44
  // Resolves what one event implies for the search index, without touching
41
45
  // the adapter — same decision tree the old per-event handler ran inline.
42
46
  async function resolveSearchOperation(event, ctx, registry) {
43
47
  const entityName = event.aggregateType;
44
48
  const verb = event.type.split(".").pop();
45
49
  const tenantId = event.tenantId;
46
- // skip: delete/forgotten remove the index entry — reconstruct only
47
- // makes sense for created/updated/restored (field data in payload).
48
- if (verb === "deleted" || verb === "forgotten") {
50
+ // skip: delete/forgotten (and an update on a still soft-deleted row) remove the
51
+ // index entry — reconstruct only makes sense for live created/updated/restored.
52
+ if (verb === "deleted" || verb === "forgotten" || isUpdateOfSoftDeletedRow(event, verb)) {
49
53
  return { kind: "remove", tenantId, entityType: entityName, entityId: event.aggregateId };
50
54
  }
51
55
  let state;
@@ -4,6 +4,7 @@ export declare const sharedWidgetEntity: import("@cosmicdrift/kumiko-types/field
4
4
  readonly description?: string;
5
5
  readonly maxLength?: number;
6
6
  readonly minLength?: number;
7
+ readonly autoComplete?: string;
7
8
  readonly required?: boolean;
8
9
  readonly searchable?: boolean;
9
10
  readonly sortable?: boolean;
@@ -28,6 +29,7 @@ export declare const sharedWidgetTable: import("../db/table-builder.js").EntityT
28
29
  readonly description?: string;
29
30
  readonly maxLength?: number;
30
31
  readonly minLength?: number;
32
+ readonly autoComplete?: string;
31
33
  readonly required?: boolean;
32
34
  readonly searchable?: boolean;
33
35
  readonly sortable?: boolean;
@@ -52,6 +54,7 @@ export declare const sharedUserEntity: import("@cosmicdrift/kumiko-types/fields"
52
54
  readonly description?: string;
53
55
  readonly maxLength?: number;
54
56
  readonly minLength?: number;
57
+ readonly autoComplete?: string;
55
58
  readonly required?: boolean;
56
59
  readonly searchable?: boolean;
57
60
  readonly sortable?: boolean;
@@ -74,6 +77,7 @@ export declare const sharedUserEntity: import("@cosmicdrift/kumiko-types/fields"
74
77
  readonly description?: string;
75
78
  readonly maxLength?: number;
76
79
  readonly minLength?: number;
80
+ readonly autoComplete?: string;
77
81
  readonly required?: boolean;
78
82
  readonly searchable?: boolean;
79
83
  readonly sortable?: boolean;
@@ -96,6 +100,7 @@ export declare const sharedUserEntity: import("@cosmicdrift/kumiko-types/fields"
96
100
  readonly description?: string;
97
101
  readonly maxLength?: number;
98
102
  readonly minLength?: number;
103
+ readonly autoComplete?: string;
99
104
  readonly required?: boolean;
100
105
  readonly searchable?: boolean;
101
106
  readonly sortable?: boolean;
@@ -123,6 +128,7 @@ export declare const sharedUserTable: import("../db/table-builder.js").EntityTab
123
128
  readonly description?: string;
124
129
  readonly maxLength?: number;
125
130
  readonly minLength?: number;
131
+ readonly autoComplete?: string;
126
132
  readonly required?: boolean;
127
133
  readonly searchable?: boolean;
128
134
  readonly sortable?: boolean;
@@ -145,6 +151,7 @@ export declare const sharedUserTable: import("../db/table-builder.js").EntityTab
145
151
  readonly description?: string;
146
152
  readonly maxLength?: number;
147
153
  readonly minLength?: number;
154
+ readonly autoComplete?: string;
148
155
  readonly required?: boolean;
149
156
  readonly searchable?: boolean;
150
157
  readonly sortable?: boolean;
@@ -167,6 +174,7 @@ export declare const sharedUserTable: import("../db/table-builder.js").EntityTab
167
174
  readonly description?: string;
168
175
  readonly maxLength?: number;
169
176
  readonly minLength?: number;
177
+ readonly autoComplete?: string;
170
178
  readonly required?: boolean;
171
179
  readonly searchable?: boolean;
172
180
  readonly sortable?: boolean;
@@ -194,6 +202,7 @@ export declare const sharedItemEntity: import("@cosmicdrift/kumiko-types/fields"
194
202
  readonly description?: string;
195
203
  readonly maxLength?: number;
196
204
  readonly minLength?: number;
205
+ readonly autoComplete?: string;
197
206
  readonly required?: boolean;
198
207
  readonly searchable?: boolean;
199
208
  readonly sortable?: boolean;
@@ -237,6 +246,7 @@ export declare const sharedItemTable: import("../db/table-builder.js").EntityTab
237
246
  readonly description?: string;
238
247
  readonly maxLength?: number;
239
248
  readonly minLength?: number;
249
+ readonly autoComplete?: string;
240
250
  readonly required?: boolean;
241
251
  readonly searchable?: boolean;
242
252
  readonly sortable?: boolean;
@@ -0,0 +1,14 @@
1
+ export declare const CARD_META_MAX = 3;
2
+ export type CardRoleColumn = {
3
+ readonly field: string;
4
+ readonly highlighted?: boolean;
5
+ readonly type?: string;
6
+ readonly renderer?: unknown;
7
+ readonly hideOnNarrow?: boolean;
8
+ };
9
+ export type CardColumnRoles<C extends CardRoleColumn> = {
10
+ readonly title: C | undefined;
11
+ readonly status: C | undefined;
12
+ readonly meta: readonly C[];
13
+ };
14
+ export declare function cardColumnRoles<C extends CardRoleColumn>(columns: readonly C[]): CardColumnRoles<C>;
@@ -0,0 +1,9 @@
1
+ export const CARD_META_MAX = 3;
2
+ // One rule for the narrow-layout list card, shared by the renderer and the
3
+ // boot validator so the validator counts exactly what the card would draw.
4
+ export function cardColumnRoles(columns) {
5
+ const title = columns.find((col) => col.highlighted === true) ?? columns[0];
6
+ const status = columns.find((col) => col !== title && col.type === "select" && col.renderer === undefined);
7
+ const meta = columns.filter((col) => col !== title && col !== status && col.hideOnNarrow !== true);
8
+ return { title, status, meta };
9
+ }
@@ -9,13 +9,15 @@ export type { BooleanFieldDef, DateFieldDef, EmbeddedDerivedCellDef, EntityDefin
9
9
  export type { AccessRule, OpenToAllAccessRule, OpenToAllDeclaration, OpenToAllPersonalData, RoleAccessPersonalData, RoleAccessRule, } from "../engine/types/handlers.js";
10
10
  export { isOpenToAllGranted, isUiAccessGranted } from "../engine/types/handlers.js";
11
11
  export type { IconKey, NavDefinition, NavIconKey } from "../engine/types/nav.js";
12
- export type { ActionFormFooterAction, ActionFormRedirect, ActionFormScreenDefinition, ConfigEditScreenDefinition, CustomScreenDefinition, CustomScreenRoute, DashboardChartKind, DashboardChartMarkerKind, DashboardChartPanel, DashboardChartRangeOption, DashboardChartRanges, DashboardChartTone, DashboardCustomPanel, DashboardDateParam, DashboardFeedPanel, DashboardFilterDefinition, DashboardI18nText, DashboardListPanel, DashboardMoneyParam, DashboardPanelDefinition, DashboardPanelEmptyState, DashboardPanelGate, DashboardPanelQueryOptions, DashboardPanelSpan, DashboardPanelVisibility, DashboardProgressListPanel, DashboardScopeDefinition, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DashboardText, DashboardTextParam, DashboardTimeRangeDefinition, DashboardValueFormat, EditExtensionSection, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EditWriteFormSection, EntityEditScreenDefinition, EntityListExpandableRow, EntityListFacetConfig, EntityListFacetExtraOption, EntityListScreenDefinition, EntityWriteExcludedFields, FieldCondition, FieldIconKey, FieldRenderer, FormWidth, ListBadgeTone, ListColumnDisplay, ListColumnSpec, ListFacetSpec, ListSortSpec, MetricNavigate, MetricSpec, PlatformComponent, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, RecordHeaderSubtitlePart, RelatedListGroupBy, RelatedListRowTone, RelatedListToolbarAction, RowAction, RowActionDisplay, RowActionDrawer, RowActionMode, RowActionNavigate, RowActionWriteHandler, RowFieldExtractor, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenSlots, SecretMintConfirmStep, SecretMintScreenDefinition, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, ToolbarAction, } from "../engine/types/screen.js";
12
+ export type { ActionFormFooterAction, ActionFormRedirect, ActionFormScreenDefinition, ConfigEditScreenDefinition, CustomScreenDefinition, CustomScreenRoute, DashboardChartKind, DashboardChartMarkerKind, DashboardChartPanel, DashboardChartRangeOption, DashboardChartRanges, DashboardChartTone, DashboardCustomPanel, DashboardDateParam, DashboardFeedPanel, DashboardFilterDefinition, DashboardI18nText, DashboardListPanel, DashboardMoneyParam, DashboardPanelDefinition, DashboardPanelEmptyState, DashboardPanelGate, DashboardPanelQueryOptions, DashboardPanelSpan, DashboardPanelVisibility, DashboardProgressListPanel, DashboardScopeDefinition, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DashboardText, DashboardTextParam, DashboardTimeRangeDefinition, DashboardValueFormat, EditExtensionSection, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EditWriteFormSection, EntityEditAction, EntityEditActionPlacement, EntityEditScreenDefinition, EntityListExpandableRow, EntityListFacetConfig, EntityListFacetExtraOption, EntityListScreenDefinition, EntityWriteExcludedFields, FieldCondition, FieldIconKey, FieldRenderer, FormWidth, ListBadgeTone, ListColumnDisplay, ListColumnSpec, ListFacetSpec, ListSortSpec, MetricNavigate, MetricSpec, PlatformComponent, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, RecordHeaderSubtitlePart, RelatedListGroupBy, RelatedListRowTone, RelatedListToolbarAction, RowAction, RowActionDisplay, RowActionDrawer, RowActionMode, RowActionNavigate, RowActionWriteHandler, RowFieldExtractor, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenSlots, SecretMintConfirmStep, SecretMintScreenDefinition, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, ToolbarAction, } from "../engine/types/screen.js";
13
13
  export { metricField, relatedListGroupHeaderLabel, relatedListGroupKey, } from "../engine/types/screen.js";
14
14
  export type { TargetRef } from "../engine/types/target-ref.js";
15
15
  export type { TreeAction, TreeNode, TreeNodeState } from "../engine/types/tree-node.js";
16
16
  export type { WorkspaceDefinition } from "../engine/types/workspace.js";
17
17
  export { ACTION_FORM_ENTITY, handlerFieldLabelKey, handlerTitleKey, PROJECTION_DETAIL_ENTITY, WRITE_FORM_SECTION_ENTITY, } from "../i18n/required-surface-keys.js";
18
18
  export type { AppSchema, FeatureSchema, QualifiedContentCollection, WorkspaceSchema, } from "./app-schema.js";
19
+ export type { CardColumnRoles, CardRoleColumn } from "./card-column-roles.js";
20
+ export { CARD_META_MAX, cardColumnRoles } from "./card-column-roles.js";
19
21
  export { type Formality, formalLocaleTag } from "./formality.js";
20
22
  export type { ListRowMetaColumnType, ListRowMetaReference, ReferenceLookupSource, SystemReferenceLabel, } from "./list-row-meta.js";
21
23
  export { LIST_ROW_META_COLUMNS, LIST_ROW_META_REFERENCES, REFERENCE_LOOKUP_SOURCES, SYSTEM_REFERENCE_LABELS, } from "./list-row-meta.js";
@@ -26,6 +26,7 @@ export { availableSelectOptions, evalFieldCondition, explicitListScreenId, findU
26
26
  export { isOpenToAllGranted, isUiAccessGranted } from "../engine/types/handlers.js";
27
27
  export { metricField, relatedListGroupHeaderLabel, relatedListGroupKey, } from "../engine/types/screen.js";
28
28
  export { ACTION_FORM_ENTITY, handlerFieldLabelKey, handlerTitleKey, PROJECTION_DETAIL_ENTITY, WRITE_FORM_SECTION_ENTITY, } from "../i18n/required-surface-keys.js";
29
+ export { CARD_META_MAX, cardColumnRoles } from "./card-column-roles.js";
29
30
  export { formalLocaleTag } from "./formality.js";
30
31
  export { LIST_ROW_META_COLUMNS, LIST_ROW_META_REFERENCES, REFERENCE_LOOKUP_SOURCES, SYSTEM_REFERENCE_LABELS, } from "./list-row-meta.js";
31
32
  export { resolveTranslationValue, translationValueOtherText, } from "./plural.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-framework",
3
- "version": "0.355.0",
3
+ "version": "0.357.0",
4
4
  "description": "Framework core — engine, pipeline, API, DB, and every other bit that makes Kumiko go.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -210,8 +210,8 @@
210
210
  "./package.json": "./package.json"
211
211
  },
212
212
  "dependencies": {
213
- "@cosmicdrift/kumiko-http": "0.355.0",
214
- "@cosmicdrift/kumiko-types": "0.355.0",
213
+ "@cosmicdrift/kumiko-http": "0.357.0",
214
+ "@cosmicdrift/kumiko-types": "0.357.0",
215
215
  "bullmq": "^5.76.7",
216
216
  "bun-types": "^1.3.13",
217
217
  "hono": "^4.13.1",
@@ -227,7 +227,7 @@
227
227
  "zod": "^4.4.3"
228
228
  },
229
229
  "devDependencies": {
230
- "@cosmicdrift/kumiko-dispatcher-live": "0.355.0",
230
+ "@cosmicdrift/kumiko-dispatcher-live": "0.357.0",
231
231
  "bun-types": "^1.3.13",
232
232
  "pino-pretty": "^13.1.3"
233
233
  },
package/src/changes.json CHANGED
@@ -1,4 +1,27 @@
1
1
  [
2
+ {
3
+ "version": "0.357.0",
4
+ "type": "improvement",
5
+ "title": "Executor update accepts includeDeleted for soft-deleted rows",
6
+ "detail": "`executor.update(payload, user, db, { includeDeleted: true })` loads the row without the soft-delete filter (the tenant filter stays), so server code can move a trashed row together with its parent (for example a team change) and a later restore lands consistently. The row stays deleted. The default is unchanged (NotFound) and the generic HTTP update handler never sets the option. The projection apply was already unfiltered by `isDeleted`, so live and rebuild agree. The search consumer treats an update on a soft-deleted row as a removal, so the moved row does not reappear in search."
7
+ },
8
+ {
9
+ "version": "0.357.0",
10
+ "type": "improvement",
11
+ "title": "Boot validation covers multiSelect optionsAvailabilityQuery on entity and screen fields"
12
+ },
13
+ {
14
+ "version": "0.356.0",
15
+ "type": "improvement",
16
+ "title": "Executor create accepts streamTenantId; ctx.forTenant(tenantId) for gated cross-tenant writes",
17
+ "detail": "createEventStoreExecutor().create takes { streamTenantId } (stream, event tenant, row tenant, PII key and cache invalidation follow it, the acting user stays the operator). The override is accepted on a system-mode db and also on a tenant-mode db bound to exactly that stream tenant. New handler-context method ctx.forTenant(tenantId) returns { db, streamTenantId }: a tenant-mode db bound to the target tenant plus the stream override to pass to the executor, so a handler needs no unsafeRaw escape hatch for cross-tenant writes. A foreign tenant is gated to SystemAdmin and system identities (AccessDeniedError with reason tenant_override_requires_system_admin otherwise); the caller's own tenant returns streamTenantId undefined. It is not offered to r.systemScope() handlers. The bundled cross-tenant handlers (tenant removeMember/updateMemberRoles, tier setTenantTier, custom-fields system fields, template-resolver upserts/set, managed-pages set, compliance-profiles setProfile) no longer rewrite event.user.tenantId; the event actor is the operator. tenant cancelPendingInvitation takes an optional streamTenantId."
18
+ },
19
+ {
20
+ "version": "0.356.0",
21
+ "type": "breaking",
22
+ "title": "Job ctx.queryAs is gated like ctx.writeAs",
23
+ "migration": "Jobs calling ctx.queryAs with createSystemUser(...) or any identity other than the job's own caller now fail with AccessDeniedError (system_identity_switch_denied / identity_switch_denied). Declare r.systemScope() on the job's feature or escapeHatch: { reason } on the job, the same grant ctx.writeAs already needs. The escape-hatch guard exempts jobs by name, so this surfaces only at runtime; grep your jobs for ctx.queryAs. Known consumer sites: offlot-app channel-text generate-texts.job, campaign-calendar sync, campaign-channel-foundation seed-starter-channels."
24
+ },
2
25
  {
3
26
  "version": "0.355.0",
4
27
  "type": "improvement",