@cosmicdrift/kumiko-framework 0.353.0 → 0.354.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/dist/api/index.d.ts +1 -1
  2. package/dist/api/index.js +1 -1
  3. package/dist/api/redis-sse-broker.d.ts +1 -0
  4. package/dist/api/redis-sse-broker.js +16 -10
  5. package/dist/api/request-context.d.ts +1 -0
  6. package/dist/api/request-context.js +5 -0
  7. package/dist/api/routes.js +4 -2
  8. package/dist/api/server.js +28 -3
  9. package/dist/api/sse-broker.d.ts +7 -3
  10. package/dist/api/sse-route.js +2 -6
  11. package/dist/api/token-expiry-timer.d.ts +1 -0
  12. package/dist/api/token-expiry-timer.js +10 -0
  13. package/dist/bun-db/query.js +29 -2
  14. package/dist/crypto/event-pii.js +4 -2
  15. package/dist/crypto/kms-wiring.js +31 -5
  16. package/dist/db/eagerload.d.ts +8 -3
  17. package/dist/db/eagerload.js +85 -81
  18. package/dist/db/event-store-executor-context.d.ts +12 -0
  19. package/dist/db/event-store-executor-context.js +59 -18
  20. package/dist/db/event-store-executor-read.js +81 -31
  21. package/dist/db/event-store-executor-write.js +54 -3
  22. package/dist/db/index.d.ts +1 -1
  23. package/dist/db/migrate-generator.js +1 -1
  24. package/dist/db/queries/backfill-pii.d.ts +1 -0
  25. package/dist/db/queries/backfill-pii.js +11 -6
  26. package/dist/db/queries/event-consumer.d.ts +2 -3
  27. package/dist/db/queries/event-consumer.js +54 -43
  28. package/dist/db/queries/event-store.d.ts +5 -0
  29. package/dist/db/queries/event-store.js +51 -3
  30. package/dist/db/queries/pii-backfill-state.d.ts +8 -1
  31. package/dist/db/queries/pii-backfill-state.js +16 -5
  32. package/dist/db/queries/shadow-swap.js +32 -3
  33. package/dist/db/render-ddl.js +6 -6
  34. package/dist/db/table-builder.js +6 -7
  35. package/dist/db/tenant-db.d.ts +5 -2
  36. package/dist/db/tenant-db.js +47 -7
  37. package/dist/derivatives/variant-key.d.ts +1 -0
  38. package/dist/derivatives/variant-key.js +3 -0
  39. package/dist/engine/boot-validator/access-declarations.js +5 -0
  40. package/dist/engine/boot-validator/config-deps.js +3 -0
  41. package/dist/engine/boot-validator/entity-handler.d.ts +1 -0
  42. package/dist/engine/boot-validator/entity-handler.js +40 -0
  43. package/dist/engine/boot-validator/handler-field-references.d.ts +2 -0
  44. package/dist/engine/boot-validator/handler-field-references.js +25 -0
  45. package/dist/engine/boot-validator/index.js +6 -1
  46. package/dist/engine/boot-validator/nav.js +3 -1
  47. package/dist/engine/boot-validator/ownership.d.ts +2 -1
  48. package/dist/engine/boot-validator/ownership.js +32 -1
  49. package/dist/engine/boot-validator/pii-retention.js +8 -1
  50. package/dist/engine/boot-validator/query-output-columns.js +9 -9
  51. package/dist/engine/boot-validator/screens.js +98 -7
  52. package/dist/engine/boot-validator/transfer-graph.d.ts +2 -1
  53. package/dist/engine/boot-validator/transfer-graph.js +8 -63
  54. package/dist/engine/boot-validator/url-prefill-fields.d.ts +1 -0
  55. package/dist/engine/boot-validator/url-prefill-fields.js +35 -0
  56. package/dist/engine/build-app-schema.js +3 -0
  57. package/dist/engine/changeset-changes.d.ts +1 -0
  58. package/dist/engine/changeset-changes.js +49 -33
  59. package/dist/engine/define-feature.js +11 -0
  60. package/dist/engine/entity-handlers.d.ts +3 -3
  61. package/dist/engine/entity-handlers.js +55 -34
  62. package/dist/engine/escape-hatch-reason.d.ts +2 -0
  63. package/dist/engine/escape-hatch-reason.js +8 -0
  64. package/dist/engine/extension-names.d.ts +8 -9
  65. package/dist/engine/extension-names.js +8 -9
  66. package/dist/engine/extensions/tenant-data.d.ts +3 -1
  67. package/dist/engine/extensions/tenant-resource.d.ts +9 -1
  68. package/dist/engine/factories.d.ts +1 -1
  69. package/dist/engine/feature-changelog.d.ts +2 -0
  70. package/dist/engine/feature-changelog.js +6 -0
  71. package/dist/engine/feature-config-events-jobs.js +41 -4
  72. package/dist/engine/handler-field-references.d.ts +3 -0
  73. package/dist/engine/handler-field-references.js +31 -0
  74. package/dist/engine/i18n-key.js +2 -3
  75. package/dist/engine/index.d.ts +8 -2
  76. package/dist/engine/index.js +6 -1
  77. package/dist/engine/ownership.js +3 -4
  78. package/dist/engine/project-app-schema-for-roles.js +3 -1
  79. package/dist/engine/registry-ingest.d.ts +1 -0
  80. package/dist/engine/registry-ingest.js +8 -3
  81. package/dist/engine/run-pipeline.js +12 -1
  82. package/dist/engine/schema-builder.js +35 -2
  83. package/dist/engine/screen-helpers.d.ts +13 -0
  84. package/dist/engine/screen-helpers.js +31 -0
  85. package/dist/engine/soft-delete-cleanup.js +3 -2
  86. package/dist/engine/steps/_read-source.d.ts +4 -0
  87. package/dist/engine/steps/_read-source.js +22 -0
  88. package/dist/engine/steps/describe-workflow-step-error.js +3 -1
  89. package/dist/engine/steps/read-find-many.js +2 -1
  90. package/dist/engine/steps/read-find-one.js +2 -1
  91. package/dist/engine/steps/workflow-step-error.d.ts +5 -0
  92. package/dist/engine/steps/workflow-step-error.js +13 -0
  93. package/dist/engine/transfer-adjacency.d.ts +24 -0
  94. package/dist/engine/transfer-adjacency.js +107 -0
  95. package/dist/entrypoint/index.d.ts +2 -0
  96. package/dist/entrypoint/index.js +2 -0
  97. package/dist/env/index.js +45 -23
  98. package/dist/errors/classes.d.ts +8 -0
  99. package/dist/errors/classes.js +13 -0
  100. package/dist/errors/index.d.ts +2 -1
  101. package/dist/errors/index.js +2 -1
  102. package/dist/errors/reasons.d.ts +1 -0
  103. package/dist/errors/reasons.js +3 -0
  104. package/dist/errors/subject-erased.d.ts +2 -0
  105. package/dist/errors/subject-erased.js +9 -0
  106. package/dist/event-store/admin-api.js +14 -10
  107. package/dist/event-store/event-store.d.ts +1 -0
  108. package/dist/event-store/event-store.js +14 -9
  109. package/dist/event-store/events-schema.js +2 -1
  110. package/dist/event-store/index.d.ts +1 -1
  111. package/dist/event-store/index.js +1 -1
  112. package/dist/files/file-handle.d.ts +1 -1
  113. package/dist/files/file-handle.js +30 -6
  114. package/dist/files/file-routes.js +10 -4
  115. package/dist/files/resolve-content-type.d.ts +2 -0
  116. package/dist/files/resolve-content-type.js +8 -0
  117. package/dist/i18n/required-surface-keys.d.ts +2 -0
  118. package/dist/i18n/required-surface-keys.js +8 -0
  119. package/dist/jobs/index.d.ts +1 -1
  120. package/dist/jobs/job-runner.d.ts +13 -0
  121. package/dist/jobs/job-runner.js +128 -22
  122. package/dist/migrations/pii-event-backfill.js +46 -25
  123. package/dist/observability/index.d.ts +1 -1
  124. package/dist/observability/index.js +1 -1
  125. package/dist/observability/standard-metrics.d.ts +4 -0
  126. package/dist/observability/standard-metrics.js +12 -0
  127. package/dist/pipeline/dispatch-shared.d.ts +2 -0
  128. package/dist/pipeline/dispatch-shared.js +74 -9
  129. package/dist/pipeline/dispatch-stream.d.ts +5 -1
  130. package/dist/pipeline/dispatch-stream.js +19 -12
  131. package/dist/pipeline/dispatcher.d.ts +3 -1
  132. package/dist/pipeline/dispatcher.js +10 -15
  133. package/dist/pipeline/event-consumer-state.js +0 -26
  134. package/dist/pipeline/event-dispatcher-admin.js +17 -11
  135. package/dist/pipeline/event-dispatcher-delivery.d.ts +1 -1
  136. package/dist/pipeline/event-dispatcher-delivery.js +3 -2
  137. package/dist/pipeline/event-dispatcher.js +48 -7
  138. package/dist/pipeline/index.d.ts +2 -0
  139. package/dist/pipeline/index.js +1 -0
  140. package/dist/pipeline/member-read-only-transaction.js +25 -5
  141. package/dist/pipeline/msp-rebuild.js +3 -3
  142. package/dist/pipeline/pending-gap-ranges.d.ts +1 -0
  143. package/dist/pipeline/pending-gap-ranges.js +22 -0
  144. package/dist/pipeline/system-hooks.js +4 -4
  145. package/dist/pipeline/system-identity-switch.d.ts +1 -1
  146. package/dist/pipeline/system-identity-switch.js +5 -4
  147. package/dist/pipeline/tenant-timezone-cache.js +5 -3
  148. package/dist/redis/cache-sync-topics.d.ts +1 -0
  149. package/dist/redis/cache-sync-topics.js +1 -0
  150. package/dist/redis/client.d.ts +1 -0
  151. package/dist/redis/client.js +7 -0
  152. package/dist/redis/index.d.ts +1 -1
  153. package/dist/redis/index.js +1 -1
  154. package/dist/redis/pubsub-signal.d.ts +1 -0
  155. package/dist/redis/pubsub-signal.js +3 -0
  156. package/dist/search/meilisearch-adapter.js +34 -4
  157. package/dist/stack/test-stack.d.ts +4 -1
  158. package/dist/stack/test-stack.js +9 -2
  159. package/dist/testing/e2e-generator.js +7 -2
  160. package/dist/ui-types/index.d.ts +3 -3
  161. package/dist/ui-types/index.js +2 -2
  162. package/dist/upgrade-cli.js +7 -2
  163. package/package.json +12 -4
  164. package/src/changes.json +209 -0
  165. package/src/scripts/codemod/migrate-cross-tenant.ts +2 -1
@@ -3,13 +3,15 @@ import * as z from "zod";
3
3
  import { collectReferenceFields, enrichRowWithReferences, enrichWithReferences, } from "../db/eagerload.js";
4
4
  import { createEventStoreExecutor } from "../db/event-store-executor.js";
5
5
  import { buildEntityTable } from "../db/table-builder.js";
6
- import { acknowledgeConventionCrossTenant } from "../db/tenant-db.js";
6
+ import { acknowledgeConventionCrossTenant, reportConventionCrossTenantUse, } from "../db/tenant-db.js";
7
7
  import { isSystemIdentity } from "../pipeline/system-identity-switch.js";
8
8
  import { assertUnreachable } from "../utils/index.js";
9
9
  import { PAGED_QUERY_HANDLER_BRAND } from "./define-handler.js";
10
+ import { codemodPlaceholderReasonProblem } from "./escape-hatch-reason.js";
10
11
  import { dropEmptyWriteOnlyValues } from "./field-access.js";
11
12
  import { instructionFieldNames } from "./instruction-fields.js";
12
13
  import { buildInsertSchema, buildUpdateSchema } from "./schema-builder.js";
14
+ import { parseTenantId } from "./types/identifiers.js";
13
15
  // Convention-based handler factories for event-sourced aggregates.
14
16
  //
15
17
  // You register one handler per call (no auto-generation of a whole CRUD set),
@@ -46,26 +48,27 @@ import { buildInsertSchema, buildUpdateSchema } from "./schema-builder.js";
46
48
  const WRITE_VERBS = ["create", "update", "delete", "restore"];
47
49
  const QUERY_VERBS = ["list", "detail"];
48
50
  const idSchema = z.object({ id: z.uuid() });
49
- // Keyed by handler function: the registrar rebuilds the def object but keeps the function reference.
50
- const deprecatedCrossTenantHandlers = new WeakSet();
51
- export function isDeprecatedCrossTenantHandler(handler) {
52
- return deprecatedCrossTenantHandlers.has(handler);
51
+ const SYSTEM_ADMIN_ROLE = "SystemAdmin";
52
+ // A write handler with escapeHatch reads/writes rows of every tenant, so only an operator
53
+ // role may reach it — otherwise any tenant-level role holder could mutate foreign tenants.
54
+ function isSystemAdminOnly(access) {
55
+ return ("roles" in access &&
56
+ access.roles.length > 0 &&
57
+ access.roles.every((role) => role === SYSTEM_ADMIN_ROLE));
53
58
  }
54
- export const DEPRECATED_CROSS_TENANT_SIGNAL = "deprecation:entity-handler-cross-tenant";
55
- function resolveCrossTenantReason(name, options) {
56
- if (options.escapeHatch !== undefined && options.crossTenant === true) {
57
- throw new Error(`"${name}": declare either escapeHatch or the deprecated crossTenant, not both.`);
59
+ function resolveCrossTenantReason(name, options, kind) {
60
+ if (options.escapeHatch === undefined)
61
+ return undefined;
62
+ if (options.escapeHatch.reason.trim().length === 0) {
63
+ throw new Error(`"${name}": escapeHatch requires a non-empty reason.`);
58
64
  }
59
- if (options.escapeHatch !== undefined) {
60
- if (options.escapeHatch.reason.trim().length === 0) {
61
- throw new Error(`"${name}": escapeHatch requires a non-empty reason.`);
62
- }
63
- return options.escapeHatch.reason;
64
- }
65
- if (options.crossTenant === true) {
66
- return `deprecated crossTenant: true on entity convention handler ${name}`;
65
+ const placeholderProblem = codemodPlaceholderReasonProblem(options.escapeHatch.reason);
66
+ if (placeholderProblem !== undefined)
67
+ throw new Error(`"${name}": ${placeholderProblem}`);
68
+ if (kind === "write" && !isSystemAdminOnly(options.access)) {
69
+ throw new Error(`"${name}": a write handler with escapeHatch reaches across every tenant, so its access must be SystemAdmin-only ({ roles: ["${SYSTEM_ADMIN_ROLE}"] }); got ${JSON.stringify(options.access)}.`);
67
70
  }
68
- return undefined;
71
+ return options.escapeHatch.reason;
69
72
  }
70
73
  // Upper bound on entity-list `limit`: unbounded/fractional values ran
71
74
  // straight into the raw `LIMIT ${limit}` SQL string (event-store-executor-
@@ -194,10 +197,10 @@ export function defineEntityWriteHandler(name, entity, options) {
194
197
  // behavior-preserving: ctx.db was already an unfiltered "system"-mode
195
198
  // TenantDb for these handlers before the ctx.db cutover.
196
199
  //
197
- // crossTenantReason (escapeHatch or the deprecated crossTenant) mirrors the
200
+ // crossTenantReason (escapeHatch) mirrors the
198
201
  // query-handler branch below: this ONE write handler reads/writes across
199
202
  // every tenant without making the whole feature r.systemScope().
200
- const crossTenantReason = resolveCrossTenantReason(name, options);
203
+ const crossTenantReason = resolveCrossTenantReason(name, options, "write");
201
204
  // create always writes into the acting user's own tenant, so its FK,
202
205
  // unique and preSave lookups must stay tenant-filtered even when the
203
206
  // handler declares cross-tenant access for the row-addressing verbs.
@@ -206,20 +209,31 @@ export function defineEntityWriteHandler(name, entity, options) {
206
209
  return ctx.systemDb.acknowledgeCrossTenant(`entity convention handler for r.systemScope() feature (${name})`);
207
210
  }
208
211
  return crossTenantReason !== undefined && verb !== "create"
209
- ? acknowledgeConventionCrossTenant(ctx.db, crossTenantReason)
212
+ ? acknowledgeConventionCrossTenant(ctx.db, crossTenantReason, {
213
+ deferReport: reportsPerTouchedRow(ctx),
214
+ })
210
215
  : ctx.db;
211
216
  };
217
+ // actingUserFor reads the row it is about to touch anyway, so for these calls it reports the
218
+ // cross-tenant use per row (with that row as target) instead of dbFor reporting once up front.
219
+ const reportsPerTouchedRow = (ctx) => ctx.systemDb === undefined && crossTenantReason !== undefined && !entity.systemStream;
212
220
  // The event stream is keyed by the acting user's tenantId (streamTenantFor in
213
221
  // event-store-executor-context.ts), not by the db handed to the executor — an
214
222
  // unfiltered "system"-mode db alone reads the foreign row but appends to an
215
223
  // empty stream in the operator's own tenant, surfacing as a bogus
216
224
  // version_conflict. Rewriting the acting tenant also keeps PII envelope keys
217
225
  // derived from the row's tenant rather than the operator's.
218
- const actingUserFor = async (user, id, db) => {
226
+ const actingUserFor = async (user, id, db, ctx) => {
219
227
  if (crossTenantReason === undefined || entity.systemStream)
220
228
  return user;
221
229
  const row = await db.fetchOne(table, { id });
222
230
  const rowTenantId = row?.["tenantId"];
231
+ if (reportsPerTouchedRow(ctx)) {
232
+ const targetTenantId = parseTenantId(rowTenantId);
233
+ reportConventionCrossTenantUse(db, targetTenantId !== null && typeof id === "string"
234
+ ? { id, tenantId: targetTenantId }
235
+ : undefined);
236
+ }
223
237
  if (typeof rowTenantId !== "string" || rowTenantId === user.tenantId)
224
238
  return user;
225
239
  return { ...user, tenantId: rowTenantId };
@@ -258,7 +272,7 @@ export function defineEntityWriteHandler(name, entity, options) {
258
272
  ...submitted,
259
273
  changes: dropEmptyWriteOnlyValues(entity, submitted.changes),
260
274
  };
261
- const user = await actingUserFor(event.user, payload.id, db);
275
+ const user = await actingUserFor(event.user, payload.id, db, ctx);
262
276
  // skipUnchanged (#464): API-driven updates diff against the stored
263
277
  // row so a resubmitted-but-identical field doesn't force a fresh
264
278
  // pii/encrypted ciphertext. Direct executor.update() callers (e.g.
@@ -277,7 +291,7 @@ export function defineEntityWriteHandler(name, entity, options) {
277
291
  handler = async (event, ctx) => {
278
292
  const db = dbFor(ctx);
279
293
  const payload = event.payload; // @cast-boundary engine-payload
280
- const user = await actingUserFor(event.user, payload.id, db);
294
+ const user = await actingUserFor(event.user, payload.id, db, ctx);
281
295
  return executor.delete(payload, user, db);
282
296
  };
283
297
  break;
@@ -286,15 +300,13 @@ export function defineEntityWriteHandler(name, entity, options) {
286
300
  handler = async (event, ctx) => {
287
301
  const db = dbFor(ctx);
288
302
  const payload = event.payload; // @cast-boundary engine-payload
289
- const user = await actingUserFor(event.user, payload.id, db);
303
+ const user = await actingUserFor(event.user, payload.id, db, ctx);
290
304
  return executor.restore(payload, user, db);
291
305
  };
292
306
  break;
293
307
  default:
294
308
  assertUnreachable(verb, "write verb");
295
309
  }
296
- if (options.crossTenant === true)
297
- deprecatedCrossTenantHandlers.add(handler);
298
310
  // escapeHatch stays off the def: WriteHandlerDef.escapeHatch also grants unsafeRaw, global writes, identity switches.
299
311
  return {
300
312
  name,
@@ -338,13 +350,13 @@ export function defineEntityQueryHandler(name, entity, options) {
338
350
  // useReferenceLookup stays as a fallback (for apps that write custom
339
351
  // handlers by hand without this wrapper).
340
352
  const hasRefFields = collectReferenceFields(entity).length > 0;
341
- const crossTenantReason = resolveCrossTenantReason(name, options);
353
+ const crossTenantReason = resolveCrossTenantReason(name, options, "query");
342
354
  // Preference order:
343
355
  // 1. ctx.systemDb — the feature declared r.systemScope() (whole-feature
344
356
  // cutover, dispatch-shared.ts), so ctx.db is fail-closed. This is
345
357
  // behavior-preserving: ctx.db was already an unfiltered "system"-mode
346
358
  // TenantDb for these handlers before that cutover.
347
- // 2. crossTenantReason (escapeHatch or the deprecated crossTenant) — this
359
+ // 2. crossTenantReason (escapeHatch) — this
348
360
  // ONE handler reads across every tenant (e.g. a SystemAdmin-only
349
361
  // operator inspector) without making the whole feature r.systemScope()
350
362
  // — that would drop tenant isolation from every OTHER handler the
@@ -389,8 +401,12 @@ export function defineEntityQueryHandler(name, entity, options) {
389
401
  },
390
402
  parentVisibility: { entities: ctx.registry.getAllEntities() },
391
403
  });
404
+ const eagerLoadViewer = {
405
+ user: query.user,
406
+ parentVisibility: { entities: ctx.registry.getAllEntities() },
407
+ };
392
408
  const enrichedRows = hasRefFields
393
- ? await enrichWithReferences(result.rows, entity, (name) => ctx.registry.getEntity(name), db)
409
+ ? await enrichWithReferences(result.rows, entity, (name) => ctx.registry.getEntity(name), db, eagerLoadViewer)
394
410
  : result.rows;
395
411
  return { ...result, rows: augmentDerivedFields(enrichedRows, entity) };
396
412
  };
@@ -404,14 +420,15 @@ export function defineEntityQueryHandler(name, entity, options) {
404
420
  }); // @cast-boundary engine-payload
405
421
  if (row === null || !hasRefFields)
406
422
  return row;
407
- return enrichRowWithReferences(row, entity, (name) => ctx.registry.getEntity(name), db);
423
+ return enrichRowWithReferences(row, entity, (name) => ctx.registry.getEntity(name), db, {
424
+ user: query.user,
425
+ parentVisibility: { entities: ctx.registry.getAllEntities() },
426
+ });
408
427
  };
409
428
  break;
410
429
  default:
411
430
  assertUnreachable(verb, "query verb");
412
431
  }
413
- if (options.crossTenant === true)
414
- deprecatedCrossTenantHandlers.add(handler);
415
432
  // escapeHatch stays off the def: QueryHandlerDef.escapeHatch also grants unsafeRaw and identity switches.
416
433
  return {
417
434
  name,
@@ -500,6 +517,10 @@ export function createEntityExecutor(entityName, entity) {
500
517
  // For anything more involved (filters, joins, custom shaping), write the
501
518
  // query-handler explicitly with ctx.queryProjection or a raw select.
502
519
  export function defineProjectionQueryHandler(name, projectionQualifiedName, options) {
520
+ const escapeHatch = options.escapeHatch ??
521
+ (typeof options.unsafeAllTenants === "object"
522
+ ? { reason: options.unsafeAllTenants.reason }
523
+ : undefined);
503
524
  return {
504
525
  name,
505
526
  schema: z.object({}),
@@ -509,7 +530,7 @@ export function defineProjectionQueryHandler(name, projectionQualifiedName, opti
509
530
  // pagination envelopes or added metadata.
510
531
  handler: async (_query, ctx) => ctx.queryProjection(projectionQualifiedName, options.unsafeAllTenants ? { unsafeAllTenants: true } : undefined), // @wrapper-known semantic-alias
511
532
  access: options.access,
512
- ...(options.escapeHatch && { escapeHatch: options.escapeHatch }),
533
+ ...(escapeHatch && { escapeHatch }),
513
534
  };
514
535
  }
515
536
  function defaultCrudVerbs(entity) {
@@ -0,0 +1,2 @@
1
+ export declare const CODEMOD_PLACEHOLDER_REASON_MARKER = "state the operator use case here";
2
+ export declare function codemodPlaceholderReasonProblem(reason: string): string | undefined;
@@ -0,0 +1,8 @@
1
+ // Shared by the migrate-cross-tenant codemod (which writes it) and the escapeHatch reason
2
+ // validation (which rejects it), so an unedited codemod reason can never reach a running app.
3
+ export const CODEMOD_PLACEHOLDER_REASON_MARKER = "state the operator use case here";
4
+ export function codemodPlaceholderReasonProblem(reason) {
5
+ if (!reason.includes(CODEMOD_PLACEHOLDER_REASON_MARKER))
6
+ return undefined;
7
+ return `the reason still contains the migrate-cross-tenant codemod placeholder ("${CODEMOD_PLACEHOLDER_REASON_MARKER}") — replace the codemod placeholder with the real operator use case`;
8
+ }
@@ -23,19 +23,18 @@ export declare const EXT_USER_DATA_ORDER: {
23
23
  readonly DEFAULT: 0;
24
24
  };
25
25
  /**
26
- * `tenantData` — Tenant-Destroy-Hooks pro Entity (DSGVO + AVV-Beendigung).
26
+ * `tenantData` — per-entity tenant-destroy hooks (GDPR + end of data processing agreement).
27
27
  *
28
- * Erwartete Hook-Methoden:
29
- * - `destroy(tenantId, ctx) => Promise<void>`
28
+ * Expected hook method: `destroy(ctx) => Promise<TenantDestroyHookResult | void>`.
30
29
  *
31
- * Registriert von: `tenant-lifecycle` (Sprint 5).
32
- * Genutzt von: jedes Feature mit tenantId-Field.
30
+ * Registered by: `tenant-lifecycle`.
31
+ * Consumed by: every feature with a tenantId field.
33
32
  */
34
33
  export declare const EXT_TENANT_DATA: "tenantData";
35
34
  /**
36
35
  * `storageProvider` — file-storage-plugin tenant-destroy hook.
37
36
  *
38
- * Expected hook method: `destroyTenant(tenantId, ctx) => Promise<void>`.
37
+ * Expected hook method: `destroyTenant(tenantId, ctx) => Promise<TenantDestroyHookResult | void>`.
39
38
  *
40
39
  * Registered by: `files-tenant-data`.
41
40
  * Consumed by: pluggable providers (Local, MinIO, S3, R2).
@@ -113,7 +112,7 @@ export declare const EXT_DERIVATIVE_OVERLAY_RESOLVER: "derivativeOverlayResolver
113
112
  * `searchAdapter` — search-index tenant-destroy hook (Meilisearch index
114
113
  * cleanup on tenant-destroy).
115
114
  *
116
- * Expected hook method: `destroyTenant(tenantId, ctx) => Promise<void>`.
115
+ * Expected hook method: `destroyTenant(tenantId, ctx) => Promise<TenantDestroyHookResult | void>`.
117
116
  *
118
117
  * Consumed by: Meilisearch and other search-adapter implementations.
119
118
  */
@@ -122,14 +121,14 @@ export declare const EXT_SEARCH_ADAPTER: "searchAdapter";
122
121
  * `externalResource` — external-service tenant-destroy hook (webhook
123
122
  * subscriptions, Brevo recipient lists, provider customer accounts).
124
123
  *
125
- * Expected hook method: `destroyTenant(tenantId, ctx) => Promise<void>`.
124
+ * Expected hook method: `destroyTenant(tenantId, ctx) => Promise<TenantDestroyHookResult | void>`.
126
125
  */
127
126
  export declare const EXT_EXTERNAL_RESOURCE: "externalResource";
128
127
  /**
129
128
  * `infraResource` — Pulumi-managed per-tenant resource tenant-destroy hook
130
129
  * (custom domain, cert-manager issuer, dedicated pod/volume).
131
130
  *
132
- * Expected hook method: `destroyTenant(tenantId, ctx) => Promise<void>`.
131
+ * Expected hook method: `destroyTenant(tenantId, ctx) => Promise<TenantDestroyHookResult | void>`.
133
132
  */
134
133
  export declare const EXT_INFRA_RESOURCE: "infraResource";
135
134
  /**
@@ -42,19 +42,18 @@ export const EXT_USER_DATA_ORDER = {
42
42
  DEFAULT: 0,
43
43
  };
44
44
  /**
45
- * `tenantData` — Tenant-Destroy-Hooks pro Entity (DSGVO + AVV-Beendigung).
45
+ * `tenantData` — per-entity tenant-destroy hooks (GDPR + end of data processing agreement).
46
46
  *
47
- * Erwartete Hook-Methoden:
48
- * - `destroy(tenantId, ctx) => Promise<void>`
47
+ * Expected hook method: `destroy(ctx) => Promise<TenantDestroyHookResult | void>`.
49
48
  *
50
- * Registriert von: `tenant-lifecycle` (Sprint 5).
51
- * Genutzt von: jedes Feature mit tenantId-Field.
49
+ * Registered by: `tenant-lifecycle`.
50
+ * Consumed by: every feature with a tenantId field.
52
51
  */
53
52
  export const EXT_TENANT_DATA = "tenantData";
54
53
  /**
55
54
  * `storageProvider` — file-storage-plugin tenant-destroy hook.
56
55
  *
57
- * Expected hook method: `destroyTenant(tenantId, ctx) => Promise<void>`.
56
+ * Expected hook method: `destroyTenant(tenantId, ctx) => Promise<TenantDestroyHookResult | void>`.
58
57
  *
59
58
  * Registered by: `files-tenant-data`.
60
59
  * Consumed by: pluggable providers (Local, MinIO, S3, R2).
@@ -142,7 +141,7 @@ export const EXT_DERIVATIVE_OVERLAY_RESOLVER = "derivativeOverlayResolver";
142
141
  * `searchAdapter` — search-index tenant-destroy hook (Meilisearch index
143
142
  * cleanup on tenant-destroy).
144
143
  *
145
- * Expected hook method: `destroyTenant(tenantId, ctx) => Promise<void>`.
144
+ * Expected hook method: `destroyTenant(tenantId, ctx) => Promise<TenantDestroyHookResult | void>`.
146
145
  *
147
146
  * Consumed by: Meilisearch and other search-adapter implementations.
148
147
  */
@@ -151,14 +150,14 @@ export const EXT_SEARCH_ADAPTER = "searchAdapter";
151
150
  * `externalResource` — external-service tenant-destroy hook (webhook
152
151
  * subscriptions, Brevo recipient lists, provider customer accounts).
153
152
  *
154
- * Expected hook method: `destroyTenant(tenantId, ctx) => Promise<void>`.
153
+ * Expected hook method: `destroyTenant(tenantId, ctx) => Promise<TenantDestroyHookResult | void>`.
155
154
  */
156
155
  export const EXT_EXTERNAL_RESOURCE = "externalResource";
157
156
  /**
158
157
  * `infraResource` — Pulumi-managed per-tenant resource tenant-destroy hook
159
158
  * (custom domain, cert-manager issuer, dedicated pod/volume).
160
159
  *
161
- * Expected hook method: `destroyTenant(tenantId, ctx) => Promise<void>`.
160
+ * Expected hook method: `destroyTenant(tenantId, ctx) => Promise<TenantDestroyHookResult | void>`.
162
161
  */
163
162
  export const EXT_INFRA_RESOURCE = "infraResource";
164
163
  /**
@@ -1,14 +1,16 @@
1
1
  import type { FileProviderResolver } from "@cosmicdrift/kumiko-types/file-provider-resolver-types";
2
2
  import type { TenantDb } from "../../db/tenant-db.js";
3
3
  import type { Registry, TenantId } from "../types/index.js";
4
+ import type { TenantDestroyHookResult } from "./tenant-resource.js";
4
5
  export interface TenantDataHookCtx {
5
6
  readonly db: TenantDb;
6
7
  readonly registry: Registry;
7
8
  readonly tenantId: TenantId;
9
+ readonly deadlineAt?: number;
8
10
  readonly fileProviderResolver?: FileProviderResolver;
9
11
  readonly log?: (message: string) => void;
10
12
  }
11
- export type TenantDataDestroyHook = (ctx: TenantDataHookCtx) => Promise<void>;
13
+ export type TenantDataDestroyHook = (ctx: TenantDataHookCtx) => Promise<TenantDestroyHookResult | void>;
12
14
  export interface TenantDataExtensionHooks {
13
15
  readonly destroy: TenantDataDestroyHook;
14
16
  }
@@ -1,13 +1,21 @@
1
1
  import type { FileProviderResolver } from "@cosmicdrift/kumiko-types/file-provider-resolver-types";
2
2
  import type { DbRunner } from "../../db/connection.js";
3
3
  import type { TenantId } from "../types/index.js";
4
+ export type TenantDestroyHookResult = {
5
+ readonly done: true;
6
+ readonly processed?: number;
7
+ } | {
8
+ readonly done: false;
9
+ readonly processed: number;
10
+ };
4
11
  export interface TenantResourceHookCtx {
5
12
  readonly tenantId: TenantId;
13
+ readonly deadlineAt?: number;
6
14
  readonly db: DbRunner;
7
15
  readonly fileProviderResolver?: FileProviderResolver;
8
16
  readonly log?: (message: string) => void;
9
17
  }
10
- export type TenantResourceDestroyHook = (tenantId: TenantId, ctx: TenantResourceHookCtx) => Promise<void>;
18
+ export type TenantResourceDestroyHook = (tenantId: TenantId, ctx: TenantResourceHookCtx) => Promise<TenantDestroyHookResult | void>;
11
19
  export interface TenantResourceExtensionHooks {
12
20
  readonly destroyTenant: TenantResourceDestroyHook;
13
21
  }
@@ -35,7 +35,7 @@ export declare function createBooleanField<R extends true | false = false>(overr
35
35
  };
36
36
  export declare function createSelectField<const TOptions extends readonly string[], R extends true | false = false>(opts: {
37
37
  options: TOptions;
38
- } & Partial<Omit<SelectFieldDef<TOptions>, "type" | "options" | "required" | keyof ResolvedPiiFlags>> & PersonalAnnotationsNoFind & {
38
+ } & Partial<Omit<SelectFieldDef<NoInfer<TOptions>>, "type" | "options" | "required" | keyof ResolvedPiiFlags>> & PersonalAnnotationsNoFind & {
39
39
  required?: R;
40
40
  }): SelectFieldDef<TOptions> & {
41
41
  required: R;
@@ -8,6 +8,8 @@ export type ChangelogEntry = {
8
8
  readonly migration?: string;
9
9
  /** Path (relative to packages/framework/src/, under scripts/codemod/) run by `kumiko upgrade --apply`. */
10
10
  readonly codemod?: string;
11
+ /** The codemod only covers part of the migration: the entry is also recorded as pending manual work after it ran. */
12
+ readonly manualAfterCodemod?: boolean;
11
13
  };
12
14
  export type FeatureChangelog = {
13
15
  readonly feature: string;
@@ -33,6 +33,9 @@ function isChangelogEntry(value) {
33
33
  return false;
34
34
  if (typeof obj["title"] !== "string")
35
35
  return false;
36
+ if (obj["manualAfterCodemod"] !== undefined && typeof obj["manualAfterCodemod"] !== "boolean") {
37
+ return false;
38
+ }
36
39
  return true;
37
40
  }
38
41
  export function validateChangelog(entry) {
@@ -43,6 +46,9 @@ export function validateChangelog(entry) {
43
46
  if (entry.type === "breaking" && entry.migration?.trim() === "") {
44
47
  errors.push(`breaking change "${entry.title}" has empty migration field`);
45
48
  }
49
+ if (entry.manualAfterCodemod && !entry.codemod) {
50
+ errors.push(`change "${entry.title}" sets manualAfterCodemod without a codemod field`);
51
+ }
46
52
  return errors;
47
53
  }
48
54
  export function compareVersions(a, b) {
@@ -13,6 +13,39 @@ function isAbsentable(field) {
13
13
  return parsed.success && (parsed.data === null || parsed.data === undefined);
14
14
  });
15
15
  }
16
+ const NON_STRING_SCHEMA_TYPES = new Set([
17
+ "number",
18
+ "int",
19
+ "boolean",
20
+ "bigint",
21
+ "date",
22
+ "object",
23
+ "array",
24
+ ]);
25
+ // An owner id is resolved through "non-empty string or absent" at append time,
26
+ // so a schema that can never yield a string would make every append throw in
27
+ // prod. Wrappers (optional/nullable/default/...) are looked through.
28
+ function neverYieldsString(field) {
29
+ let current = field;
30
+ for (;;) {
31
+ const def = current._zod.def;
32
+ if (def.innerType === undefined)
33
+ return NON_STRING_SCHEMA_TYPES.has(def.type);
34
+ current = def.innerType;
35
+ }
36
+ }
37
+ function assertOwnerFieldYieldsPresentString(args) {
38
+ const { featureName, eventName, field, ownerField, owner } = args;
39
+ if (neverYieldsString(owner)) {
40
+ throw new Error(`[Feature ${featureName}] defineEvent("${eventName}"): piiFields."${field}" is owned by "${ownerField}", whose payload schema is not a string — the subject id must be a non-empty string, so every append would fail. ` +
41
+ `Use a string owner field, or declare whenAbsent ("tenant" | "plaintext").`);
42
+ }
43
+ if (isAbsentable(owner)) {
44
+ throw new Error(`[Feature ${featureName}] defineEvent("${eventName}"): piiFields."${field}" is owned by "${ownerField}", which the payload schema allows to be null/undefined. ` +
45
+ `Declare what happens then: { personal: { of: "${ownerField}", whenAbsent: "tenant" } } encrypts under the envelope tenant key, ` +
46
+ `whenAbsent: "plaintext" acknowledges that the value ships unencrypted and cannot be crypto-shredded (fw#2776).`);
47
+ }
48
+ }
16
49
  // Builds config/secrets/claims/events/jobs/notifications registrar methods.
17
50
  export function buildConfigEventsJobsMethods(state, name) {
18
51
  function config(arg1, arg2) {
@@ -82,10 +115,14 @@ export function buildConfigEventsJobsMethods(state, name) {
82
115
  }
83
116
  }
84
117
  const owner = shape?.[normalized.ownerField];
85
- if (normalized.whenAbsent === undefined && owner !== undefined && isAbsentable(owner)) {
86
- throw new Error(`[Feature ${name}] defineEvent("${eventName}"): piiFields."${field}" is owned by "${normalized.ownerField}", which the payload schema allows to be null/undefined. ` +
87
- `Declare what happens then: { personal: { of: "${normalized.ownerField}", whenAbsent: "tenant" } } encrypts under the envelope tenant key, ` +
88
- `whenAbsent: "plaintext" acknowledges that the value ships unencrypted and cannot be crypto-shredded (fw#2776).`);
118
+ if (normalized.whenAbsent === undefined && owner !== undefined) {
119
+ assertOwnerFieldYieldsPresentString({
120
+ featureName: name,
121
+ eventName,
122
+ field,
123
+ ownerField: normalized.ownerField,
124
+ owner,
125
+ });
89
126
  }
90
127
  }
91
128
  }
@@ -0,0 +1,3 @@
1
+ import { type ZodType } from "zod";
2
+ export declare const HANDLER_FIELD_REFERENCES_META_KEY = "references";
3
+ export declare function handlerFieldReference(schema: ZodType, field: string): string | undefined;
@@ -0,0 +1,31 @@
1
+ import { ZodObject } from "zod";
2
+ import { zodDef, zodMeta, zodShapeField } from "../env/_zod-introspect.js";
3
+ export const HANDLER_FIELD_REFERENCES_META_KEY = "references";
4
+ const MAX_WRAPPER_DEPTH = 8;
5
+ function referencesFromMeta(field) {
6
+ const meta = zodMeta(field);
7
+ if (typeof meta !== "object" || meta === null)
8
+ return undefined;
9
+ const value = Reflect.get(meta, HANDLER_FIELD_REFERENCES_META_KEY);
10
+ return typeof value === "string" ? value : undefined;
11
+ }
12
+ // `.optional()` / `.nullable()` / `.default()` after `.meta()` hide the meta
13
+ // from the outer type, so every wrapper layer is probed on the way in.
14
+ export function handlerFieldReference(schema, field) {
15
+ let current = schema;
16
+ for (let depth = 0; depth < MAX_WRAPPER_DEPTH && current !== undefined; depth++) {
17
+ if (current instanceof ZodObject)
18
+ break;
19
+ current = zodDef(current)?.innerType;
20
+ }
21
+ if (!(current instanceof ZodObject))
22
+ return undefined;
23
+ let fieldSchema = zodShapeField(current, field);
24
+ for (let depth = 0; depth < MAX_WRAPPER_DEPTH && fieldSchema !== undefined; depth++) {
25
+ const reference = referencesFromMeta(fieldSchema);
26
+ if (reference !== undefined)
27
+ return reference;
28
+ fieldSchema = zodDef(fieldSchema)?.innerType;
29
+ }
30
+ return undefined;
31
+ }
@@ -1,8 +1,7 @@
1
1
  // Dot-form labels are indistinguishable from literal display text ("actions.open"),
2
2
  // so authors mark the i18n ones explicitly; the registry is what boot validation reads.
3
- // Marking is by string value and therefore global across features. The registry sits on
4
- // globalThis so a second installed copy of the framework still reads the marks the
5
- // other copy wrote; otherwise they are silently ignored and #2313 reopens.
3
+ // A mark applies process-wide, by string value, across all features and framework copies.
4
+ // The registry sits on globalThis so a second installed copy reads the other's marks.
6
5
  const REGISTRY_KEY = Symbol.for("kumiko.i18n.explicitDotFormKeys");
7
6
  function explicitDotFormKeys() {
8
7
  const existing = Reflect.get(globalThis, REGISTRY_KEY);
@@ -1,3 +1,4 @@
1
+ export { handlerFieldLabelKey, handlerTitleKey } from "../i18n/required-surface-keys.js";
1
2
  export { hasAccess } from "./access.js";
2
3
  export { isPrincipalStatusPlugin, isTenantLifecycleStatusPlugin, type PrincipalProfile, type PrincipalStatus, type PrincipalStatusPlugin, TENANT_TEARDOWN_STATUSES, type TenantLifecycleStatusPlugin, } from "./active-membership.js";
3
4
  export { PII_DIRECT_NAME_HINTS, PII_USER_OWNED_NAME_HINTS, PII_USER_REFERENCE_NAME_HINTS, } from "./boot-validator/entity-handler.js";
@@ -34,7 +35,7 @@ export { EXTENSION_SELECTOR_HINT_KEY, extensionSelectorTargets, SELECTED_EXTENSI
34
35
  export { extensionUsageEscapeHatchReason } from "./extensions/escape-hatch-usage.js";
35
36
  export type { StorageProviderDestroyTenantHook, StorageProviderExtensionHooks, StorageProviderHookCtx, } from "./extensions/storage-provider.js";
36
37
  export { isTenantDataExtensionHooks, type TenantDataDestroyHook, type TenantDataExtensionHooks, type TenantDataHookCtx, } from "./extensions/tenant-data.js";
37
- export { isTenantResourceExtensionHooks, type TenantResourceDestroyHook, type TenantResourceExtensionHooks, type TenantResourceHookCtx, } from "./extensions/tenant-resource.js";
38
+ export { isTenantResourceExtensionHooks, type TenantDestroyHookResult, type TenantResourceDestroyHook, type TenantResourceExtensionHooks, type TenantResourceHookCtx, } from "./extensions/tenant-resource.js";
38
39
  export type { TenantUserModel, UserDataDeleteHook, UserDataDeleteStrategy, UserDataExportHook, UserDataExportSnippet, UserDataExtensionHooks, UserDataExtensionOptions, UserDataHookCtx, UserDataStorageProvider, } from "./extensions/user-data.js";
39
40
  export { createBigIntField, createBooleanField, createDateField, createDecimalField, createDerivedField, createEmbeddedField, createEmbeddedListField, createEntity, createFileField, createFilesField, createImageField, createImagesField, createJsonbField, createLocatedTimestampField, createLongTextField, createMoneyField, createMultiSelectField, createNumberField, createSelectField, createTextField, createTimestampField, createTzField, } from "./factories.js";
40
41
  export type { AddEntityArgs, AddHookArgs, AddRelationArgs, AddWriteHandlerArgs, FeaturePatcher, FeaturePattern, FeaturePatternKind, FormFieldLabel, FormFieldSpec, FormInputType, HandlerHeaderUpdate, ParseError, ParseResult, PatternCategory, PatternChange, PatternChangeIssue, PatternChangesParseResult, PatternFormSchema, PatternId, RenderFeatureFileInput, SourceLocation, } from "./feature-ast/index.js";
@@ -42,6 +43,7 @@ export { addPattern, applyChanges, createFeaturePatcher, getPatternSchema, group
42
43
  export { type ChangelogEntry, type ChangelogType, compareVersions, type FeatureChangelog, filterEntriesAfter, parseFeatureChangelog, sortEntries, validateChangelog, } from "./feature-changelog.js";
43
44
  export { type BuildManifestOptions, buildManifestFromRegistry, type FeatureManifest, type ManifestConfigKey, type ManifestExtension, type ManifestFeature, type ManifestSecret, type ManifestSystemEvent, serializeManifest, } from "./feature-manifest.js";
44
45
  export { checkWriteFieldOwnership, checkWriteFieldRoles, filterReadFields, maskWriteOnlyFields, } from "./field-access.js";
46
+ export { HANDLER_FIELD_REFERENCES_META_KEY, handlerFieldReference, } from "./handler-field-references.js";
45
47
  export { resolveName, withResponseData } from "./handler-helpers.js";
46
48
  export { i18nKey } from "./i18n-key.js";
47
49
  export { buildSessionRoles, findForbiddenMembershipRole, isForbiddenMembershipRole, stripForbiddenMembershipRoles, } from "./membership-roles.js";
@@ -56,6 +58,7 @@ export type { BuiltinQnType, ParsedQn, QnType } from "./qualified-name.js";
56
58
  export { isValidQn, parseQn, QnTypes, qn, toKebab } from "./qualified-name.js";
57
59
  export { readClaim } from "./read-claim.js";
58
60
  export { createRegistry } from "./registry.js";
61
+ export { qualifyTranslationKey } from "./registry-ingest.js";
59
62
  export type { ClampInfo, ResolveOptions } from "./resolve-config-or-param.js";
60
63
  export { resolveConfigOrParam } from "./resolve-config-or-param.js";
61
64
  export type { AssignableAppRoles, AssignableFromRole } from "./role-assignment.js";
@@ -64,15 +67,18 @@ export { runsInLane } from "./run-in.js";
64
67
  export type { StepListOutcome } from "./run-pipeline.js";
65
68
  export { runPipeline, runStepList } from "./run-pipeline.js";
66
69
  export { buildInsertSchema, buildUpdateSchema, fieldToZod, SYSTEM_ONLY_JSON_SCHEMA_KEY, } from "./schema-builder.js";
67
- export { isExtensionEditSection, isFieldsEditSection, isWriteFormEditSection, normalizeEditField, normalizeListColumn, sectionFieldSpecs, } from "./screen-helpers.js";
70
+ export type { UnavailableSelectOption } from "./screen-helpers.js";
71
+ export { availableSelectOptions, findUnavailableSelectOptions, isExtensionEditSection, isFieldsEditSection, isWriteFormEditSection, normalizeEditField, normalizeListColumn, sectionFieldSpecs, } from "./screen-helpers.js";
68
72
  export type { TransitionGraph } from "./state-machine.js";
69
73
  export { defineTransitions, guardTransition } from "./state-machine.js";
70
74
  export { evaluateEventMatch } from "./steps/_event-match.js";
71
75
  export { STEP_DISPATCH_AGGREGATE_TYPE, STEP_DISPATCH_FAILED_TYPE, STEP_DISPATCH_REQUESTED_TYPE, STEP_DISPATCHED_TYPE, SUSPEND_SENTINEL, WORKFLOW_AGGREGATE_TYPE, WORKFLOW_RESUMED_TYPE, WORKFLOW_RETRY_SCHEDULED_TYPE, WORKFLOW_RUN_COMPLETED_TYPE, WORKFLOW_RUN_FAILED_TYPE, WORKFLOW_RUN_STARTED_TYPE, WORKFLOW_WAITING_FOR_EVENT_TYPE, WORKFLOW_WAITING_TYPE, } from "./steps/_step-dispatch-constants.js";
72
76
  export { describeWorkflowStepError } from "./steps/describe-workflow-step-error.js";
77
+ export { unwrapWorkflowStepError, WorkflowStepError } from "./steps/workflow-step-error.js";
73
78
  export { WORKFLOW_SYSTEM_EVENT_DEFS } from "./steps/workflow-system-events.js";
74
79
  export { ANONYMOUS_ROLE, ANONYMOUS_USER_ID, createAnonymousUser, createSystemUser, SYSTEM_ROLE, SYSTEM_USER_ID, } from "./system-user.js";
75
80
  export { type EffectiveFeaturesResolver, findTierResolverUsage, isTierResolverPlugin, TENANT_TIER_RESOLVER_EXT, type TierResolverPlugin, type TrialGate, } from "./tier-resolver-extension.js";
81
+ export { findOverDeepTransferChain, resolveTransferAdjacency as resolveEntityTransferAdjacency, type TransferAdjacency, type TransferEdge, type TransferEdgeLink, } from "./transfer-adjacency.js";
76
82
  export { isSystemTenant, isUuid, parseTenantId, SYSTEM_TENANT_ID } from "./types/identifiers.js";
77
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";
78
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";
@@ -1,4 +1,5 @@
1
1
  // Public API
2
+ export { handlerFieldLabelKey, handlerTitleKey } from "../i18n/required-surface-keys.js";
2
3
  export { hasAccess } from "./access.js";
3
4
  export { isPrincipalStatusPlugin, isTenantLifecycleStatusPlugin, TENANT_TEARDOWN_STATUSES, } from "./active-membership.js";
4
5
  export { PII_DIRECT_NAME_HINTS, PII_USER_OWNED_NAME_HINTS, PII_USER_REFERENCE_NAME_HINTS, } from "./boot-validator/entity-handler.js";
@@ -37,6 +38,7 @@ export { addPattern, applyChanges, createFeaturePatcher, getPatternSchema, group
37
38
  export { compareVersions, filterEntriesAfter, parseFeatureChangelog, sortEntries, validateChangelog, } from "./feature-changelog.js";
38
39
  export { buildManifestFromRegistry, serializeManifest, } from "./feature-manifest.js";
39
40
  export { checkWriteFieldOwnership, checkWriteFieldRoles, filterReadFields, maskWriteOnlyFields, } from "./field-access.js";
41
+ export { HANDLER_FIELD_REFERENCES_META_KEY, handlerFieldReference, } from "./handler-field-references.js";
40
42
  export { resolveName, withResponseData } from "./handler-helpers.js";
41
43
  export { i18nKey } from "./i18n-key.js";
42
44
  // findForbiddenMembershipRole/isForbiddenMembershipRole/
@@ -54,19 +56,22 @@ export { defineApply, defineMspApply, setFields } from "./projection-helpers.js"
54
56
  export { isValidQn, parseQn, QnTypes, qn, toKebab } from "./qualified-name.js";
55
57
  export { readClaim } from "./read-claim.js";
56
58
  export { createRegistry } from "./registry.js";
59
+ export { qualifyTranslationKey } from "./registry-ingest.js";
57
60
  export { resolveConfigOrParam } from "./resolve-config-or-param.js";
58
61
  export { assignableAppRolesFromUsages, assignableAppRolesOf, BUILT_IN_MEMBERSHIP_ROLES, canActorAssignRole, findForbiddenRoleAssignment, isAssignableByRole, mergeAssignedRoles, } from "./role-assignment.js";
59
62
  export { runsInLane } from "./run-in.js";
60
63
  export { runPipeline, runStepList } from "./run-pipeline.js";
61
64
  export { buildInsertSchema, buildUpdateSchema, fieldToZod, SYSTEM_ONLY_JSON_SCHEMA_KEY, } from "./schema-builder.js";
62
- export { isExtensionEditSection, isFieldsEditSection, isWriteFormEditSection, normalizeEditField, normalizeListColumn, sectionFieldSpecs, } from "./screen-helpers.js";
65
+ export { availableSelectOptions, findUnavailableSelectOptions, isExtensionEditSection, isFieldsEditSection, isWriteFormEditSection, normalizeEditField, normalizeListColumn, sectionFieldSpecs, } from "./screen-helpers.js";
63
66
  export { defineTransitions, guardTransition } from "./state-machine.js";
64
67
  export { evaluateEventMatch } from "./steps/_event-match.js";
65
68
  export { STEP_DISPATCH_AGGREGATE_TYPE, STEP_DISPATCH_FAILED_TYPE, STEP_DISPATCH_REQUESTED_TYPE, STEP_DISPATCHED_TYPE, SUSPEND_SENTINEL, WORKFLOW_AGGREGATE_TYPE, WORKFLOW_RESUMED_TYPE, WORKFLOW_RETRY_SCHEDULED_TYPE, WORKFLOW_RUN_COMPLETED_TYPE, WORKFLOW_RUN_FAILED_TYPE, WORKFLOW_RUN_STARTED_TYPE, WORKFLOW_WAITING_FOR_EVENT_TYPE, WORKFLOW_WAITING_TYPE, } from "./steps/_step-dispatch-constants.js";
66
69
  export { describeWorkflowStepError } from "./steps/describe-workflow-step-error.js";
70
+ export { unwrapWorkflowStepError, WorkflowStepError } from "./steps/workflow-step-error.js";
67
71
  export { WORKFLOW_SYSTEM_EVENT_DEFS } from "./steps/workflow-system-events.js";
68
72
  export { ANONYMOUS_ROLE, ANONYMOUS_USER_ID, createAnonymousUser, createSystemUser, SYSTEM_ROLE, SYSTEM_USER_ID, } from "./system-user.js";
69
73
  export { findTierResolverUsage, isTierResolverPlugin, TENANT_TIER_RESOLVER_EXT, } from "./tier-resolver-extension.js";
74
+ export { findOverDeepTransferChain, resolveTransferAdjacency as resolveEntityTransferAdjacency, } from "./transfer-adjacency.js";
70
75
  export { isSystemTenant, isUuid, parseTenantId, SYSTEM_TENANT_ID } from "./types/identifiers.js";
71
76
  export { DEFAULT_CURRENCIES, DEFAULT_LOCALES, DELIVERY_FAILURE_CODES, DELIVERY_SKIP_REASONS, HookPhases, HTTP_ERROR_CODE_PATTERN, isAgentVisibleScreen, isDeliveryErrorCode, isOpenToAllGranted, resolveAgentExposure, } from "./types/index.js";
72
77
  export { runValidation } from "./validation.js";