@cosmicdrift/kumiko-bundled-features 0.337.1 → 0.339.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 (235) hide show
  1. package/dist/agent-tools/tool-catalog.js +37 -17
  2. package/dist/auth-email-password/handlers/invite-accept-with-login.write.js +3 -2
  3. package/dist/auth-email-password/handlers/invite-accept.write.js +3 -4
  4. package/dist/auth-email-password/handlers/invite-info.query.js +4 -1
  5. package/dist/auth-email-password/handlers/invite-signup-complete.write.js +3 -4
  6. package/dist/auth-email-password/handlers/signup-confirm.write.js +2 -1
  7. package/dist/auth-email-password/handlers/signup-request.write.js +2 -2
  8. package/dist/auth-email-password/web/auth-gate.d.ts +12 -2
  9. package/dist/auth-email-password/web/auth-gate.js +25 -1
  10. package/dist/auth-email-password/web/auth-redirect.d.ts +5 -0
  11. package/dist/auth-email-password/web/auth-redirect.js +58 -0
  12. package/dist/auth-email-password/web/client-plugin.d.ts +8 -1
  13. package/dist/auth-email-password/web/client-plugin.js +2 -0
  14. package/dist/auth-email-password/web/index.d.ts +2 -1
  15. package/dist/auth-email-password/web/index.js +1 -0
  16. package/dist/auth-email-password/web/session.d.ts +3 -1
  17. package/dist/auth-email-password/web/session.js +8 -2
  18. package/dist/auth-mfa/feature.js +1 -1
  19. package/dist/auth-mfa/handlers/enable-confirm-preauth.write.js +1 -1
  20. package/dist/auth-mfa/handlers/enable-start-preauth.write.js +2 -1
  21. package/dist/auth-mfa/handlers/verify.write.js +1 -1
  22. package/dist/auth-mfa/mfa-code-verifier.js +1 -1
  23. package/dist/auth-mfa/mfa-status-checker.js +1 -1
  24. package/dist/billing-foundation/aggregate-id.d.ts +2 -0
  25. package/dist/billing-foundation/aggregate-id.js +8 -0
  26. package/dist/billing-foundation/checkout-core.d.ts +3 -0
  27. package/dist/billing-foundation/checkout-core.js +3 -0
  28. package/dist/billing-foundation/constants.d.ts +38 -0
  29. package/dist/billing-foundation/constants.js +43 -0
  30. package/dist/billing-foundation/consumer-protection/confirmation-mail.d.ts +21 -0
  31. package/dist/billing-foundation/consumer-protection/confirmation-mail.js +125 -0
  32. package/dist/billing-foundation/consumer-protection/consent-gate.d.ts +32 -0
  33. package/dist/billing-foundation/consumer-protection/consent-gate.js +96 -0
  34. package/dist/billing-foundation/consumer-protection/consent-locale.d.ts +5 -0
  35. package/dist/billing-foundation/consumer-protection/consent-locale.js +17 -0
  36. package/dist/billing-foundation/consumer-protection/consent-text.d.ts +11 -0
  37. package/dist/billing-foundation/consumer-protection/consent-text.js +28 -0
  38. package/dist/billing-foundation/consumer-protection/issue-confirmation.d.ts +15 -0
  39. package/dist/billing-foundation/consumer-protection/issue-confirmation.js +149 -0
  40. package/dist/billing-foundation/consumer-protection/register-consent.d.ts +3 -0
  41. package/dist/billing-foundation/consumer-protection/register-consent.js +63 -0
  42. package/dist/billing-foundation/consumer-protection/register-termination.d.ts +3 -0
  43. package/dist/billing-foundation/consumer-protection/register-termination.js +40 -0
  44. package/dist/billing-foundation/consumer-protection/termination-mail.d.ts +45 -0
  45. package/dist/billing-foundation/consumer-protection/termination-mail.js +76 -0
  46. package/dist/billing-foundation/consumer-protection/termination-notify.d.ts +18 -0
  47. package/dist/billing-foundation/consumer-protection/termination-notify.js +21 -0
  48. package/dist/billing-foundation/consumer-protection/termination-pages.d.ts +8 -0
  49. package/dist/billing-foundation/consumer-protection/termination-pages.js +268 -0
  50. package/dist/billing-foundation/consumer-protection/termination-record.d.ts +73 -0
  51. package/dist/billing-foundation/consumer-protection/termination-record.js +230 -0
  52. package/dist/billing-foundation/consumer-protection/termination-request.d.ts +45 -0
  53. package/dist/billing-foundation/consumer-protection/termination-request.js +231 -0
  54. package/dist/billing-foundation/consumer-protection/termination-texts.d.ts +62 -0
  55. package/dist/billing-foundation/consumer-protection/termination-texts.js +168 -0
  56. package/dist/billing-foundation/events.d.ts +105 -2
  57. package/dist/billing-foundation/events.js +88 -2
  58. package/dist/billing-foundation/feature.js +6 -2
  59. package/dist/billing-foundation/handlers/billing-plans.query.js +19 -1
  60. package/dist/billing-foundation/handlers/create-checkout-session.write.js +33 -2
  61. package/dist/billing-foundation/handlers/process-event.write.d.ts +2 -2
  62. package/dist/billing-foundation/handlers/process-event.write.js +43 -15
  63. package/dist/billing-foundation/handlers/process-payment-event.write.d.ts +2 -1
  64. package/dist/billing-foundation/handlers/process-payment-event.write.js +6 -2
  65. package/dist/billing-foundation/handlers/start-plan-checkout.write.js +17 -1
  66. package/dist/billing-foundation/handlers/sync-subscription.write.js +0 -1
  67. package/dist/billing-foundation/i18n.js +61 -0
  68. package/dist/billing-foundation/index.d.ts +3 -2
  69. package/dist/billing-foundation/index.js +2 -1
  70. package/dist/billing-foundation/tenant-destroy-hook.js +4 -4
  71. package/dist/billing-foundation/types.d.ts +54 -9
  72. package/dist/billing-foundation/validate-options.js +51 -10
  73. package/dist/billing-foundation/web/billing-plans-panel.js +40 -3
  74. package/dist/billing-foundation/web/cancel-contract-dialog.d.ts +7 -0
  75. package/dist/billing-foundation/web/cancel-contract-dialog.js +57 -0
  76. package/dist/billing-foundation/web/checkout-consent-dialog.d.ts +28 -0
  77. package/dist/billing-foundation/web/checkout-consent-dialog.js +38 -0
  78. package/dist/billing-foundation/webhook-handler.js +5 -2
  79. package/dist/cap-counter/with-cap-enforcement.js +7 -3
  80. package/dist/cap-overview/access.d.ts +2 -1
  81. package/dist/cap-overview/access.js +26 -5
  82. package/dist/cap-overview/feature.d.ts +4 -0
  83. package/dist/cap-overview/feature.js +8 -6
  84. package/dist/cap-overview/handlers/caps-usage.query.d.ts +1 -1
  85. package/dist/cap-overview/handlers/caps-usage.query.js +6 -4
  86. package/dist/cap-overview/handlers/tenant-caps-list.query.js +12 -7
  87. package/dist/cap-overview/index.d.ts +2 -2
  88. package/dist/cap-overview/index.js +2 -2
  89. package/dist/cap-overview/screens.d.ts +1 -1
  90. package/dist/cap-overview/screens.js +17 -16
  91. package/dist/channel-discord/discord-channel.d.ts +6 -0
  92. package/dist/channel-discord/discord-channel.js +19 -0
  93. package/dist/channel-discord/feature.d.ts +3 -0
  94. package/dist/channel-discord/feature.js +30 -0
  95. package/dist/channel-discord/index.d.ts +2 -0
  96. package/dist/channel-discord/index.js +2 -0
  97. package/dist/channel-slack/feature.d.ts +3 -0
  98. package/dist/channel-slack/feature.js +30 -0
  99. package/dist/channel-slack/index.d.ts +2 -0
  100. package/dist/channel-slack/index.js +2 -0
  101. package/dist/channel-slack/slack-channel.d.ts +5 -0
  102. package/dist/channel-slack/slack-channel.js +16 -0
  103. package/dist/channel-teams/feature.d.ts +3 -0
  104. package/dist/channel-teams/feature.js +34 -0
  105. package/dist/channel-teams/index.d.ts +2 -0
  106. package/dist/channel-teams/index.js +2 -0
  107. package/dist/channel-teams/teams-channel.d.ts +5 -0
  108. package/dist/channel-teams/teams-channel.js +38 -0
  109. package/dist/channel-telegram/feature.d.ts +3 -0
  110. package/dist/channel-telegram/feature.js +28 -0
  111. package/dist/channel-telegram/index.d.ts +2 -0
  112. package/dist/channel-telegram/index.js +2 -0
  113. package/dist/channel-telegram/telegram-channel.d.ts +8 -0
  114. package/dist/channel-telegram/telegram-channel.js +54 -0
  115. package/dist/config/handlers/set.write.js +18 -2
  116. package/dist/crypto-shredding/feature.js +3 -2
  117. package/dist/crypto-shredding/handlers/forget-subject.write.d.ts +1 -1
  118. package/dist/crypto-shredding/handlers/forget-subject.write.js +30 -19
  119. package/dist/custom-fields/feature.js +9 -6
  120. package/dist/custom-fields/handlers/clear-custom-field.write.js +2 -1
  121. package/dist/custom-fields/handlers/define-tenant-field.write.js +2 -1
  122. package/dist/custom-fields/handlers/set-custom-field.write.js +2 -1
  123. package/dist/custom-fields/wire-user-data-rights.js +1 -1
  124. package/dist/data-retention/feature.js +1 -1
  125. package/dist/delivery/channel-context.d.ts +2 -1
  126. package/dist/delivery/channel-context.js +2 -2
  127. package/dist/delivery/chat-webhook-channel.d.ts +19 -0
  128. package/dist/delivery/chat-webhook-channel.js +48 -0
  129. package/dist/delivery/chat-webhook-sender.d.ts +21 -0
  130. package/dist/delivery/chat-webhook-sender.js +102 -0
  131. package/dist/delivery/delivery-service.js +12 -7
  132. package/dist/delivery/index.d.ts +2 -0
  133. package/dist/delivery/index.js +2 -0
  134. package/dist/delivery/jobs.js +1 -1
  135. package/dist/delivery/testing.d.ts +2 -0
  136. package/dist/delivery/types.d.ts +3 -1
  137. package/dist/delivery/types.js +1 -2
  138. package/dist/document-ingest-foundation/feature.js +3 -2
  139. package/dist/file-derivatives/handlers/public-variant-by-file-ref.query.js +1 -1
  140. package/dist/files-tenant-data/index.js +1 -1
  141. package/dist/form-draft/feature.js +1 -1
  142. package/dist/form-draft/handlers/discard.write.js +2 -1
  143. package/dist/form-draft/handlers/save.write.js +2 -1
  144. package/dist/inbound-mail-foundation/connect-routes.d.ts +7 -0
  145. package/dist/inbound-mail-foundation/connect-routes.js +13 -8
  146. package/dist/inbound-mail-foundation/feature.js +1 -1
  147. package/dist/inbound-mail-foundation/handlers/ingest-message.write.js +2 -1
  148. package/dist/inbound-mail-foundation/index.d.ts +1 -0
  149. package/dist/inbound-mail-foundation/index.js +1 -0
  150. package/dist/inbound-mail-foundation/oauth-access-token.d.ts +28 -0
  151. package/dist/inbound-mail-foundation/oauth-access-token.js +131 -0
  152. package/dist/inbound-mail-foundation/tenant-destroy-hook.js +1 -1
  153. package/dist/inbound-mail-foundation/types.d.ts +18 -1
  154. package/dist/inbound-mail-foundation/watch-supervisor.d.ts +5 -0
  155. package/dist/inbound-mail-foundation/watch-supervisor.js +42 -14
  156. package/dist/managed-pages/handlers/set.write.js +2 -1
  157. package/dist/notes-history/executor.d.ts +2 -0
  158. package/dist/notes-history/executor.js +9 -1
  159. package/dist/notes-history/feature.d.ts +2 -3
  160. package/dist/notes-history/feature.js +2 -1
  161. package/dist/notes-history/handlers/add-note.write.d.ts +2 -1
  162. package/dist/notes-history/handlers/add-note.write.js +21 -4
  163. package/dist/notes-history-user-data/hooks.js +26 -8
  164. package/dist/renderer-simple/feature.d.ts +8 -2
  165. package/dist/renderer-simple/feature.js +8 -5
  166. package/dist/renderer-simple/index.d.ts +2 -2
  167. package/dist/renderer-simple/index.js +1 -1
  168. package/dist/renderer-simple/simple-renderer.d.ts +15 -0
  169. package/dist/renderer-simple/simple-renderer.js +74 -27
  170. package/dist/secrets/feature.js +1 -1
  171. package/dist/sessions/feature.js +1 -1
  172. package/dist/sessions/handlers/revoke-all-for-user.write.js +2 -1
  173. package/dist/subscription-mollie/plugin-methods.js +1 -0
  174. package/dist/subscription-mollie/verify-webhook.js +5 -3
  175. package/dist/subscription-stripe/plugin-methods.d.ts +3 -1
  176. package/dist/subscription-stripe/plugin-methods.js +78 -13
  177. package/dist/subscription-stripe/verify-webhook.js +7 -3
  178. package/dist/tags/executor.d.ts +2 -0
  179. package/dist/tags/executor.js +9 -1
  180. package/dist/tags/feature.d.ts +4 -3
  181. package/dist/tags/feature.js +4 -2
  182. package/dist/tags/handlers/assign-tag.write.d.ts +2 -1
  183. package/dist/tags/handlers/assign-tag.write.js +4 -4
  184. package/dist/tags/handlers/remove-tag.write.d.ts +2 -1
  185. package/dist/tags/handlers/remove-tag.write.js +3 -3
  186. package/dist/tenant-handover/handlers/claim.write.js +2 -2
  187. package/dist/tenant-lifecycle/feature.js +1 -1
  188. package/dist/tenant-lifecycle/handlers/cancel-destruction.write.js +4 -1
  189. package/dist/tenant-lifecycle/handlers/request-destruction.write.js +2 -1
  190. package/dist/tier-engine/feature.js +1 -1
  191. package/dist/tier-engine/handlers/get-tenant-tier.query.js +2 -1
  192. package/dist/tier-engine/handlers/set-tenant-tier.write.js +2 -1
  193. package/dist/user/handlers/detail.query.js +1 -0
  194. package/dist/user/handlers/list.query.js +1 -0
  195. package/dist/user-data-rights/feature.js +2 -2
  196. package/dist/user-data-rights/handlers/cancel-deletion.write.js +2 -1
  197. package/dist/user-data-rights/handlers/confirm-deletion-by-token.write.js +1 -1
  198. package/dist/user-data-rights/handlers/download-attempt-list.query.js +1 -0
  199. package/dist/user-data-rights/handlers/download-by-job.query.js +2 -1
  200. package/dist/user-data-rights/handlers/download-by-token.query.js +2 -1
  201. package/dist/user-data-rights/handlers/export-status.query.js +2 -1
  202. package/dist/user-data-rights/handlers/lift-restriction.write.js +3 -4
  203. package/dist/user-data-rights/handlers/my-audit-log.query.js +2 -1
  204. package/dist/user-data-rights/handlers/request-deletion-by-email.write.js +2 -1
  205. package/dist/user-data-rights/handlers/request-deletion.write.js +1 -1
  206. package/dist/user-data-rights/handlers/request-export.write.js +2 -1
  207. package/dist/user-data-rights/handlers/restrict-account.write.js +2 -2
  208. package/dist/user-data-rights/handlers/run-forget-cleanup.write.js +2 -1
  209. package/dist/user-data-rights/lib/update-user-lifecycle.js +1 -1
  210. package/dist/user-data-rights/run-forget-cleanup.js +30 -15
  211. package/dist/workflow-runner/event-subscriber.js +6 -2
  212. package/dist/workflow-runner/event-trigger.js +5 -0
  213. package/dist/workflow-runner/handlers/resume-run.write.js +147 -59
  214. package/dist/workflow-runner/pending-projection.js +1 -0
  215. package/dist/workflow-runner/runner.d.ts +7 -1
  216. package/dist/workflow-runner/runner.js +1 -1
  217. package/dist/workflow-runner/tables.js +8 -1
  218. package/package.json +41 -9
  219. package/src/agent-tools/changes.json +14 -0
  220. package/src/auth-email-password/changes.json +7 -0
  221. package/src/billing-foundation/changes.json +83 -0
  222. package/src/cap-counter/changes.json +7 -0
  223. package/src/cap-overview/changes.json +14 -0
  224. package/src/config/changes.json +7 -0
  225. package/src/crypto-shredding/changes.json +13 -0
  226. package/src/custom-fields/changes.json +7 -0
  227. package/src/delivery/changes.json +14 -0
  228. package/src/inbound-mail-foundation/changes.json +7 -0
  229. package/src/notes-history/changes.json +7 -0
  230. package/src/renderer-simple/changes.json +9 -1
  231. package/src/subscription-mollie/changes.json +13 -0
  232. package/src/subscription-stripe/changes.json +13 -0
  233. package/src/tags/changes.json +7 -0
  234. package/src/user-data-rights/changes.json +6 -0
  235. package/src/workflow-runner/changes.json +7 -0
@@ -1,6 +1,6 @@
1
1
  import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import { buildEntityTable, decodeCursor, encodeCursor } from "@cosmicdrift/kumiko-framework/db";
3
- import { definePagedQueryHandler, MAX_LIST_LIMIT } from "@cosmicdrift/kumiko-framework/engine";
3
+ import { definePagedQueryHandler, MAX_LIST_LIMIT, } from "@cosmicdrift/kumiko-framework/engine";
4
4
  import { InternalError, ValidationError } from "@cosmicdrift/kumiko-framework/errors";
5
5
  import * as z from "zod";
6
6
  import { subscriptionsProjectionTable } from "../../billing-foundation/index.js";
@@ -21,11 +21,16 @@ function billingLabel(subscription, tierSource) {
21
21
  }
22
22
  const FILTER_OP = z.enum(["eq", "ne", "lt", "gt", "in"]);
23
23
  const SORTABLE_FIELDS = ["name", "tier", "billing"];
24
- function capTierKey(capId, tier) {
25
- return JSON.stringify([capId, tier]);
24
+ function tenantCapKey(tenantId, capId) {
25
+ return JSON.stringify([tenantId, capId]);
26
26
  }
27
- async function resolveLimitsByCapAndTier(caps, tiers, context) {
28
- const entries = await Promise.all(caps.flatMap((cap) => [...tiers].map(async (tier) => [capTierKey(cap.id, tier), await cap.limit(tier, context)])));
27
+ // One config accessor per tenant: limits may read tenant-scoped config keys, so
28
+ // a limit shared across tenants of the same tier would show the wrong value.
29
+ async function resolveLimitsByTenantAndCap(caps, tenants, configFor) {
30
+ const entries = await Promise.all(tenants.flatMap(({ tenantId, tier }) => {
31
+ const context = { config: configFor?.(tenantId) };
32
+ return caps.map(async (cap) => [tenantCapKey(tenantId, cap.id), await cap.limit(tier, context)]);
33
+ }));
29
34
  return new Map(entries);
30
35
  }
31
36
  function isSortableField(field) {
@@ -183,7 +188,7 @@ export function createTenantCapsListQuery(caps, listCaps) {
183
188
  const perTenantEntries = await Promise.all(pageTenantIds.map(async (tenantId) => [tenantId, await cap.usage(db, tenantId)]));
184
189
  usageByCap.set(cap.id, new Map(perTenantEntries));
185
190
  }));
186
- const limitByCapAndTier = await resolveLimitsByCapAndTier(listedCaps, new Set(page.map((row) => row.tier)), { config: ctx.config });
191
+ const limitByTenantAndCap = await resolveLimitsByTenantAndCap(listedCaps, page, ctx.configFor);
187
192
  const rows = page.map((row) => {
188
193
  const capFields = {};
189
194
  for (const cap of listedCaps) {
@@ -191,7 +196,7 @@ export function createTenantCapsListQuery(caps, listCaps) {
191
196
  // only an explicit `null` value means "not measured".
192
197
  const rawUsed = usageByCap.get(cap.id)?.get(row.tenantId);
193
198
  const used = rawUsed === undefined ? 0 : rawUsed;
194
- const limit = limitByCapAndTier.get(capTierKey(cap.id, row.tier)) ?? null;
199
+ const limit = limitByTenantAndCap.get(tenantCapKey(row.tenantId, cap.id)) ?? null;
195
200
  capFields[capFieldName(cap.id)] =
196
201
  used === null
197
202
  ? { used: null, limit, fraction: 0 }
@@ -1,6 +1,6 @@
1
- export { MY_CAPS_ACCESS_ROLES } from "./access.js";
1
+ export { DEFAULT_CAP_USAGE_ROLES } from "./access.js";
2
2
  export { CAP_CARDS_PANEL_COMPONENT, CAP_OVERVIEW_FEATURE, CAP_USAGE_CELL_COMPONENT, CapOverviewQueries, capFieldName, MY_CAPS_SCREEN_ID, PLATFORM_TENANT_CAPS_SCREEN_ID, TENANT_CAP_LIST_SCREEN_ID, } from "./constants.js";
3
3
  export { type CreateCapOverviewOptions, createCapOverviewFeature } from "./feature.js";
4
- export { createTenantCapListScreen, myCapsScreen, platformTenantCapsScreen, } from "./screens.js";
4
+ export { createMyCapsScreen, createTenantCapListScreen, platformTenantCapsScreen, } from "./screens.js";
5
5
  export type { CapSpec, CapUsage, CapUsageTone, CapUsageWithMeta } from "./types.js";
6
6
  export { computeFraction, computeTone } from "./usage-math.js";
@@ -1,6 +1,6 @@
1
1
  // Public API of the cap-overview bundled-feature.
2
- export { MY_CAPS_ACCESS_ROLES } from "./access.js";
2
+ export { DEFAULT_CAP_USAGE_ROLES } from "./access.js";
3
3
  export { CAP_CARDS_PANEL_COMPONENT, CAP_OVERVIEW_FEATURE, CAP_USAGE_CELL_COMPONENT, CapOverviewQueries, capFieldName, MY_CAPS_SCREEN_ID, PLATFORM_TENANT_CAPS_SCREEN_ID, TENANT_CAP_LIST_SCREEN_ID, } from "./constants.js";
4
4
  export { createCapOverviewFeature } from "./feature.js";
5
- export { createTenantCapListScreen, myCapsScreen, platformTenantCapsScreen, } from "./screens.js";
5
+ export { createMyCapsScreen, createTenantCapListScreen, platformTenantCapsScreen, } from "./screens.js";
6
6
  export { computeFraction, computeTone } from "./usage-math.js";
@@ -1,5 +1,5 @@
1
1
  import type { ScreenDefinition } from "@cosmicdrift/kumiko-framework/engine";
2
2
  import type { CapSpec } from "./types.js";
3
3
  export declare function createTenantCapListScreen(caps: readonly CapSpec[], listCaps: readonly string[], tiers?: readonly string[]): ScreenDefinition;
4
- export declare const myCapsScreen: ScreenDefinition;
4
+ export declare function createMyCapsScreen(usageRoles: readonly string[]): ScreenDefinition;
5
5
  export declare const platformTenantCapsScreen: ScreenDefinition;
@@ -1,4 +1,3 @@
1
- import { MY_CAPS_ACCESS_ROLES } from "./access.js";
2
1
  import { CAP_CARDS_PANEL_COMPONENT, CAP_USAGE_CELL_COMPONENT, CapOverviewQueries, capFieldName, MY_CAPS_SCREEN_ID, PLATFORM_TENANT_CAPS_SCREEN_ID, TENANT_CAP_LIST_SCREEN_ID, } from "./constants.js";
3
2
  // `tiers` has no room for a separate per-tier display label (it's just
4
3
  // `readonly string[]`) — tier-engine itself has no enumerated tier list to
@@ -55,21 +54,23 @@ export function createTenantCapListScreen(caps, listCaps, tiers) {
55
54
  dormant: true,
56
55
  };
57
56
  }
58
- export const myCapsScreen = {
59
- id: MY_CAPS_SCREEN_ID,
60
- type: "dashboard",
61
- panels: [
62
- {
63
- kind: "custom",
64
- id: "cap-cards",
65
- component: { react: { __component: CAP_CARDS_PANEL_COMPONENT } },
66
- },
67
- ],
68
- access: { roles: MY_CAPS_ACCESS_ROLES },
69
- // No nav wiring yet (see feature.ts header) — no nav area to resolve in
70
- // isolation.
71
- dormant: true,
72
- };
57
+ export function createMyCapsScreen(usageRoles) {
58
+ return {
59
+ id: MY_CAPS_SCREEN_ID,
60
+ type: "dashboard",
61
+ panels: [
62
+ {
63
+ kind: "custom",
64
+ id: "cap-cards",
65
+ component: { react: { __component: CAP_CARDS_PANEL_COMPONENT } },
66
+ },
67
+ ],
68
+ access: { roles: usageRoles },
69
+ // No nav wiring yet (see feature.ts header) — no nav area to resolve in
70
+ // isolation.
71
+ dormant: true,
72
+ };
73
+ }
73
74
  export const platformTenantCapsScreen = {
74
75
  id: PLATFORM_TENANT_CAPS_SCREEN_ID,
75
76
  type: "dashboard",
@@ -0,0 +1,6 @@
1
+ import { type ChatWebhookChannelOptions, type DeliveryChannel } from "../delivery/index.js";
2
+ export type DiscordChannelOptions = ChatWebhookChannelOptions;
3
+ export declare const DISCORD_DEFAULT_ALLOWED_HOSTS: readonly ["discord.com", "discordapp.com"];
4
+ export declare const DISCORD_WEBHOOK_PATH_PREFIX = "/api/webhooks/";
5
+ export declare const DISCORD_CONTENT_MAX_CHARS = 2000;
6
+ export declare function createDiscordChannel(options: DiscordChannelOptions, keyFor: (connection: string) => string): DeliveryChannel;
@@ -0,0 +1,19 @@
1
+ import { chatMessageText, createChatWebhookChannel, truncateChars, } from "../delivery/index.js";
2
+ export const DISCORD_DEFAULT_ALLOWED_HOSTS = ["discord.com", "discordapp.com"];
3
+ export const DISCORD_WEBHOOK_PATH_PREFIX = "/api/webhooks/";
4
+ export const DISCORD_CONTENT_MAX_CHARS = 2000;
5
+ export function createDiscordChannel(options, keyFor) {
6
+ return createChatWebhookChannel({
7
+ name: "discord",
8
+ featureName: "channel-discord",
9
+ defaultAllowedHosts: DISCORD_DEFAULT_ALLOWED_HOSTS,
10
+ requiredPathPrefix: DISCORD_WEBHOOK_PATH_PREFIX,
11
+ keyFor,
12
+ // parse: [] keeps @everyone / @here / role and user mentions in the text inert.
13
+ buildBody: (message) => ({
14
+ content: truncateChars(chatMessageText(message), DISCORD_CONTENT_MAX_CHARS),
15
+ allowed_mentions: { parse: [] },
16
+ }),
17
+ options,
18
+ });
19
+ }
@@ -0,0 +1,3 @@
1
+ import { type FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
2
+ import { type DiscordChannelOptions } from "./discord-channel.js";
3
+ export declare function createChannelDiscordFeature(options?: DiscordChannelOptions): FeatureDefinition;
@@ -0,0 +1,30 @@
1
+ import { chatConnectionNameSchema, DELIVERY_CHANNEL_EXTENSION, } from "@cosmicdrift/kumiko-bundled-features/delivery";
2
+ import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
3
+ import { createDiscordChannel } from "./discord-channel.js";
4
+ export function createChannelDiscordFeature(options = {}) {
5
+ return defineFeature("channel-discord", (r) => {
6
+ r.describe('Posts delivery notifications to a Discord webhook, registered as the `discord` channel in the delivery system. Tenant-owned chat target: reach it only via `ctx.notify(type, { route: { discord: "<connection name>" } })`. The webhook URL is a tenant secret under `channel-discord:webhooks.<connection name>`; only the connection name is logged. Mentions are disabled and content is cut to 2000 characters. Requires `delivery` and `secrets`. Host allowlist (discord.com, discordapp.com, path /api/webhooks/), https-only and the request timeout are app options of `createChannelDiscordFeature(opts)`, never tenant config.');
7
+ r.uiHints({
8
+ displayLabel: "Discord Channel",
9
+ category: "notifications",
10
+ recommended: false,
11
+ });
12
+ r.requires("delivery");
13
+ r.requires("secrets");
14
+ const webhooks = r.secretNamespace("webhooks", {
15
+ label: { en: "Discord webhooks", de: "Discord-Webhooks", es: "Webhooks de Discord" },
16
+ hint: {
17
+ en: 'One secret per connection name; the value is the Discord webhook URL. Address it with route: { discord: "<connection name>" }.',
18
+ de: 'Ein Secret pro Verbindungsname; der Wert ist die Discord-Webhook-URL. Adressierung mit route: { discord: "<Verbindungsname>" }.',
19
+ es: 'Un secreto por nombre de conexión; el valor es la URL del webhook de Discord. Se usa con route: { discord: "<nombre de conexión>" }.',
20
+ },
21
+ scope: "tenant",
22
+ nameSchema: chatConnectionNameSchema,
23
+ });
24
+ const channel = createDiscordChannel(options, webhooks.keyFor);
25
+ r.useExtension(DELIVERY_CHANNEL_EXTENSION, "discord", {
26
+ mode: channel.mode,
27
+ send: channel.send,
28
+ });
29
+ });
30
+ }
@@ -0,0 +1,2 @@
1
+ export { createDiscordChannel, type DiscordChannelOptions } from "./discord-channel.js";
2
+ export { createChannelDiscordFeature } from "./feature.js";
@@ -0,0 +1,2 @@
1
+ export { createDiscordChannel } from "./discord-channel.js";
2
+ export { createChannelDiscordFeature } from "./feature.js";
@@ -0,0 +1,3 @@
1
+ import { type FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
2
+ import { type SlackChannelOptions } from "./slack-channel.js";
3
+ export declare function createChannelSlackFeature(options?: SlackChannelOptions): FeatureDefinition;
@@ -0,0 +1,30 @@
1
+ import { chatConnectionNameSchema, DELIVERY_CHANNEL_EXTENSION, } from "@cosmicdrift/kumiko-bundled-features/delivery";
2
+ import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
3
+ import { createSlackChannel } from "./slack-channel.js";
4
+ export function createChannelSlackFeature(options = {}) {
5
+ return defineFeature("channel-slack", (r) => {
6
+ r.describe('Posts delivery notifications to a Slack incoming webhook, registered as the `slack` channel in the delivery system. Tenant-owned chat target: reach it only via `ctx.notify(type, { route: { slack: "<connection name>" } })`. The webhook URL is a tenant secret under `channel-slack:webhooks.<connection name>`; only the connection name is logged. Requires `delivery` and `secrets`. Host allowlist (hooks.slack.com), https-only and the request timeout are app options of `createChannelSlackFeature(opts)`, never tenant config.');
7
+ r.uiHints({
8
+ displayLabel: "Slack Channel",
9
+ category: "notifications",
10
+ recommended: false,
11
+ });
12
+ r.requires("delivery");
13
+ r.requires("secrets");
14
+ const webhooks = r.secretNamespace("webhooks", {
15
+ label: { en: "Slack webhooks", de: "Slack-Webhooks", es: "Webhooks de Slack" },
16
+ hint: {
17
+ en: 'One secret per connection name; the value is the Slack webhook URL. Address it with route: { slack: "<connection name>" }.',
18
+ de: 'Ein Secret pro Verbindungsname; der Wert ist die Slack-Webhook-URL. Adressierung mit route: { slack: "<Verbindungsname>" }.',
19
+ es: 'Un secreto por nombre de conexión; el valor es la URL del webhook de Slack. Se usa con route: { slack: "<nombre de conexión>" }.',
20
+ },
21
+ scope: "tenant",
22
+ nameSchema: chatConnectionNameSchema,
23
+ });
24
+ const channel = createSlackChannel(options, webhooks.keyFor);
25
+ r.useExtension(DELIVERY_CHANNEL_EXTENSION, "slack", {
26
+ mode: channel.mode,
27
+ send: channel.send,
28
+ });
29
+ });
30
+ }
@@ -0,0 +1,2 @@
1
+ export { createChannelSlackFeature } from "./feature.js";
2
+ export { createSlackChannel, type SlackChannelOptions } from "./slack-channel.js";
@@ -0,0 +1,2 @@
1
+ export { createChannelSlackFeature } from "./feature.js";
2
+ export { createSlackChannel } from "./slack-channel.js";
@@ -0,0 +1,5 @@
1
+ import { type ChatWebhookChannelOptions, type DeliveryChannel } from "../delivery/index.js";
2
+ export type SlackChannelOptions = ChatWebhookChannelOptions;
3
+ export declare const SLACK_DEFAULT_ALLOWED_HOSTS: readonly ["hooks.slack.com"];
4
+ export declare function escapeSlackText(text: string): string;
5
+ export declare function createSlackChannel(options: SlackChannelOptions, keyFor: (connection: string) => string): DeliveryChannel;
@@ -0,0 +1,16 @@
1
+ import { chatMessageText, createChatWebhookChannel, } from "../delivery/index.js";
2
+ export const SLACK_DEFAULT_ALLOWED_HOSTS = ["hooks.slack.com"];
3
+ // Slack mrkdwn treats & < > as control characters (links, @-mentions via <!channel>).
4
+ export function escapeSlackText(text) {
5
+ return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
6
+ }
7
+ export function createSlackChannel(options, keyFor) {
8
+ return createChatWebhookChannel({
9
+ name: "slack",
10
+ featureName: "channel-slack",
11
+ defaultAllowedHosts: SLACK_DEFAULT_ALLOWED_HOSTS,
12
+ keyFor,
13
+ buildBody: (message) => ({ text: escapeSlackText(chatMessageText(message)) }),
14
+ options,
15
+ });
16
+ }
@@ -0,0 +1,3 @@
1
+ import { type FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
2
+ import { type TeamsChannelOptions } from "./teams-channel.js";
3
+ export declare function createChannelTeamsFeature(options?: TeamsChannelOptions): FeatureDefinition;
@@ -0,0 +1,34 @@
1
+ import { chatConnectionNameSchema, DELIVERY_CHANNEL_EXTENSION, } from "@cosmicdrift/kumiko-bundled-features/delivery";
2
+ import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
3
+ import { createTeamsChannel } from "./teams-channel.js";
4
+ export function createChannelTeamsFeature(options = {}) {
5
+ return defineFeature("channel-teams", (r) => {
6
+ r.describe('Posts delivery notifications to a Microsoft Teams webhook (Workflows or connector) as an Adaptive Card, registered as the `teams` channel in the delivery system. Tenant-owned chat target: reach it only via `ctx.notify(type, { route: { teams: "<connection name>" } })`. The webhook URL is a tenant secret under `channel-teams:webhooks.<connection name>`; only the connection name is logged. Requires `delivery` and `secrets`. Host allowlist (.webhook.office.com, .logic.azure.com, .powerplatform.com), https-only and the request timeout are app options of `createChannelTeamsFeature(opts)`, never tenant config.');
7
+ r.uiHints({
8
+ displayLabel: "Microsoft Teams Channel",
9
+ category: "notifications",
10
+ recommended: false,
11
+ });
12
+ r.requires("delivery");
13
+ r.requires("secrets");
14
+ const webhooks = r.secretNamespace("webhooks", {
15
+ label: {
16
+ en: "Microsoft Teams webhooks",
17
+ de: "Microsoft Teams-Webhooks",
18
+ es: "Webhooks de Microsoft Teams",
19
+ },
20
+ hint: {
21
+ en: 'One secret per connection name; the value is the Microsoft Teams webhook URL. Address it with route: { teams: "<connection name>" }.',
22
+ de: 'Ein Secret pro Verbindungsname; der Wert ist die Microsoft Teams-Webhook-URL. Adressierung mit route: { teams: "<Verbindungsname>" }.',
23
+ es: 'Un secreto por nombre de conexión; el valor es la URL del webhook de Microsoft Teams. Se usa con route: { teams: "<nombre de conexión>" }.',
24
+ },
25
+ scope: "tenant",
26
+ nameSchema: chatConnectionNameSchema,
27
+ });
28
+ const channel = createTeamsChannel(options, webhooks.keyFor);
29
+ r.useExtension(DELIVERY_CHANNEL_EXTENSION, "teams", {
30
+ mode: channel.mode,
31
+ send: channel.send,
32
+ });
33
+ });
34
+ }
@@ -0,0 +1,2 @@
1
+ export { createChannelTeamsFeature } from "./feature.js";
2
+ export { createTeamsChannel, type TeamsChannelOptions } from "./teams-channel.js";
@@ -0,0 +1,2 @@
1
+ export { createChannelTeamsFeature } from "./feature.js";
2
+ export { createTeamsChannel } from "./teams-channel.js";
@@ -0,0 +1,5 @@
1
+ import { type ChatWebhookChannelOptions, type DeliveryChannel } from "../delivery/index.js";
2
+ export type TeamsChannelOptions = ChatWebhookChannelOptions;
3
+ export declare const TEAMS_DEFAULT_ALLOWED_HOSTS: readonly [".webhook.office.com", ".logic.azure.com", ".powerplatform.com"];
4
+ export declare function buildTeamsAdaptiveCard(title: string, body: string | undefined): unknown;
5
+ export declare function createTeamsChannel(options: TeamsChannelOptions, keyFor: (connection: string) => string): DeliveryChannel;
@@ -0,0 +1,38 @@
1
+ import { createChatWebhookChannel, } from "../delivery/index.js";
2
+ export const TEAMS_DEFAULT_ALLOWED_HOSTS = [
3
+ ".webhook.office.com",
4
+ ".logic.azure.com",
5
+ ".powerplatform.com",
6
+ ];
7
+ // Text only ever goes into TextBlocks (no markup interpretation of titles/bodies
8
+ // beyond Adaptive Card's own text rendering).
9
+ export function buildTeamsAdaptiveCard(title, body) {
10
+ return {
11
+ type: "message",
12
+ attachments: [
13
+ {
14
+ contentType: "application/vnd.microsoft.card.adaptive",
15
+ contentUrl: null,
16
+ content: {
17
+ $schema: "http://adaptivecards.io/schemas/adaptive-card.json",
18
+ type: "AdaptiveCard",
19
+ version: "1.4",
20
+ body: [
21
+ { type: "TextBlock", text: title, weight: "Bolder", wrap: true },
22
+ ...(body ? [{ type: "TextBlock", text: body, wrap: true }] : []),
23
+ ],
24
+ },
25
+ },
26
+ ],
27
+ };
28
+ }
29
+ export function createTeamsChannel(options, keyFor) {
30
+ return createChatWebhookChannel({
31
+ name: "teams",
32
+ featureName: "channel-teams",
33
+ defaultAllowedHosts: TEAMS_DEFAULT_ALLOWED_HOSTS,
34
+ keyFor,
35
+ buildBody: (message) => buildTeamsAdaptiveCard(message.title, message.body),
36
+ options,
37
+ });
38
+ }
@@ -0,0 +1,3 @@
1
+ import { type FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
2
+ import { type TelegramChannelOptions } from "./telegram-channel.js";
3
+ export declare function createChannelTelegramFeature(options?: TelegramChannelOptions): FeatureDefinition;
@@ -0,0 +1,28 @@
1
+ import { DELIVERY_CHANNEL_EXTENSION } from "@cosmicdrift/kumiko-bundled-features/delivery";
2
+ import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
3
+ import { createTelegramChannel } from "./telegram-channel.js";
4
+ export function createChannelTelegramFeature(options = {}) {
5
+ return defineFeature("channel-telegram", (r) => {
6
+ r.describe('Sends delivery notifications through the Telegram Bot API (`sendMessage`, plain text), registered as the `telegram` channel in the delivery system. Tenant-owned chat target: reach it only via `ctx.notify(type, { route: { telegram: "<chat id or @channel>" } })`. The bot token is the tenant secret `channel-telegram:secret:bot-token`; the token never reaches logs or delivery attempts. Requires `delivery` and `secrets`. The API base URL and request timeout are app options of `createChannelTelegramFeature(opts)`, never tenant config.');
7
+ r.uiHints({
8
+ displayLabel: "Telegram Channel",
9
+ category: "notifications",
10
+ recommended: false,
11
+ });
12
+ r.requires("delivery");
13
+ r.requires("secrets");
14
+ const botToken = r.secret("botToken", {
15
+ label: { en: "Telegram bot token" },
16
+ hint: {
17
+ en: 'Token from @BotFather. Used for every Telegram chat the tenant addresses via route: { telegram: "<chat id>" }.',
18
+ },
19
+ redact: () => "••••••••",
20
+ scope: "tenant",
21
+ });
22
+ const channel = createTelegramChannel(options, botToken.name);
23
+ r.useExtension(DELIVERY_CHANNEL_EXTENSION, "telegram", {
24
+ mode: channel.mode,
25
+ send: channel.send,
26
+ });
27
+ });
28
+ }
@@ -0,0 +1,2 @@
1
+ export { createChannelTelegramFeature } from "./feature.js";
2
+ export { createTelegramChannel, type TelegramChannelOptions } from "./telegram-channel.js";
@@ -0,0 +1,2 @@
1
+ export { createChannelTelegramFeature } from "./feature.js";
2
+ export { createTelegramChannel } from "./telegram-channel.js";
@@ -0,0 +1,8 @@
1
+ import { type DeliveryChannel } from "../delivery/index.js";
2
+ export type TelegramChannelOptions = {
3
+ readonly apiBaseUrl?: string;
4
+ readonly timeoutMs?: number;
5
+ };
6
+ export declare const TELEGRAM_DEFAULT_API_BASE_URL = "https://api.telegram.org";
7
+ export declare const TELEGRAM_TEXT_MAX_CHARS = 4096;
8
+ export declare function createTelegramChannel(options: TelegramChannelOptions, botTokenKey: string): DeliveryChannel;
@@ -0,0 +1,54 @@
1
+ import { SYSTEM_USER_ID } from "@cosmicdrift/kumiko-types/identifiers";
2
+ import { chatMessageText, DEFAULT_CHAT_TIMEOUT_MS, postChatWebhook, toChannelResult, truncateChars, } from "../delivery/index.js";
3
+ export const TELEGRAM_DEFAULT_API_BASE_URL = "https://api.telegram.org";
4
+ export const TELEGRAM_TEXT_MAX_CHARS = 4096;
5
+ // Numeric chat/group id (groups and channels are negative) or a public @channelname.
6
+ const TELEGRAM_CHAT_ID_PATTERN = /^(-?\d{1,20}|@[A-Za-z][A-Za-z0-9_]{3,31})$/;
7
+ // `<bot id>:<secret>`; the token is spliced into the URL path, so only the real shape passes.
8
+ const TELEGRAM_BOT_TOKEN_PATTERN = /^\d{1,20}:[A-Za-z0-9_-]{10,}$/;
9
+ export function createTelegramChannel(options, botTokenKey) {
10
+ const apiBaseUrl = options.apiBaseUrl ?? TELEGRAM_DEFAULT_API_BASE_URL;
11
+ return {
12
+ name: "telegram",
13
+ mode: "queued",
14
+ async send(address, message, ctx) {
15
+ if (!TELEGRAM_CHAT_ID_PATTERN.test(address)) {
16
+ return toChannelResult(address, { ok: false, code: "invalid_address" });
17
+ }
18
+ let token;
19
+ try {
20
+ const revealed = await ctx.secrets?.get(ctx.tenantId, botTokenKey, {
21
+ userId: SYSTEM_USER_ID,
22
+ handlerName: "channel-telegram:send",
23
+ });
24
+ token = revealed?.reveal();
25
+ }
26
+ catch {
27
+ token = undefined;
28
+ }
29
+ if (!token || !TELEGRAM_BOT_TOKEN_PATTERN.test(token)) {
30
+ return toChannelResult(address, { ok: false, code: "missing_credentials" });
31
+ }
32
+ // The base URL is the allowlist: only its own host is reachable.
33
+ let baseHost;
34
+ try {
35
+ baseHost = new URL(apiBaseUrl).hostname;
36
+ }
37
+ catch {
38
+ return toChannelResult(address, { ok: false, code: "host_not_allowed" });
39
+ }
40
+ const result = await postChatWebhook({
41
+ url: `${apiBaseUrl.replace(/\/+$/, "")}/bot${token}/sendMessage`,
42
+ allowedHosts: [baseHost],
43
+ requireHttps: apiBaseUrl.startsWith("https:"),
44
+ timeoutMs: options.timeoutMs ?? DEFAULT_CHAT_TIMEOUT_MS,
45
+ // No parse_mode: Telegram renders the text literally.
46
+ body: {
47
+ chat_id: address,
48
+ text: truncateChars(chatMessageText(message), TELEGRAM_TEXT_MAX_CHARS),
49
+ },
50
+ });
51
+ return toChannelResult(address, result);
52
+ },
53
+ };
54
+ }
@@ -2,7 +2,7 @@ import { requestContext } from "@cosmicdrift/kumiko-framework/api";
2
2
  import { configuredPiiSubjectKms, encryptPiiValueForSubject, } from "@cosmicdrift/kumiko-framework/crypto";
3
3
  import { createEventStoreExecutor } from "@cosmicdrift/kumiko-framework/db";
4
4
  import { ConfigScopes, defineWriteHandler, isEncryptedAtRest, SYSTEM_TENANT_ID, } from "@cosmicdrift/kumiko-framework/engine";
5
- import { InternalError, UnprocessableError, writeFailure, } from "@cosmicdrift/kumiko-framework/errors";
5
+ import { InternalError, isKumikoError, UnprocessableError, writeFailure, } from "@cosmicdrift/kumiko-framework/errors";
6
6
  import * as z from "zod";
7
7
  import { requireConfigEncryption, requireSystemDb } from "../feature.js";
8
8
  import { MASKED } from "../read-redaction.js";
@@ -14,7 +14,7 @@ const executor = createEventStoreExecutor(configValuesTable, configValueEntity,
14
14
  });
15
15
  export const setWrite = defineWriteHandler({
16
16
  name: "set",
17
- description: "Stores a config value for one key at the requested scope (user, tenant or system) after type, bounds and pattern validation, encrypting it when the key declares that; use it to change a setting.",
17
+ description: "Stores a config value for one key at the requested scope (user, tenant or system) after type, bounds, pattern and the key's own validator, encrypting it when the key declares that; use it to change a setting.",
18
18
  schema: z.object({
19
19
  key: z.string(),
20
20
  value: z.union([z.string(), z.number(), z.boolean()]),
@@ -67,6 +67,22 @@ export const setWrite = defineWriteHandler({
67
67
  const patternError = validatePattern(event.payload.value, keyDef);
68
68
  if (patternError)
69
69
  return writeFailure(patternError);
70
+ if (keyDef.validate) {
71
+ try {
72
+ await keyDef.validate(event.payload.value, {
73
+ key: event.payload.key,
74
+ scope,
75
+ tenantId,
76
+ userId,
77
+ db,
78
+ });
79
+ }
80
+ catch (error) {
81
+ if (isKumikoError(error))
82
+ return writeFailure(error);
83
+ throw error;
84
+ }
85
+ }
70
86
  // The batch idempotency cache stores this result and only masks entity
71
87
  // writeOnly fields, so an at-rest-encrypted value must not be echoed.
72
88
  const echoedValue = isEncryptedAtRest(keyDef) ? MASKED : event.payload.value;
@@ -8,8 +8,9 @@ export function createCryptoShreddingFeature() {
8
8
  displayLabel: "Crypto-Shredding",
9
9
  category: "compliance",
10
10
  });
11
- // `reason` is operator free text and may name the subject; encrypting it under
12
- // the subject key is pointless (that key is being erased) — see #2776.
11
+ // `reason` is operator free text and may name the subject, yet stays plaintext: the forget
12
+ // erases the subject key before the audit append, so a forget of the actor's own tenant
13
+ // would fail to seal the reason under that tenant's key.
13
14
  r.defineEvent("subject-forgotten", subjectForgottenSchema, { piiFields: "none" });
14
15
  r.defineEvent("forget-denied", subjectForgetDeniedSchema, { piiFields: "none" });
15
16
  r.writeHandler(forgetSubjectWrite);
@@ -21,7 +21,7 @@ export declare const subjectForgottenSchema: z.ZodObject<{
21
21
  forgottenBy: z.ZodString;
22
22
  }, z.core.$strip>;
23
23
  export declare const subjectForgetDeniedSchema: z.ZodObject<{
24
- subjectKeyDigest: z.ZodString;
24
+ subjectKeyDigest: z.ZodOptional<z.ZodString>;
25
25
  subjectKind: z.ZodEnum<{
26
26
  record: "record";
27
27
  tenant: "tenant";