@cosmicdrift/kumiko-bundled-features 0.352.0 → 0.354.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (259) hide show
  1. package/dist/agent-tools/agent-doc-lint.d.ts +2 -0
  2. package/dist/agent-tools/agent-doc-lint.js +61 -1
  3. package/dist/agent-tools/agent-manifest.d.ts +1 -0
  4. package/dist/agent-tools/agent-manifest.js +17 -5
  5. package/dist/agent-tools/feature.js +7 -2
  6. package/dist/agent-tools/index.d.ts +1 -1
  7. package/dist/agent-tools/index.js +1 -1
  8. package/dist/agent-tools/manifest-deny-qns.d.ts +3 -0
  9. package/dist/agent-tools/manifest-deny-qns.js +17 -0
  10. package/dist/agent-tools/tool-catalog.d.ts +2 -1
  11. package/dist/agent-tools/tool-catalog.js +4 -1
  12. package/dist/agent-tools/tool-dispatch.js +1 -1
  13. package/dist/audit/handlers/list.query.js +13 -8
  14. package/dist/auth-email-password/handlers/change-password.write.js +1 -0
  15. package/dist/auth-email-password/handlers/invite-accept-with-login.write.js +2 -2
  16. package/dist/auth-email-password/handlers/invite-accept.write.js +2 -2
  17. package/dist/auth-email-password/handlers/invite-create.write.js +17 -0
  18. package/dist/auth-email-password/handlers/invite-signup-complete.write.js +4 -1
  19. package/dist/auth-email-password/handlers/login.write.js +1 -0
  20. package/dist/auth-email-password/handlers/reset-password.write.js +1 -0
  21. package/dist/auth-email-password/handlers/signup-confirm.write.js +6 -4
  22. package/dist/auth-email-password/handlers/signup-request.write.js +51 -77
  23. package/dist/auth-email-password/handlers/token-request-handler.js +1 -0
  24. package/dist/auth-email-password/handlers/verify-email.write.js +1 -0
  25. package/dist/auth-email-password/index.d.ts +2 -0
  26. package/dist/auth-email-password/index.js +2 -0
  27. package/dist/auth-email-password/seeding.d.ts +2 -0
  28. package/dist/auth-email-password/seeding.js +30 -8
  29. package/dist/auth-email-password/signup-activation.d.ts +24 -0
  30. package/dist/auth-email-password/signup-activation.js +41 -0
  31. package/dist/auth-email-password/signup-token-store.d.ts +5 -0
  32. package/dist/auth-email-password/signup-token-store.js +8 -3
  33. package/dist/auth-email-password/web/auth-gate.d.ts +2 -2
  34. package/dist/auth-email-password/web/auth-gate.js +5 -5
  35. package/dist/auth-email-password/web/auth-redirect.d.ts +1 -0
  36. package/dist/auth-email-password/web/auth-redirect.js +14 -0
  37. package/dist/auth-email-password/web/login-screen.js +2 -2
  38. package/dist/auth-email-password/web/user-menu.js +1 -1
  39. package/dist/auth-mfa/feature.js +5 -0
  40. package/dist/auth-mfa/handlers/enable-confirm-preauth.write.js +1 -0
  41. package/dist/auth-mfa/handlers/verify.write.js +1 -0
  42. package/dist/auth-mfa/schema/user-mfa.d.ts +6 -0
  43. package/dist/auth-mfa/screens.js +2 -2
  44. package/dist/auth-mfa/web/mfa-recovery-codes-reveal.js +1 -1
  45. package/dist/auth-mfa/web/mfa-setup-preauth-screen.d.ts +1 -1
  46. package/dist/auth-mfa/web/mfa-setup-preauth-screen.js +3 -3
  47. package/dist/auth-mfa/web/mfa-verify-screen.d.ts +1 -1
  48. package/dist/auth-mfa/web/mfa-verify-screen.js +1 -1
  49. package/dist/billing-foundation/billing-info-query.d.ts +5 -2
  50. package/dist/billing-foundation/checkout-core.d.ts +7 -0
  51. package/dist/billing-foundation/checkout-core.js +7 -2
  52. package/dist/billing-foundation/entities.d.ts +8 -0
  53. package/dist/billing-foundation/handlers/start-plan-checkout.write.js +7 -1
  54. package/dist/billing-foundation/projection.d.ts +8 -0
  55. package/dist/cap-counter/entity.d.ts +1 -0
  56. package/dist/cap-overview/access.js +2 -9
  57. package/dist/cap-overview/handlers/tenant-caps-list.query.js +2 -2
  58. package/dist/cap-overview/handlers/tenant-options.query.js +4 -5
  59. package/dist/channel-in-app/in-app-channel.js +1 -1
  60. package/dist/channel-in-app/tables.js +4 -2
  61. package/dist/compliance-profiles/schema/profile-selection.d.ts +10 -8
  62. package/dist/compliance-profiles-ops/handlers/tenants-missing-profile.query.js +2 -4
  63. package/dist/config/handlers/values.query.d.ts +3 -1
  64. package/dist/config/handlers/values.query.js +7 -2
  65. package/dist/config/table.d.ts +6 -0
  66. package/dist/crypto-shredding/handlers/forget-subject.write.d.ts +1 -0
  67. package/dist/crypto-shredding/handlers/forget-subject.write.js +110 -61
  68. package/dist/custom-fields/entity.d.ts +4 -0
  69. package/dist/data-retention/schema/tenant-retention-override.d.ts +4 -0
  70. package/dist/delivery/db/queries/address-opt-outs.d.ts +3 -1
  71. package/dist/delivery/db/queries/address-opt-outs.js +12 -7
  72. package/dist/delivery/delivery-service.js +60 -12
  73. package/dist/delivery/public-names.js +1 -1
  74. package/dist/delivery/tables.d.ts +6 -0
  75. package/dist/delivery/unsubscribe.js +66 -14
  76. package/dist/document-ingest-foundation/entity.d.ts +4 -0
  77. package/dist/feature-toggles/feature.js +2 -3
  78. package/dist/feature-toggles/toggle-runtime.d.ts +5 -1
  79. package/dist/feature-toggles/toggle-runtime.js +30 -6
  80. package/dist/feature-toggles/toggle-sync-signal.d.ts +3 -2
  81. package/dist/feature-toggles/toggle-sync-signal.js +29 -10
  82. package/dist/file-derivatives/handlers/public-variant.query.d.ts +1 -0
  83. package/dist/file-derivatives/handlers/public-variant.query.js +7 -1
  84. package/dist/files-tenant-data/handlers/sweep-orphaned-derivatives.job.js +5 -0
  85. package/dist/files-tenant-data/hooks.js +61 -24
  86. package/dist/folders/entity.d.ts +8 -0
  87. package/dist/folders/feature.js +2 -10
  88. package/dist/folders/handlers/delete-folder.write.js +31 -4
  89. package/dist/form-draft/db/queries/cleanup.d.ts +1 -1
  90. package/dist/form-draft/db/queries/cleanup.js +4 -3
  91. package/dist/form-draft/entity.d.ts +2 -0
  92. package/dist/form-draft/handlers/cleanup.job.d.ts +5 -1
  93. package/dist/form-draft/handlers/cleanup.job.js +11 -5
  94. package/dist/foundation-shared/index.d.ts +1 -1
  95. package/dist/foundation-shared/index.js +1 -1
  96. package/dist/foundation-shared/mail-host-policy.d.ts +4 -0
  97. package/dist/foundation-shared/mail-host-policy.js +13 -4
  98. package/dist/inbound-mail-foundation/entities.d.ts +25 -0
  99. package/dist/inbound-mail-foundation/projection.d.ts +20 -0
  100. package/dist/inbound-provider-imap/feature.js +2 -0
  101. package/dist/inbound-provider-imap/imap-client.js +3 -4
  102. package/dist/jobs/constants.d.ts +1 -0
  103. package/dist/jobs/constants.js +1 -0
  104. package/dist/jobs/db/queries/retention.d.ts +1 -0
  105. package/dist/jobs/db/queries/retention.js +5 -0
  106. package/dist/jobs/db/queries/stale-run-sweep.d.ts +2 -0
  107. package/dist/jobs/db/queries/stale-run-sweep.js +13 -2
  108. package/dist/jobs/feature.js +7 -1
  109. package/dist/jobs/handlers/list.query.d.ts +2 -1
  110. package/dist/jobs/handlers/list.query.js +40 -15
  111. package/dist/jobs/handlers/tenant-failures.query.js +1 -1
  112. package/dist/jobs/handlers/tenant-runs.query.d.ts +17 -0
  113. package/dist/jobs/handlers/tenant-runs.query.js +72 -0
  114. package/dist/jobs/i18n.js +1 -0
  115. package/dist/jobs/index.d.ts +2 -0
  116. package/dist/jobs/index.js +1 -0
  117. package/dist/jobs/job-run-logger.d.ts +2 -2
  118. package/dist/jobs/job-run-logger.js +98 -9
  119. package/dist/jobs/job-run-table.d.ts +6 -0
  120. package/dist/jobs/tenant-job-failure-table.js +15 -2
  121. package/dist/jobs/tenant-job-run-table.d.ts +4 -0
  122. package/dist/jobs/tenant-job-run-table.js +34 -0
  123. package/dist/ledger/entity.d.ts +24 -8
  124. package/dist/ledger/entity.js +17 -5
  125. package/dist/ledger/feature.js +3 -0
  126. package/dist/ledger/handlers/confirm-schedule-period.write.js +9 -3
  127. package/dist/ledger/handlers/create-transaction.write.js +1 -0
  128. package/dist/ledger/handlers/reverse-transaction.write.js +1 -0
  129. package/dist/ledger/schedule-subject-pair.d.ts +2 -0
  130. package/dist/ledger/schedule-subject-pair.js +24 -0
  131. package/dist/ledger/schemas.js +8 -2
  132. package/dist/mail-transport-smtp/feature.d.ts +3 -4
  133. package/dist/mail-transport-smtp/feature.js +4 -13
  134. package/dist/managed-pages/feature.js +28 -10
  135. package/dist/managed-pages/table.d.ts +12 -0
  136. package/dist/notes-history/entity.d.ts +12 -0
  137. package/dist/notes-history/entity.js +4 -0
  138. package/dist/notes-history/feature.js +2 -10
  139. package/dist/personal-access-tokens/handlers/create.write.js +1 -0
  140. package/dist/personal-access-tokens/schema/api-token.d.ts +6 -0
  141. package/dist/personal-access-tokens/screens.js +14 -3
  142. package/dist/secrets/table.d.ts +1 -0
  143. package/dist/seo/feature.js +28 -6
  144. package/dist/sessions/feature.d.ts +1 -0
  145. package/dist/sessions/feature.js +9 -8
  146. package/dist/sessions/handlers/detail.query.d.ts +1 -1
  147. package/dist/sessions/handlers/detail.query.js +2 -2
  148. package/dist/sessions/handlers/list.query.d.ts +1 -1
  149. package/dist/sessions/handlers/list.query.js +5 -3
  150. package/dist/sessions/schema/user-session.d.ts +4 -0
  151. package/dist/sessions/screens.d.ts +2 -2
  152. package/dist/sessions/screens.js +6 -7
  153. package/dist/shared/assert-no-where-rule-in-ownership-write.d.ts +2 -0
  154. package/dist/shared/assert-no-where-rule-in-ownership-write.js +18 -0
  155. package/dist/shared/encrypt-for-direct-write.js +10 -2
  156. package/dist/shared/index.d.ts +4 -3
  157. package/dist/shared/index.js +4 -3
  158. package/dist/shared/parent-visibility.d.ts +2 -1
  159. package/dist/shared/parent-visibility.js +21 -0
  160. package/dist/shared/rate-limited-text-response.d.ts +2 -0
  161. package/dist/shared/rate-limited-text-response.js +9 -0
  162. package/dist/shared/row-bound-grant.js +16 -2
  163. package/dist/shared/session-locale-field.d.ts +1 -0
  164. package/dist/shared/session-locale-field.js +6 -0
  165. package/dist/tags/entity.d.ts +9 -0
  166. package/dist/tags/feature.js +2 -10
  167. package/dist/template-resolver/api.d.ts +2 -2
  168. package/dist/template-resolver/handlers/list.query.d.ts +2 -0
  169. package/dist/template-resolver/handlers/list.query.js +2 -2
  170. package/dist/template-resolver/table.d.ts +48 -30
  171. package/dist/template-resolver/user-content-table.d.ts +34 -20
  172. package/dist/tenant/decrypt-tenant-names.d.ts +3 -0
  173. package/dist/tenant/decrypt-tenant-names.js +10 -0
  174. package/dist/tenant/feature.js +7 -1
  175. package/dist/tenant/handlers/me.query.js +5 -1
  176. package/dist/tenant/handlers/memberships.query.js +3 -2
  177. package/dist/tenant/handlers/tenant-directory.query.js +9 -3
  178. package/dist/tenant/handlers/toggle-enabled.write.js +2 -0
  179. package/dist/tenant/handlers/update-member-roles.write.js +1 -0
  180. package/dist/tenant/index.d.ts +1 -0
  181. package/dist/tenant/index.js +1 -0
  182. package/dist/tenant/invitation-table.d.ts +54 -8
  183. package/dist/tenant/invitation-table.js +6 -1
  184. package/dist/tenant/invited-membership.d.ts +8 -6
  185. package/dist/tenant/invited-membership.js +28 -7
  186. package/dist/tenant/membership-table.d.ts +4 -0
  187. package/dist/tenant/schema/tenant.d.ts +18 -10
  188. package/dist/tenant/schema/tenant.js +4 -6
  189. package/dist/tenant/screens.js +1 -1
  190. package/dist/tenant-handover/move-entity-graph.js +11 -18
  191. package/dist/tenant-handover/transfer-graph.d.ts +2 -21
  192. package/dist/tenant-handover/transfer-graph.js +8 -75
  193. package/dist/tenant-lifecycle/boot-checks.d.ts +1 -0
  194. package/dist/tenant-lifecycle/boot-checks.js +13 -0
  195. package/dist/tenant-lifecycle/constants.d.ts +3 -0
  196. package/dist/tenant-lifecycle/constants.js +5 -0
  197. package/dist/tenant-lifecycle/events.d.ts +5 -0
  198. package/dist/tenant-lifecycle/events.js +5 -0
  199. package/dist/tenant-lifecycle/feature.js +6 -3
  200. package/dist/tenant-lifecycle/run-tenant-destroy.d.ts +10 -5
  201. package/dist/tenant-lifecycle/run-tenant-destroy.js +59 -17
  202. package/dist/tenant-lifecycle/stages.d.ts +8 -1
  203. package/dist/tenant-lifecycle/stages.js +33 -4
  204. package/dist/tenant-lifecycle/testing.d.ts +8 -0
  205. package/dist/tenant-lifecycle/testing.js +42 -0
  206. package/dist/tier-engine/entity.d.ts +2 -0
  207. package/dist/tier-engine/handlers/set-tenant-tier.write.js +1 -0
  208. package/dist/tier-engine/tier-resolver.d.ts +2 -0
  209. package/dist/user/handlers/detail.query.js +2 -0
  210. package/dist/user/handlers/list.query.js +6 -2
  211. package/dist/user/handlers/update.write.js +1 -0
  212. package/dist/user/schema/user.d.ts +22 -8
  213. package/dist/user/seeding.d.ts +1 -0
  214. package/dist/user/seeding.js +20 -0
  215. package/dist/user-data-rights/boot-checks.js +5 -0
  216. package/dist/user-data-rights/handlers/confirm-deletion-by-token.write.js +2 -2
  217. package/dist/user-data-rights/handlers/request-deletion.write.js +1 -0
  218. package/dist/user-data-rights/index.d.ts +1 -0
  219. package/dist/user-data-rights/index.js +1 -0
  220. package/dist/user-data-rights/schema/download-attempt.d.ts +14 -0
  221. package/dist/user-data-rights/schema/download-attempt.js +17 -9
  222. package/dist/user-data-rights/schema/download-token.d.ts +8 -0
  223. package/dist/user-data-rights/schema/export-job.d.ts +16 -8
  224. package/dist/user-profile/handlers/change-email.write.js +8 -2
  225. package/dist/waitlist/entity.d.ts +22 -6
  226. package/dist/workflow-runner/event-trigger.d.ts +1 -0
  227. package/dist/workflow-runner/event-trigger.js +63 -15
  228. package/dist/workflow-runner/handlers/resume-run.write.js +8 -2
  229. package/dist/workflow-runner/runner.d.ts +2 -1
  230. package/dist/workflow-runner/runner.js +5 -1
  231. package/package.json +35 -10
  232. package/src/agent-tools/changes.json +12 -0
  233. package/src/audit/changes.json +6 -0
  234. package/src/auth-email-password/changes.json +24 -0
  235. package/src/auth-mfa/changes.json +7 -0
  236. package/src/billing-foundation/changes.json +6 -0
  237. package/src/channel-in-app/changes.json +8 -1
  238. package/src/crypto-shredding/changes.json +12 -0
  239. package/src/delivery/changes.json +6 -0
  240. package/src/files/changes.json +24 -0
  241. package/src/files-tenant-data/changes.json +5 -0
  242. package/src/folders/changes.json +6 -0
  243. package/src/form-draft/changes.json +7 -1
  244. package/src/inbound-provider-imap/changes.json +6 -0
  245. package/src/jobs/changes.json +30 -0
  246. package/src/ledger/changes.json +23 -0
  247. package/src/managed-pages/changes.json +6 -0
  248. package/src/notes-history/changes.json +13 -0
  249. package/src/seo/changes.json +5 -0
  250. package/src/template-resolver/changes.json +5 -0
  251. package/src/tenant/changes.json +19 -0
  252. package/src/tenant-handover/changes.json +12 -0
  253. package/src/tenant-lifecycle/changes.json +22 -0
  254. package/src/tier-engine/changes.json +5 -0
  255. package/src/user/changes.json +15 -0
  256. package/src/user-data-rights/changes.json +6 -0
  257. package/src/workflow-runner/changes.json +5 -0
  258. package/dist/shared/has-where-rule.d.ts +0 -2
  259. package/dist/shared/has-where-rule.js +0 -10
@@ -2,6 +2,7 @@ import { fetchOne, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import { access, definePagedQueryHandler, MAX_LIST_LIMIT, } from "@cosmicdrift/kumiko-framework/engine";
3
3
  import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
4
4
  import * as z from "zod";
5
+ import { decryptTenantNames } from "../decrypt-tenant-names.js";
5
6
  import { tenantTable } from "../schema/tenant.js";
6
7
  import { isSystemAdmin } from "./is-system-admin.js";
7
8
  // Label source behind every `tenant:tenant` reference column (fw#3142). The
@@ -38,7 +39,7 @@ export const tenantDirectoryQuery = definePagedQueryHandler({
38
39
  tenants = await selectMany(db, tenantTable, undefined, {
39
40
  limit: scanLimit,
40
41
  orderBy: [
41
- { col: "name", direction: "asc" },
42
+ { col: "key", direction: "asc" },
42
43
  { col: "id", direction: "asc" },
43
44
  ],
44
45
  });
@@ -48,10 +49,15 @@ export const tenantDirectoryQuery = definePagedQueryHandler({
48
49
  const row = await fetchOne(db, tenantTable, { id: query.user.tenantId });
49
50
  tenants = row ? [row] : [];
50
51
  }
51
- const labeled = tenants.map((tenant) => {
52
+ // name is ciphertext at rest, so the SQL page is keyed by the plaintext
53
+ // `key`; label order and search happen in memory after decrypting.
54
+ const decrypted = await decryptTenantNames(tenants, "tenant:tenant-directory");
55
+ const labeled = decrypted
56
+ .map((tenant) => {
52
57
  const id = String(tenant.id);
53
58
  return { id, label: typeof tenant.name === "string" ? tenant.name : id };
54
- });
59
+ })
60
+ .sort((a, b) => a.label.localeCompare(b.label) || a.id.localeCompare(b.id));
55
61
  const needle = search?.toLowerCase();
56
62
  const rows = needle === undefined
57
63
  ? labeled
@@ -12,6 +12,8 @@ function createToggleTenantHandler(enable) {
12
12
  name: verbName,
13
13
  schema: z.object({ id: z.uuid() }),
14
14
  access: { roles: ["SystemAdmin"] },
15
+ // Disabling locks every member out; enabling is its undo.
16
+ ...(enable ? {} : { agent: { risk: "high" } }),
15
17
  description: enable
16
18
  ? "Re-enables a disabled tenant so its members can sign in and switch to it again; this is the undo for tenant:write:disable."
17
19
  : "Marks a tenant disabled so it stops counting as a membership — its members can no longer sign in to it or switch to it — while leaving all of its data in place.",
@@ -41,6 +41,7 @@ export const updateMemberRolesWrite = defineWriteHandler({
41
41
  // are session-scoped (event.user.tenantId).
42
42
  access: { roles: ["system", ...access.admin] },
43
43
  description: "Sets the roles a user holds in a tenant, rejecting reserved role names and any grant that would raise the target above the caller; roles the caller could not grant (platform roles such as DataProtectionOfficer or TenantOwner, undeclared or higher-tier app roles) are kept, only the system user replaces the full list; admins act on their own tenant, SystemAdmins on any.",
44
+ agent: { risk: "high" },
44
45
  handler: async (event, ctx) => {
45
46
  if (!ctx.systemDb) {
46
47
  throw new InternalError({
@@ -1,6 +1,7 @@
1
1
  export { cancelPendingInvitation } from "./cancel-pending-invitation.js";
2
2
  export { TenantCommandSchemas } from "./command-schemas.js";
3
3
  export { OWNER_INVITE_ROLE_OPTIONS, TENANT_FEATURE, TenantErrors, TenantHandlers, TenantQueries, } from "./constants.js";
4
+ export { decryptTenantNames } from "./decrypt-tenant-names.js";
4
5
  export { collectAssignableAppRoles, createTenantFeature } from "./feature.js";
5
6
  export type { InvitationStatus } from "./invitation-table.js";
6
7
  export { INVITATION_STATUS, INVITATION_STATUSES, tenantInvitationEntity, tenantInvitationsTable, } from "./invitation-table.js";
@@ -1,6 +1,7 @@
1
1
  export { cancelPendingInvitation } from "./cancel-pending-invitation.js";
2
2
  export { TenantCommandSchemas } from "./command-schemas.js";
3
3
  export { OWNER_INVITE_ROLE_OPTIONS, TENANT_FEATURE, TenantErrors, TenantHandlers, TenantQueries, } from "./constants.js";
4
+ export { decryptTenantNames } from "./decrypt-tenant-names.js";
4
5
  export { collectAssignableAppRoles, createTenantFeature } from "./feature.js";
5
6
  export { INVITATION_STATUS, INVITATION_STATUSES, tenantInvitationEntity, tenantInvitationsTable, } from "./invitation-table.js";
6
7
  export { isTenantServingPublicContent } from "./is-tenant-serving-public-content.js";
@@ -11,6 +11,7 @@ export declare const tenantInvitationEntity: import("@cosmicdrift/kumiko-types/f
11
11
  readonly type: "text";
12
12
  readonly description?: string;
13
13
  readonly maxLength?: number;
14
+ readonly minLength?: number;
14
15
  readonly required?: boolean;
15
16
  readonly searchable?: boolean;
16
17
  readonly sortable?: boolean;
@@ -32,6 +33,7 @@ export declare const tenantInvitationEntity: import("@cosmicdrift/kumiko-types/f
32
33
  readonly type: "text";
33
34
  readonly description?: string;
34
35
  readonly maxLength?: number;
36
+ readonly minLength?: number;
35
37
  readonly required?: boolean;
36
38
  readonly searchable?: boolean;
37
39
  readonly sortable?: boolean;
@@ -62,14 +64,15 @@ export declare const tenantInvitationEntity: import("@cosmicdrift/kumiko-types/f
62
64
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
63
65
  readonly display?: "radio" | "dropdown";
64
66
  readonly optionTones?: {
65
- readonly accepted?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
66
- readonly cancelled?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
67
- readonly expired?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
68
- readonly pending?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
67
+ readonly accepted?: "bad" | "neutral" | "ok" | "warn" | undefined;
68
+ readonly cancelled?: "bad" | "neutral" | "ok" | "warn" | undefined;
69
+ readonly expired?: "bad" | "neutral" | "ok" | "warn" | undefined;
70
+ readonly pending?: "bad" | "neutral" | "ok" | "warn" | undefined;
69
71
  } | undefined;
70
72
  readonly optionsQuery?: string;
71
73
  readonly optionsQueryPayload?: import("@cosmicdrift/kumiko-types/fields").OptionsQueryPayload;
72
74
  readonly optionsAvailabilityQuery?: string;
75
+ readonly conditionalOptions?: readonly import("@cosmicdrift/kumiko-types/fields").SelectConditionalOptions<"accepted" | "cancelled" | "expired" | "pending">[] | undefined;
73
76
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
74
77
  required: true;
75
78
  };
@@ -77,6 +80,7 @@ export declare const tenantInvitationEntity: import("@cosmicdrift/kumiko-types/f
77
80
  readonly type: "text";
78
81
  readonly description?: string;
79
82
  readonly maxLength?: number;
83
+ readonly minLength?: number;
80
84
  readonly required?: boolean;
81
85
  readonly searchable?: boolean;
82
86
  readonly sortable?: boolean;
@@ -94,6 +98,25 @@ export declare const tenantInvitationEntity: import("@cosmicdrift/kumiko-types/f
94
98
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
95
99
  required: true;
96
100
  };
101
+ membershipVersion: {
102
+ readonly type: "number";
103
+ readonly description?: string;
104
+ readonly required?: boolean;
105
+ readonly sortable?: boolean;
106
+ readonly filterable?: boolean;
107
+ readonly sensitive?: boolean;
108
+ readonly default?: number;
109
+ readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
110
+ readonly min?: number;
111
+ readonly max?: number;
112
+ readonly integer?: boolean;
113
+ readonly unit?: string | {
114
+ readonly field: string;
115
+ };
116
+ readonly grouping?: boolean;
117
+ } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
118
+ required: false;
119
+ };
97
120
  expiresAt: {
98
121
  readonly type: "timestamp";
99
122
  readonly description?: string;
@@ -115,6 +138,7 @@ export declare const tenantInvitationsTable: import("@cosmicdrift/kumiko-framewo
115
138
  readonly type: "text";
116
139
  readonly description?: string;
117
140
  readonly maxLength?: number;
141
+ readonly minLength?: number;
118
142
  readonly required?: boolean;
119
143
  readonly searchable?: boolean;
120
144
  readonly sortable?: boolean;
@@ -136,6 +160,7 @@ export declare const tenantInvitationsTable: import("@cosmicdrift/kumiko-framewo
136
160
  readonly type: "text";
137
161
  readonly description?: string;
138
162
  readonly maxLength?: number;
163
+ readonly minLength?: number;
139
164
  readonly required?: boolean;
140
165
  readonly searchable?: boolean;
141
166
  readonly sortable?: boolean;
@@ -166,14 +191,15 @@ export declare const tenantInvitationsTable: import("@cosmicdrift/kumiko-framewo
166
191
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
167
192
  readonly display?: "radio" | "dropdown";
168
193
  readonly optionTones?: {
169
- readonly accepted?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
170
- readonly cancelled?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
171
- readonly expired?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
172
- readonly pending?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
194
+ readonly accepted?: "bad" | "neutral" | "ok" | "warn" | undefined;
195
+ readonly cancelled?: "bad" | "neutral" | "ok" | "warn" | undefined;
196
+ readonly expired?: "bad" | "neutral" | "ok" | "warn" | undefined;
197
+ readonly pending?: "bad" | "neutral" | "ok" | "warn" | undefined;
173
198
  } | undefined;
174
199
  readonly optionsQuery?: string;
175
200
  readonly optionsQueryPayload?: import("@cosmicdrift/kumiko-types/fields").OptionsQueryPayload;
176
201
  readonly optionsAvailabilityQuery?: string;
202
+ readonly conditionalOptions?: readonly import("@cosmicdrift/kumiko-types/fields").SelectConditionalOptions<"accepted" | "cancelled" | "expired" | "pending">[] | undefined;
177
203
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
178
204
  required: true;
179
205
  };
@@ -181,6 +207,7 @@ export declare const tenantInvitationsTable: import("@cosmicdrift/kumiko-framewo
181
207
  readonly type: "text";
182
208
  readonly description?: string;
183
209
  readonly maxLength?: number;
210
+ readonly minLength?: number;
184
211
  readonly required?: boolean;
185
212
  readonly searchable?: boolean;
186
213
  readonly sortable?: boolean;
@@ -198,6 +225,25 @@ export declare const tenantInvitationsTable: import("@cosmicdrift/kumiko-framewo
198
225
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
199
226
  required: true;
200
227
  };
228
+ membershipVersion: {
229
+ readonly type: "number";
230
+ readonly description?: string;
231
+ readonly required?: boolean;
232
+ readonly sortable?: boolean;
233
+ readonly filterable?: boolean;
234
+ readonly sensitive?: boolean;
235
+ readonly default?: number;
236
+ readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
237
+ readonly min?: number;
238
+ readonly max?: number;
239
+ readonly integer?: boolean;
240
+ readonly unit?: string | {
241
+ readonly field: string;
242
+ };
243
+ readonly grouping?: boolean;
244
+ } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
245
+ required: false;
246
+ };
201
247
  expiresAt: {
202
248
  readonly type: "timestamp";
203
249
  readonly description?: string;
@@ -22,7 +22,7 @@
22
22
  // pending → re-use existing row + refresh Redis-token + send mail
23
23
  // (analog zu signup-Resend).
24
24
  import { buildEntityTable } from "@cosmicdrift/kumiko-framework/db";
25
- import { createEntity, createMultiSelectField, createSelectField, createTextField, createTimestampField, } from "@cosmicdrift/kumiko-framework/engine";
25
+ import { createEntity, createMultiSelectField, createNumberField, createSelectField, createTextField, createTimestampField, } from "@cosmicdrift/kumiko-framework/engine";
26
26
  // kumiko-lint-ignore cross-feature-import global-role options are owned by the user feature
27
27
  import { GLOBAL_ROLE_OPTIONS } from "../user/constants.js";
28
28
  // Status-const-Object damit Handler-Code keine Magic-Strings nutzt.
@@ -83,6 +83,11 @@ export const tenantInvitationEntity = createEntity({
83
83
  personal: { of: "invitedBy" },
84
84
  find: "exact",
85
85
  }),
86
+ // Version of the invitee's membership row when the invitation was issued
87
+ // (0 = no membership yet). Accept compares it against the current version
88
+ // to detect a membership decision made after the invite. NULL only on rows
89
+ // issued before this field existed.
90
+ membershipVersion: createNumberField({ integer: true, min: 0 }),
86
91
  // UI-Anzeige — Wahrheit liegt in Redis-TTL.
87
92
  expiresAt: createTimestampField({ required: true }),
88
93
  },
@@ -1,12 +1,18 @@
1
- import { type DbRunner } from "@cosmicdrift/kumiko-framework/db";
1
+ import { type DbRunner, type TenantDb } from "@cosmicdrift/kumiko-framework/db";
2
2
  import { type TenantId } from "@cosmicdrift/kumiko-framework/engine";
3
3
  import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
4
4
  export type InvitedMembershipOptions = {
5
5
  readonly userId: string;
6
6
  readonly tenantId: TenantId;
7
7
  readonly role: string;
8
- readonly invitationIssuedAt: Temporal.Instant;
8
+ readonly invitation: InvitationIssuance;
9
9
  };
10
+ export type InvitationIssuance = {
11
+ readonly membershipVersion: number | null;
12
+ readonly insertedAt: Temporal.Instant;
13
+ readonly modifiedAt: Temporal.Instant | null;
14
+ };
15
+ export declare function membershipVersionOf(db: Pick<TenantDb, "fetchOne">, tenantId: string, userId: string): Promise<number>;
10
16
  export declare function grantInvitedMembershipRole(db: DbRunner, options: InvitedMembershipOptions): Promise<import("@cosmicdrift/kumiko-types/write-error-info-types").WriteFailure | {
11
17
  readonly isSuccess: true;
12
18
  readonly data: {
@@ -14,7 +20,3 @@ export declare function grantInvitedMembershipRole(db: DbRunner, options: Invite
14
20
  readonly roles: readonly string[];
15
21
  };
16
22
  }>;
17
- export declare function invitationIssuedAt(invitation: {
18
- readonly insertedAt: Temporal.Instant;
19
- readonly modifiedAt: Temporal.Instant | null;
20
- }): Temporal.Instant;
@@ -9,6 +9,15 @@ import { findForbiddenMembershipRole, reservedMembershipRoleError } from "./memb
9
9
  import { tenantMembershipEntity, tenantMembershipsTable } from "./membership-table.js";
10
10
  import { seedTenantMembership } from "./seeding.js";
11
11
  const membershipExecutor = createEventStoreExecutor(tenantMembershipsTable, tenantMembershipEntity, { entityName: "tenant-membership" });
12
+ const MEMBERSHIP_CREATED_VERSION = 1;
13
+ // 0 = the user has no membership in the tenant yet.
14
+ export async function membershipVersionOf(db, tenantId, userId) {
15
+ const membership = await db.fetchOne(tenantMembershipsTable, {
16
+ tenantId,
17
+ userId,
18
+ });
19
+ return membership?.version ?? 0;
20
+ }
12
21
  // Add-only, so unlike updateMemberRoles no session revoke or last-TenantAdmin check.
13
22
  // Read unfiltered: tenant:query:memberships hides disabled tenants.
14
23
  export async function grantInvitedMembershipRole(db, options) {
@@ -28,7 +37,7 @@ export async function grantInvitedMembershipRole(db, options) {
28
37
  return grantedMembership(false, [options.role]);
29
38
  }
30
39
  // A membership decision made after the invitation was issued wins over it.
31
- if (isMembershipChangedAfter(existing, options.invitationIssuedAt)) {
40
+ if (isSupersededByMembership(existing, options.invitation)) {
32
41
  return writeFailure(invitationSupersededError());
33
42
  }
34
43
  const currentRoles = parseRoles(existing.roles);
@@ -40,13 +49,25 @@ export async function grantInvitedMembershipRole(db, options) {
40
49
  return updateResult;
41
50
  return grantedMembership(true, roles);
42
51
  }
43
- // Resend reuses the invitation row, so its last modification is the latest issuance.
44
- export function invitationIssuedAt(invitation) {
45
- return invitation.modifiedAt ?? invitation.insertedAt;
46
- }
47
- function isMembershipChangedAfter(membership, instant) {
52
+ // The pinned version is the membership's version when the invitation was issued
53
+ // (0 = none yet). A different version now means the membership was decided after
54
+ // the invite. Pin 0: a membership that appeared afterwards is an additive grant
55
+ // only while it is untouched since its creation (version 1). A role change bumps
56
+ // the version above 1 and counts as a decision. A removal in between hard-deletes
57
+ // the row and cancels the invitation (remove-member), so it never reaches this check.
58
+ // Rows issued before the pin existed (null) fall back to wall-clock timestamps,
59
+ // which can misjudge changes within the same millisecond but is all they carry.
60
+ function isSupersededByMembership(membership, invitation) {
61
+ if (invitation.membershipVersion !== null) {
62
+ if (invitation.membershipVersion === 0) {
63
+ return membership.version > MEMBERSHIP_CREATED_VERSION;
64
+ }
65
+ return membership.version !== invitation.membershipVersion;
66
+ }
67
+ // Resend reuses the invitation row, so its last modification is the latest issuance.
68
+ const issuedAt = invitation.modifiedAt ?? invitation.insertedAt;
48
69
  const lastChangedAt = membership.modifiedAt ?? membership.insertedAt;
49
- return Temporal.Instant.compare(lastChangedAt, instant) > 0;
70
+ return Temporal.Instant.compare(lastChangedAt, issuedAt) > 0;
50
71
  }
51
72
  function invitationSupersededError() {
52
73
  return new ConflictError({
@@ -3,6 +3,7 @@ export declare const tenantMembershipEntity: import("@cosmicdrift/kumiko-types/f
3
3
  readonly type: "text";
4
4
  readonly description?: string;
5
5
  readonly maxLength?: number;
6
+ readonly minLength?: number;
6
7
  readonly required?: boolean;
7
8
  readonly searchable?: boolean;
8
9
  readonly sortable?: boolean;
@@ -24,6 +25,7 @@ export declare const tenantMembershipEntity: import("@cosmicdrift/kumiko-types/f
24
25
  readonly type: "text";
25
26
  readonly description?: string;
26
27
  readonly maxLength?: number;
28
+ readonly minLength?: number;
27
29
  readonly required?: boolean;
28
30
  readonly searchable?: boolean;
29
31
  readonly sortable?: boolean;
@@ -47,6 +49,7 @@ export declare const tenantMembershipsTable: import("@cosmicdrift/kumiko-framewo
47
49
  readonly type: "text";
48
50
  readonly description?: string;
49
51
  readonly maxLength?: number;
52
+ readonly minLength?: number;
50
53
  readonly required?: boolean;
51
54
  readonly searchable?: boolean;
52
55
  readonly sortable?: boolean;
@@ -68,6 +71,7 @@ export declare const tenantMembershipsTable: import("@cosmicdrift/kumiko-framewo
68
71
  readonly type: "text";
69
72
  readonly description?: string;
70
73
  readonly maxLength?: number;
74
+ readonly minLength?: number;
71
75
  readonly required?: boolean;
72
76
  readonly searchable?: boolean;
73
77
  readonly sortable?: boolean;
@@ -5,6 +5,7 @@ export declare const tenantEntity: import("@cosmicdrift/kumiko-types/fields").En
5
5
  readonly type: "text";
6
6
  readonly description?: string;
7
7
  readonly maxLength?: number;
8
+ readonly minLength?: number;
8
9
  readonly required?: boolean;
9
10
  readonly searchable?: boolean;
10
11
  readonly sortable?: boolean;
@@ -26,6 +27,7 @@ export declare const tenantEntity: import("@cosmicdrift/kumiko-types/fields").En
26
27
  readonly type: "text";
27
28
  readonly description?: string;
28
29
  readonly maxLength?: number;
30
+ readonly minLength?: number;
29
31
  readonly required?: boolean;
30
32
  readonly searchable?: boolean;
31
33
  readonly sortable?: boolean;
@@ -58,15 +60,16 @@ export declare const tenantEntity: import("@cosmicdrift/kumiko-types/fields").En
58
60
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
59
61
  readonly display?: "radio" | "dropdown";
60
62
  readonly optionTones?: {
61
- readonly active?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
62
- readonly destroyFailed?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
63
- readonly destroyRequested?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
64
- readonly destroyed?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
65
- readonly destroying?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
63
+ readonly active?: "bad" | "neutral" | "ok" | "warn" | undefined;
64
+ readonly destroyFailed?: "bad" | "neutral" | "ok" | "warn" | undefined;
65
+ readonly destroyRequested?: "bad" | "neutral" | "ok" | "warn" | undefined;
66
+ readonly destroyed?: "bad" | "neutral" | "ok" | "warn" | undefined;
67
+ readonly destroying?: "bad" | "neutral" | "ok" | "warn" | undefined;
66
68
  } | undefined;
67
69
  readonly optionsQuery?: string;
68
70
  readonly optionsQueryPayload?: import("@cosmicdrift/kumiko-types/fields").OptionsQueryPayload;
69
71
  readonly optionsAvailabilityQuery?: string;
72
+ readonly conditionalOptions?: readonly import("@cosmicdrift/kumiko-types/fields").SelectConditionalOptions<"active" | "destroyFailed" | "destroyRequested" | "destroyed" | "destroying">[] | undefined;
70
73
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
71
74
  required: true;
72
75
  };
@@ -89,6 +92,7 @@ export declare const tenantEntity: import("@cosmicdrift/kumiko-types/fields").En
89
92
  readonly type: "text";
90
93
  readonly description?: string;
91
94
  readonly maxLength?: number;
95
+ readonly minLength?: number;
92
96
  readonly required?: boolean;
93
97
  readonly searchable?: boolean;
94
98
  readonly sortable?: boolean;
@@ -157,6 +161,7 @@ export declare const tenantTable: import("@cosmicdrift/kumiko-framework/db").Ent
157
161
  readonly type: "text";
158
162
  readonly description?: string;
159
163
  readonly maxLength?: number;
164
+ readonly minLength?: number;
160
165
  readonly required?: boolean;
161
166
  readonly searchable?: boolean;
162
167
  readonly sortable?: boolean;
@@ -178,6 +183,7 @@ export declare const tenantTable: import("@cosmicdrift/kumiko-framework/db").Ent
178
183
  readonly type: "text";
179
184
  readonly description?: string;
180
185
  readonly maxLength?: number;
186
+ readonly minLength?: number;
181
187
  readonly required?: boolean;
182
188
  readonly searchable?: boolean;
183
189
  readonly sortable?: boolean;
@@ -210,15 +216,16 @@ export declare const tenantTable: import("@cosmicdrift/kumiko-framework/db").Ent
210
216
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
211
217
  readonly display?: "radio" | "dropdown";
212
218
  readonly optionTones?: {
213
- readonly active?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
214
- readonly destroyFailed?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
215
- readonly destroyRequested?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
216
- readonly destroyed?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
217
- readonly destroying?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
219
+ readonly active?: "bad" | "neutral" | "ok" | "warn" | undefined;
220
+ readonly destroyFailed?: "bad" | "neutral" | "ok" | "warn" | undefined;
221
+ readonly destroyRequested?: "bad" | "neutral" | "ok" | "warn" | undefined;
222
+ readonly destroyed?: "bad" | "neutral" | "ok" | "warn" | undefined;
223
+ readonly destroying?: "bad" | "neutral" | "ok" | "warn" | undefined;
218
224
  } | undefined;
219
225
  readonly optionsQuery?: string;
220
226
  readonly optionsQueryPayload?: import("@cosmicdrift/kumiko-types/fields").OptionsQueryPayload;
221
227
  readonly optionsAvailabilityQuery?: string;
228
+ readonly conditionalOptions?: readonly import("@cosmicdrift/kumiko-types/fields").SelectConditionalOptions<"active" | "destroyFailed" | "destroyRequested" | "destroyed" | "destroying">[] | undefined;
222
229
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
223
230
  required: true;
224
231
  };
@@ -241,6 +248,7 @@ export declare const tenantTable: import("@cosmicdrift/kumiko-framework/db").Ent
241
248
  readonly type: "text";
242
249
  readonly description?: string;
243
250
  readonly maxLength?: number;
251
+ readonly minLength?: number;
244
252
  readonly required?: boolean;
245
253
  readonly searchable?: boolean;
246
254
  readonly sortable?: boolean;
@@ -17,19 +17,17 @@ export const tenantEntity = createEntity({
17
17
  key: createTextField({
18
18
  required: true,
19
19
  maxLength: 50,
20
+ sortable: true,
20
21
  personal: false,
21
22
  reason: "technical_reference",
22
23
  }),
23
- // Workspace/company display name, not an individual's identity —
24
- // same class as the `company.legalName` example in the boot-validator's
25
- // own PII-heuristic doc-comment.
24
+ // Subject is the tenant record itself: tenant events live in the writer's (system) tenant stream, so `personal: "tenant"` would not resolve to this tenant.
26
25
  name: createTextField({
27
26
  required: true,
28
27
  maxLength: 200,
29
28
  searchable: true,
30
- sortable: true,
31
- personal: false,
32
- reason: "is_business_data",
29
+ personal: { of: "id" },
30
+ find: "none",
33
31
  }),
34
32
  isEnabled: createBooleanField({ default: true, filterable: true }),
35
33
  // Tenant-destroy lifecycle (tenant-lifecycle feature). Defaults keep
@@ -23,7 +23,7 @@ export const tenantListScreen = {
23
23
  entityId: "id",
24
24
  },
25
25
  ],
26
- defaultSort: { field: "name", dir: "asc" },
26
+ defaultSort: { field: "key", dir: "asc" },
27
27
  searchable: true,
28
28
  access: { roles: ["SystemAdmin"] },
29
29
  // Inert until an app navs it (see file header) — no nav area to resolve
@@ -174,7 +174,7 @@ export async function moveTransferGraph(args) {
174
174
  let pending = [
175
175
  { entityName: rootEntityName, rowIds: [rootRowId] },
176
176
  ];
177
- for (let round = 0; round < MAX_TRANSFER_DEPTH && pending.length > 0; round++) {
177
+ for (let round = 0; round <= MAX_TRANSFER_DEPTH && pending.length > 0; round++) {
178
178
  // Merged per type so an edge leaving a type runs once per round, not once
179
179
  // per edge that reached it.
180
180
  const discovered = new Map();
@@ -231,24 +231,17 @@ export async function moveTransferGraph(args) {
231
231
  }));
232
232
  }
233
233
  }
234
+ // The extra round past the limit is a probe: it only exists to find out
235
+ // whether rows still hang below the last allowed hop. Its moves are rolled
236
+ // back with the transaction when this throws.
237
+ const [strandedEntityName] = discovered.keys();
238
+ if (round === MAX_TRANSFER_DEPTH && strandedEntityName !== undefined) {
239
+ throw new UnprocessableError("transfer_graph_too_deep", {
240
+ i18nKey: "errors.tenantHandover.transferGraphTooDeep",
241
+ details: { entityName: strandedEntityName },
242
+ });
243
+ }
234
244
  pending = [...discovered].map(([entityName, rowIds]) => ({ entityName, rowIds }));
235
245
  }
236
- // The depth limit is a safety net, not a licence to move part of a graph: if
237
- // rounds ran out while rows remain whose type still leads somewhere, the
238
- // declaration reaches further than the mover does, and returning quietly
239
- // would leave exactly the orphaned rows #3088 is about.
240
- //
241
- // Rounds count hops of both edge kinds, while the boot validator measures
242
- // reference chains between transferable entities only. A graph that fills the
243
- // limit with reference hops and then adds a parentRef child therefore boots
244
- // clean and fails here instead — the residual the validator cannot see, and
245
- // the reason this check has to exist rather than trusting boot alone.
246
- const stranded = pending.find(({ entityName }) => (adjacency.get(entityName) ?? []).length > 0);
247
- if (stranded !== undefined) {
248
- throw new UnprocessableError("transfer_graph_too_deep", {
249
- i18nKey: "errors.tenantHandover.transferGraphTooDeep",
250
- details: { entityName: stranded.entityName },
251
- });
252
- }
253
246
  return movedCounts;
254
247
  }
@@ -1,23 +1,4 @@
1
- import { type EntityDefinition, type Registry } from "@cosmicdrift/kumiko-framework/engine";
2
- export type TransferEdgeLink = {
3
- readonly kind: "parentRef";
4
- readonly typeField: string;
5
- readonly idField: string;
6
- } | {
7
- readonly kind: "reference";
8
- readonly field: string;
9
- };
10
- export type TransferEdge = {
11
- readonly entityName: string;
12
- readonly entity: EntityDefinition;
13
- /** The already-resolved type this edge hangs off — its moved row ids are
14
- * the parent ids the mover matches this edge's rows against. */
15
- readonly parentEntityName: string;
16
- readonly link: TransferEdgeLink;
17
- };
1
+ import { type EntityDefinition, type Registry, type TransferAdjacency } from "@cosmicdrift/kumiko-framework/engine";
2
+ export type { TransferAdjacency, TransferEdge, TransferEdgeLink, } from "@cosmicdrift/kumiko-framework/engine";
18
3
  export declare function resolveTransferableRoot(registry: Registry, entityType: string): EntityDefinition | undefined;
19
- /** Outgoing edges per type: keyed by the type an edge hangs off, so the mover
20
- * can ask "what hangs off the rows I just moved" for any type it reaches, at
21
- * any depth, as often as it reaches it. */
22
- export type TransferAdjacency = ReadonlyMap<string, readonly TransferEdge[]>;
23
4
  export declare function resolveTransferAdjacency(registry: Registry, rootEntityType: string): TransferAdjacency;