@cosmicdrift/kumiko-bundled-features 0.333.0 → 0.335.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 (277) hide show
  1. package/dist/admin-shell/overview-panels.js +2 -2
  2. package/dist/agent-tools/agent-doc-lint.d.ts +1 -0
  3. package/dist/agent-tools/agent-doc-lint.js +33 -3
  4. package/dist/agent-tools/agent-manifest.js +47 -6
  5. package/dist/agent-tools/filter-ops.d.ts +2 -0
  6. package/dist/agent-tools/filter-ops.js +4 -0
  7. package/dist/agent-tools/tool-catalog.js +1 -4
  8. package/dist/agent-tools/tool-dispatch.js +1 -4
  9. package/dist/audit/constants.d.ts +1 -1
  10. package/dist/audit/constants.js +1 -1
  11. package/dist/audit/handlers/list.query.js +4 -3
  12. package/dist/auth-email-password/handlers/change-password.write.js +2 -0
  13. package/dist/auth-email-password/handlers/invite-create.write.js +12 -2
  14. package/dist/auth-email-password/handlers/login.write.js +2 -0
  15. package/dist/auth-email-password/handlers/self-registration-status.query.js +1 -1
  16. package/dist/auth-email-password/i18n.js +2 -0
  17. package/dist/auth-email-password/web/auth-client.d.ts +1 -0
  18. package/dist/auth-email-password/web/auth-client.js +1 -1
  19. package/dist/auth-email-password/web/auth-gate.js +6 -8
  20. package/dist/auth-email-password/web/index.d.ts +1 -1
  21. package/dist/auth-email-password/web/index.js +1 -1
  22. package/dist/auth-email-password/web/invite-accept-screen.js +16 -12
  23. package/dist/auth-email-password/web/login-screen.js +1 -1
  24. package/dist/auth-email-password/web/session-bootstrap-error.d.ts +5 -1
  25. package/dist/auth-email-password/web/session-bootstrap-error.js +2 -2
  26. package/dist/auth-email-password/web/session.d.ts +4 -0
  27. package/dist/auth-email-password/web/session.js +15 -0
  28. package/dist/auth-mfa/feature.js +3 -2
  29. package/dist/auth-mfa/handlers/enable-confirm-preauth.write.js +2 -2
  30. package/dist/auth-mfa/handlers/verify.write.js +2 -0
  31. package/dist/auth-mfa/hmac-token-codec.js +3 -0
  32. package/dist/auth-mfa/mfa-challenge-token.js +4 -0
  33. package/dist/auth-mfa/mfa-preauth-setup-token.js +3 -0
  34. package/dist/auth-mfa/schema/user-mfa.d.ts +6 -0
  35. package/dist/auth-mfa/web/mfa-client.d.ts +1 -1
  36. package/dist/auth-mfa/web/mfa-client.js +4 -10
  37. package/dist/billing-foundation/billing-info-query.js +1 -1
  38. package/dist/billing-foundation/checkout-core.d.ts +5 -5
  39. package/dist/billing-foundation/checkout-core.js +10 -6
  40. package/dist/billing-foundation/entities.d.ts +8 -0
  41. package/dist/billing-foundation/handlers/switch-plan.write.js +5 -8
  42. package/dist/billing-foundation/projection.d.ts +8 -0
  43. package/dist/cap-counter/book-cap-usage.js +15 -5
  44. package/dist/cap-counter/enforce-cap.js +9 -3
  45. package/dist/cap-counter/entity.d.ts +1 -0
  46. package/dist/cap-counter/feature.js +3 -2
  47. package/dist/cap-counter/i18n.d.ts +1 -0
  48. package/dist/cap-counter/i18n.js +6 -5
  49. package/dist/cap-overview/handlers/caps-usage.query.js +9 -16
  50. package/dist/cap-overview/handlers/tenant-caps-list.query.js +41 -15
  51. package/dist/cap-overview/i18n.js +5 -0
  52. package/dist/cap-overview/web/cap-cards-panel.js +4 -1
  53. package/dist/cap-overview/web/cap-usage-bar.js +1 -1
  54. package/dist/channel-email/email-channel.js +12 -1
  55. package/dist/channel-in-app/tables.js +2 -6
  56. package/dist/compliance-profiles/handlers/sub-processors.query.js +1 -1
  57. package/dist/compliance-profiles/schema/profile-selection.d.ts +2 -2
  58. package/dist/config/db/queries/resolver.js +3 -1
  59. package/dist/config/handlers/set.write.js +8 -4
  60. package/dist/config/table.d.ts +6 -0
  61. package/dist/config/web/i18n.js +13 -4
  62. package/dist/config/write-helpers.d.ts +1 -1
  63. package/dist/config/write-helpers.js +11 -5
  64. package/dist/crypto-shredding/handlers/forget-subject.write.js +15 -8
  65. package/dist/custom-fields/constants.js +1 -4
  66. package/dist/custom-fields/entity.d.ts +4 -0
  67. package/dist/data-retention/resolve-for-tenant.d.ts +2 -3
  68. package/dist/data-retention/run-retention-cleanup.js +100 -44
  69. package/dist/data-retention/schema/tenant-retention-override.d.ts +4 -0
  70. package/dist/delivery/tables.d.ts +6 -0
  71. package/dist/delivery/types.d.ts +2 -1
  72. package/dist/delivery/types.js +7 -0
  73. package/dist/delivery/unsubscribe.js +5 -11
  74. package/dist/derivatives-sharp/render.js +27 -9
  75. package/dist/document-ingest-foundation/entity.d.ts +4 -0
  76. package/dist/document-ingest-foundation/feature.js +5 -5
  77. package/dist/document-ingest-foundation/providers.d.ts +1 -0
  78. package/dist/document-ingest-foundation/providers.js +6 -0
  79. package/dist/document-ingest-foundation/write-document-extract.d.ts +2 -2
  80. package/dist/document-ingest-foundation/write-document-extract.js +10 -0
  81. package/dist/feature-toggles/global-feature-state-table.js +2 -7
  82. package/dist/feature-toggles/toggle-sync-signal.d.ts +2 -1
  83. package/dist/feature-toggles/toggle-sync-signal.js +3 -2
  84. package/dist/file-provider-s3/feature.js +1 -1
  85. package/dist/files-tenant-data/index.js +3 -2
  86. package/dist/folders/entity.d.ts +8 -0
  87. package/dist/folders/handlers/clear-folder.write.js +4 -11
  88. package/dist/folders/handlers/set-folder.write.js +4 -10
  89. package/dist/form-draft/entity.d.ts +2 -0
  90. package/dist/form-draft/feature.js +3 -2
  91. package/dist/form-draft/handlers/discard.write.js +7 -8
  92. package/dist/form-draft/handlers/get.query.js +7 -1
  93. package/dist/foundation-shared/host-allowlist.d.ts +4 -0
  94. package/dist/foundation-shared/host-allowlist.js +14 -0
  95. package/dist/foundation-shared/index.d.ts +1 -0
  96. package/dist/foundation-shared/index.js +1 -0
  97. package/dist/foundation-shared/mail-host-policy.d.ts +0 -2
  98. package/dist/foundation-shared/mail-host-policy.js +3 -13
  99. package/dist/inbound-mail-foundation/connect-routes.js +7 -5
  100. package/dist/inbound-mail-foundation/entities.d.ts +25 -0
  101. package/dist/inbound-mail-foundation/projection.d.ts +20 -0
  102. package/dist/inbound-mail-foundation/watch-supervisor.js +47 -43
  103. package/dist/jobs/handlers/list.query.d.ts +7 -2
  104. package/dist/jobs/handlers/list.query.js +9 -2
  105. package/dist/jobs/handlers/retry.write.js +3 -2
  106. package/dist/jobs/handlers/tenant-failures.query.js +3 -9
  107. package/dist/jobs/job-run-logger.js +18 -2
  108. package/dist/jobs/job-run-table.d.ts +6 -0
  109. package/dist/jobs/tenant-job-failure-table.js +2 -6
  110. package/dist/ledger/entity.d.ts +16 -3
  111. package/dist/ledger/handlers/confirm-schedule-period.write.js +2 -2
  112. package/dist/ledger/handlers/reverse-transaction.write.js +2 -2
  113. package/dist/ledger/subject-reference.d.ts +4 -0
  114. package/dist/ledger/subject-reference.js +7 -0
  115. package/dist/legal-pages/feature.js +8 -1
  116. package/dist/managed-pages/handlers/branding.query.js +1 -1
  117. package/dist/managed-pages/handlers/by-slug.query.js +1 -1
  118. package/dist/managed-pages/handlers/by-tenant-published.query.js +1 -1
  119. package/dist/managed-pages/table.d.ts +12 -0
  120. package/dist/metrics/compute.js +7 -1
  121. package/dist/metrics/window.js +5 -0
  122. package/dist/notes-history/entity.d.ts +12 -0
  123. package/dist/notes-history/handlers/add-note.write.js +4 -9
  124. package/dist/notes-history-user-data/hooks.js +8 -1
  125. package/dist/personal-access-tokens/constants.d.ts +1 -0
  126. package/dist/personal-access-tokens/constants.js +1 -0
  127. package/dist/personal-access-tokens/feature.js +2 -2
  128. package/dist/personal-access-tokens/handlers/create.write.js +2 -2
  129. package/dist/personal-access-tokens/handlers/revoke.write.js +2 -2
  130. package/dist/personal-access-tokens/i18n.js +12 -13
  131. package/dist/personal-access-tokens/schema/api-token.d.ts +6 -0
  132. package/dist/personal-access-tokens/schema/api-token.js +2 -1
  133. package/dist/personal-access-tokens/screens.js +5 -1
  134. package/dist/rate-limiting/feature.js +2 -0
  135. package/dist/rate-limiting/handlers/bucket-access.js +15 -7
  136. package/dist/rate-limiting/i18n.d.ts +10 -0
  137. package/dist/rate-limiting/i18n.js +10 -0
  138. package/dist/secrets/feature.js +3 -2
  139. package/dist/secrets/table.d.ts +1 -0
  140. package/dist/seo/handlers/seo-config.query.js +1 -1
  141. package/dist/sessions/constants.d.ts +1 -0
  142. package/dist/sessions/constants.js +1 -0
  143. package/dist/sessions/feature.js +5 -3
  144. package/dist/sessions/handlers/revoke-all-for-user.write.d.ts +1 -0
  145. package/dist/sessions/handlers/revoke-all-for-user.write.js +12 -2
  146. package/dist/sessions/handlers/revoke.write.js +2 -2
  147. package/dist/sessions/schema/user-session.d.ts +4 -0
  148. package/dist/sessions/schema/user-session.js +2 -1
  149. package/dist/sessions/screens.js +5 -1
  150. package/dist/sessions/session-callbacks.js +2 -2
  151. package/dist/shared/index.d.ts +1 -1
  152. package/dist/shared/index.js +1 -1
  153. package/dist/shared/lockout-counter.js +1 -1
  154. package/dist/shared/parent-visibility.d.ts +5 -0
  155. package/dist/shared/parent-visibility.js +17 -1
  156. package/dist/shared/row-bound-grant.js +20 -9
  157. package/dist/shared/single-use-token-store.js +5 -13
  158. package/dist/step-dispatcher/webhook-runner.d.ts +0 -2
  159. package/dist/step-dispatcher/webhook-runner.js +18 -17
  160. package/dist/subscription-mollie/plugin-methods.d.ts +3 -2
  161. package/dist/subscription-mollie/plugin-methods.js +7 -1
  162. package/dist/subscription-mollie/verify-webhook.js +6 -0
  163. package/dist/subscription-stripe/feature.js +3 -3
  164. package/dist/subscription-stripe/plugin-methods.js +25 -6
  165. package/dist/subscription-stripe/verify-webhook.js +13 -6
  166. package/dist/tags/entity.d.ts +9 -0
  167. package/dist/tags/handlers/assign-tag.write.js +4 -10
  168. package/dist/tags/handlers/remove-tag.write.js +4 -11
  169. package/dist/template-resolver/api.d.ts +2 -1
  170. package/dist/template-resolver/api.js +1 -1
  171. package/dist/template-resolver/handlers/by-slug.query.d.ts +1 -1
  172. package/dist/template-resolver/handlers/by-slug.query.js +2 -2
  173. package/dist/template-resolver/handlers/by-tenant.query.d.ts +3 -2
  174. package/dist/template-resolver/handlers/by-tenant.query.js +2 -2
  175. package/dist/template-resolver/handlers/collection-item.query.d.ts +1 -1
  176. package/dist/template-resolver/handlers/collection-list.query.d.ts +1 -1
  177. package/dist/template-resolver/handlers/collection-shared.d.ts +3 -2
  178. package/dist/template-resolver/handlers/collection-shared.js +1 -1
  179. package/dist/template-resolver/handlers/find-by-id.query.d.ts +1 -1
  180. package/dist/template-resolver/handlers/find-by-id.query.js +1 -1
  181. package/dist/template-resolver/handlers/list.query.d.ts +1 -1
  182. package/dist/template-resolver/handlers/list.query.js +1 -1
  183. package/dist/template-resolver/seeding.js +41 -39
  184. package/dist/template-resolver/table.d.ts +26 -13
  185. package/dist/template-resolver/testing.js +2 -2
  186. package/dist/template-resolver/user-content-table.d.ts +16 -9
  187. package/dist/template-resolver/web/client-plugin.d.ts +1 -1
  188. package/dist/template-resolver-user-data/hooks.js +1 -1
  189. package/dist/tenant/cancel-pending-invitation.d.ts +8 -0
  190. package/dist/tenant/cancel-pending-invitation.js +22 -0
  191. package/dist/tenant/feature.js +4 -2
  192. package/dist/tenant/handlers/cancel-invitation.write.js +6 -21
  193. package/dist/tenant/handlers/member-directory.query.d.ts +1 -0
  194. package/dist/tenant/handlers/member-directory.query.js +17 -4
  195. package/dist/tenant/handlers/remove-member.write.js +9 -14
  196. package/dist/tenant/handlers/team-list.query.js +2 -2
  197. package/dist/tenant/handlers/tenant-directory.query.d.ts +1 -0
  198. package/dist/tenant/handlers/tenant-directory.query.js +13 -3
  199. package/dist/tenant/handlers/update-member-roles.write.d.ts +1 -1
  200. package/dist/tenant/handlers/update-member-roles.write.js +28 -12
  201. package/dist/tenant/i18n.js +4 -0
  202. package/dist/tenant/index.d.ts +1 -1
  203. package/dist/tenant/index.js +1 -1
  204. package/dist/tenant/invitation-table.d.ts +8 -2
  205. package/dist/tenant/last-tenant-admin.d.ts +1 -1
  206. package/dist/tenant/last-tenant-admin.js +5 -3
  207. package/dist/tenant/membership-table.d.ts +4 -0
  208. package/dist/tenant/schema/tenant.d.ts +8 -2
  209. package/dist/tenant/seeding.d.ts +4 -0
  210. package/dist/tenant/seeding.js +9 -2
  211. package/dist/tenant-lifecycle/boot-checks.d.ts +1 -0
  212. package/dist/tenant-lifecycle/boot-checks.js +27 -1
  213. package/dist/tenant-lifecycle/feature.js +2 -1
  214. package/dist/tenant-lifecycle/stages.js +2 -2
  215. package/dist/tenant-settings/feature.js +9 -6
  216. package/dist/tier-engine/entity.d.ts +2 -0
  217. package/dist/tier-engine/feature.js +3 -2
  218. package/dist/tier-engine/tier-resolver.d.ts +2 -0
  219. package/dist/user/command-schemas.d.ts +2 -2
  220. package/dist/user/handlers/create.write.d.ts +1 -1
  221. package/dist/user/handlers/create.write.js +8 -2
  222. package/dist/user/handlers/update.write.d.ts +1 -1
  223. package/dist/user/handlers/update.write.js +8 -2
  224. package/dist/user/schema/user.d.ts +14 -2
  225. package/dist/user-data-rights/db/queries/export-jobs.d.ts +2 -0
  226. package/dist/user-data-rights/feature.js +22 -9
  227. package/dist/user-data-rights/handlers/confirm-deletion-by-token.write.js +4 -7
  228. package/dist/user-data-rights/handlers/deletion-grace-period.d.ts +3 -2
  229. package/dist/user-data-rights/handlers/deletion-grace-period.js +30 -8
  230. package/dist/user-data-rights/handlers/download-by-token.query.js +1 -1
  231. package/dist/user-data-rights/handlers/request-deletion-by-email.write.js +1 -1
  232. package/dist/user-data-rights/handlers/request-deletion.write.js +3 -6
  233. package/dist/user-data-rights/lib/update-user-lifecycle.js +7 -14
  234. package/dist/user-data-rights/run-export-jobs.d.ts +1 -0
  235. package/dist/user-data-rights/run-export-jobs.js +74 -41
  236. package/dist/user-data-rights/run-forget-cleanup.js +2 -2
  237. package/dist/user-data-rights/run-user-export.js +8 -4
  238. package/dist/user-data-rights/schema/download-attempt.d.ts +14 -0
  239. package/dist/user-data-rights/schema/download-token.d.ts +8 -0
  240. package/dist/user-data-rights/schema/export-job.d.ts +8 -2
  241. package/dist/user-data-rights-defaults/hooks/file-ref.userdata-hook.js +4 -1
  242. package/dist/user-profile/feature.js +6 -1
  243. package/dist/user-profile/handlers/change-email.write.js +2 -0
  244. package/dist/workflow-runner/db/queries/due-runs.js +6 -9
  245. package/dist/workflow-runner/event-subscriber.js +29 -9
  246. package/dist/workflow-runner/handlers/resume-run.write.js +8 -5
  247. package/dist/workflow-runner/pending-projection.js +8 -1
  248. package/package.json +11 -9
  249. package/src/agent-tools/changes.json +12 -0
  250. package/src/auth-email-password/changes.json +18 -0
  251. package/src/auth-mfa/changes.json +6 -0
  252. package/src/billing-foundation/changes.json +7 -0
  253. package/src/cap-counter/changes.json +7 -0
  254. package/src/cap-overview/changes.json +12 -0
  255. package/src/channel-email/changes.json +6 -0
  256. package/src/config/changes.json +6 -0
  257. package/src/crypto-shredding/changes.json +6 -0
  258. package/src/data-retention/changes.json +12 -0
  259. package/src/delivery/changes.json +7 -0
  260. package/src/derivatives-sharp/changes.json +11 -0
  261. package/src/document-ingest-foundation/changes.json +12 -0
  262. package/src/inbound-mail-foundation/changes.json +12 -0
  263. package/src/jobs/changes.json +13 -0
  264. package/src/legal-pages/changes.json +8 -1
  265. package/src/managed-pages/changes.json +5 -0
  266. package/src/metrics/changes.json +6 -0
  267. package/src/rate-limiting/changes.json +5 -0
  268. package/src/secrets/changes.json +7 -0
  269. package/src/sessions/changes.json +5 -0
  270. package/src/shared/changes.json +6 -0
  271. package/src/step-dispatcher/changes.json +5 -0
  272. package/src/subscription-mollie/changes.json +7 -0
  273. package/src/subscription-stripe/changes.json +11 -0
  274. package/src/template-resolver/changes.json +7 -0
  275. package/src/tenant/changes.json +29 -0
  276. package/src/user-data-rights/changes.json +22 -0
  277. package/src/workflow-runner/changes.json +6 -0
@@ -1,9 +1,9 @@
1
- import { createSystemUser, defineWriteHandler, } from "@cosmicdrift/kumiko-framework/engine";
1
+ import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
2
2
  import { UnprocessableError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
3
3
  import * as z from "zod";
4
4
  import { USER_STATUS, userTable } from "../../user/index.js";
5
5
  import { redeemDeletionToken } from "../deletion-token.js";
6
- import { startDeletionGracePeriod } from "./deletion-grace-period.js";
6
+ import { resolveGracePeriod, startDeletionGracePeriod } from "./deletion-grace-period.js";
7
7
  // Generischer 422 für jeden Token-Fehlerpfad (malformed / bad_signature /
8
8
  // expired / kein Secret) — kein Signal ob ein Token zu einem User gehört.
9
9
  function invalidToken() {
@@ -55,11 +55,8 @@ export function createConfirmDeletionByTokenHandler(opts = {}) {
55
55
  "SYSTEM_TENANT_ID user stream.",
56
56
  },
57
57
  agent: { expose: false },
58
- rateLimit: { per: "ip", limit: 10, windowSeconds: 60 },
58
+ rateLimit: { per: "ip+handler", limit: 10, windowSeconds: 60 },
59
59
  handler: async (event, ctx) => {
60
- // @cast-boundary engine-payload — queryAs returns unknown, narrowed to
61
- // the compliance-profile shape.
62
- const profile = (await ctx.queryAs(createSystemUser(event.user.tenantId), "compliance-profiles:query:for-tenant", {}));
63
60
  let gracePeriodEndIso;
64
61
  // The row's requestId is part of the verify key, so a token from a
65
62
  // cancelled or superseded cycle fails. Every error path ends in the same
@@ -71,7 +68,7 @@ export function createConfirmDeletionByTokenHandler(opts = {}) {
71
68
  secret: opts.deletionTokenSecret,
72
69
  loadPendingRequestId: (userId) => readPendingDeletionRequestId(ctx, userId),
73
70
  commitDeletion: async (userId, requestId) => {
74
- const res = await startDeletionGracePeriod(ctx, userId, profile.profile.userRights.gracePeriod, ctx.db.unsafeRaw("appends the user lifecycle event on the SYSTEM_TENANT_ID user stream"), { pendingDeletionRequestId: requestId });
71
+ const res = await startDeletionGracePeriod(ctx, userId, () => resolveGracePeriod(ctx, event.user.tenantId), ctx.db.unsafeRaw("appends the user lifecycle event on the SYSTEM_TENANT_ID user stream"), { pendingDeletionRequestId: requestId });
75
72
  if (!res.ok)
76
73
  return false;
77
74
  gracePeriodEndIso = res.gracePeriodEnd.toString();
@@ -1,9 +1,10 @@
1
1
  import { type DurationSpec } from "@cosmicdrift/kumiko-framework/compliance";
2
2
  import type { DbRunner } from "@cosmicdrift/kumiko-framework/db";
3
- import type { HandlerContext } from "@cosmicdrift/kumiko-framework/engine";
3
+ import { type HandlerContext } from "@cosmicdrift/kumiko-framework/engine";
4
4
  import { UnprocessableError } from "@cosmicdrift/kumiko-framework/errors";
5
5
  import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
6
6
  type Instant = InstanceType<ReturnType<typeof getTemporal>["Instant"]>;
7
+ export declare function resolveGracePeriod(ctx: HandlerContext, tenantId: string): Promise<DurationSpec>;
7
8
  export type StartGracePeriodResult = {
8
9
  readonly ok: true;
9
10
  readonly gracePeriodEnd: Instant;
@@ -13,5 +14,5 @@ export type StartGracePeriodResult = {
13
14
  readonly ok: false;
14
15
  readonly error: UnprocessableError;
15
16
  };
16
- export declare function startDeletionGracePeriod(ctx: HandlerContext, userId: string, gracePeriod: DurationSpec, lifecycleRunner: DbRunner, additionalExpect?: Readonly<Record<string, string | number | boolean | null>>): Promise<StartGracePeriodResult>;
17
+ export declare function startDeletionGracePeriod(ctx: HandlerContext, userId: string, resolveGracePeriodSpec: () => Promise<DurationSpec>, lifecycleRunner: DbRunner, additionalExpect?: Readonly<Record<string, string | number | boolean | null>>): Promise<StartGracePeriodResult>;
17
18
  export {};
@@ -1,9 +1,21 @@
1
- import { addDurationSpec } from "@cosmicdrift/kumiko-framework/compliance";
1
+ import { addDurationSpec, } from "@cosmicdrift/kumiko-framework/compliance";
2
+ import { createSystemUser, declareEscapeHatch, } from "@cosmicdrift/kumiko-framework/engine";
2
3
  import { UnprocessableError } from "@cosmicdrift/kumiko-framework/errors";
3
4
  import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
4
5
  import { decryptStoredPii } from "../../shared/index.js";
5
6
  import { USER_STATUS, userTable } from "../../user/index.js";
6
7
  import { updateUserLifecycle } from "../lib/update-user-lifecycle.js";
8
+ // Single trust-boundary cast for the compliance-profile lookup: the query
9
+ // handler's declared return type is EffectiveComplianceProfile, but queryAs
10
+ // only hands back unknown. Needs the calling handler's escapeHatch (system
11
+ // queryAs).
12
+ export async function resolveGracePeriod(ctx, tenantId) {
13
+ declareEscapeHatch({
14
+ reason: "reads the tenant compliance profile via queryAs(SYSTEM, ...) to compute the grace period end; the calling user's own identity has no read access to that tenant-config projection",
15
+ });
16
+ const profile = (await ctx.queryAs(createSystemUser(tenantId), "compliance-profiles:query:for-tenant", {})); // @cast-boundary engine-payload
17
+ return profile.profile.userRights.gracePeriod;
18
+ }
7
19
  // Flips an active user to DeletionRequested and sets gracePeriodEnd from the
8
20
  // caller-supplied grace period. Shared between the authenticated
9
21
  // request-deletion path (event.user) and the anonymous confirm-by-token path
@@ -13,7 +25,7 @@ import { updateUserLifecycle } from "../lib/update-user-lifecycle.js";
13
25
  // ctx.db.global(userTable); only the grace period duration is tenant-configured.
14
26
  // That read only supplies email/locale for the caller's notification — it is
15
27
  // NOT the transition's guard. The guard is `expect: { status: Active }` on
16
- // the lifecycle write itself (#3024): the executor re-checks it against a
28
+ // the lifecycle write itself: the executor re-checks it against a
17
29
  // fresh row right before writing, so a concurrent caller that already moved
18
30
  // the user off Active is rejected even though this read saw Active.
19
31
  //
@@ -31,11 +43,13 @@ import { updateUserLifecycle } from "../lib/update-user-lifecycle.js";
31
43
  // true). No-op for the authenticated request-deletion path, which never set
32
44
  // a pendingDeletionRequestId to begin with.
33
45
  //
34
- // `gracePeriod` and `lifecycleRunner` are resolved by the caller: both
35
- // require an escalation (reading the tenant compliance profile, appending to
36
- // the SYSTEM_TENANT_ID user stream) that only the declaring handler's
37
- // escapeHatch covers.
38
- export async function startDeletionGracePeriod(ctx, userId, gracePeriod, lifecycleRunner, additionalExpect) {
46
+ // `resolveGracePeriodSpec` and `lifecycleRunner` are supplied by the caller:
47
+ // both require an escalation (reading the tenant compliance profile,
48
+ // appending to the SYSTEM_TENANT_ID user stream) that only the declaring
49
+ // handler's escapeHatch covers. The grace period is a lazy provider so the
50
+ // privileged compliance-profile query only runs for a user that is still
51
+ // Active, not for replayed tokens or already-requested deletions.
52
+ export async function startDeletionGracePeriod(ctx, userId, resolveGracePeriodSpec, lifecycleRunner, additionalExpect) {
39
53
  const userRow = await ctx.db
40
54
  .global(userTable)
41
55
  .fetchOne({ id: userId });
@@ -47,8 +61,16 @@ export async function startDeletionGracePeriod(ctx, userId, gracePeriod, lifecyc
47
61
  }),
48
62
  };
49
63
  }
64
+ if (userRow["status"] !== USER_STATUS.Active) {
65
+ return {
66
+ ok: false,
67
+ error: new UnprocessableError("user_not_in_active_state", {
68
+ details: { currentStatus: userRow["status"] },
69
+ }),
70
+ };
71
+ }
50
72
  const T = getTemporal();
51
- const gracePeriodEnd = addDurationSpec(T.Now.instant(), gracePeriod);
73
+ const gracePeriodEnd = addDurationSpec(T.Now.instant(), await resolveGracePeriodSpec());
52
74
  const { applied } = await updateUserLifecycle(lifecycleRunner, userId, { status: USER_STATUS.DeletionRequested, gracePeriodEnd, pendingDeletionRequestId: null }, { expect: { status: USER_STATUS.Active, ...additionalExpect } });
53
75
  if (!applied) {
54
76
  return {
@@ -56,7 +56,7 @@ export const downloadByTokenQuery = defineQueryHandler({
56
56
  // 30 attempts/min/IP reicht fuer legitime User (mehrere Klicks bei
57
57
  // Connection-Abbruch); blockiert automatisierte Probing-Loops.
58
58
  // Memory `feedback_security_default_on`.
59
- rateLimit: { per: "ip", limit: 30, windowSeconds: 60 },
59
+ rateLimit: { per: "ip+handler", limit: 30, windowSeconds: 60 },
60
60
  escapeHatch: {
61
61
  reason: DOWNLOAD_BY_TOKEN_REASON,
62
62
  },
@@ -34,7 +34,7 @@ export function createRequestDeletionByEmailHandler(opts = {}) {
34
34
  // Mails a deletion link to any address the caller names.
35
35
  agent: { risk: "high" },
36
36
  // Defense-in-depth gegen Email-Probing auf dem anonymen Endpoint.
37
- rateLimit: { per: "ip", limit: 10, windowSeconds: 60 },
37
+ rateLimit: { per: "ip+handler", limit: 10, windowSeconds: 60 },
38
38
  escapeHatch: {
39
39
  reason: APPEND_LIFECYCLE_EVENT_REASON,
40
40
  },
@@ -1,10 +1,10 @@
1
1
  import { createTransportForTenant } from "@cosmicdrift/kumiko-bundled-features/mail-foundation";
2
- import { createSystemUser, defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
2
+ import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
3
3
  import { writeFailure } from "@cosmicdrift/kumiko-framework/errors";
4
4
  import * as z from "zod";
5
5
  import { USER_STATUS } from "../../user/index.js";
6
6
  import { isMailTransportAvailable, makeDefaultDeletionRequestedEmail, } from "../lib/default-mailers.js";
7
- import { startDeletionGracePeriod } from "./deletion-grace-period.js";
7
+ import { resolveGracePeriod, startDeletionGracePeriod } from "./deletion-grace-period.js";
8
8
  // POST /api/user/request-deletion (S2.U5a) — DSGVO Art. 17 Forget-Antrag.
9
9
  // Flippt status=Active → deletionRequested, setzt gracePeriodEnd aus dem
10
10
  // Compliance-Profile (geteilte Logik: startDeletionGracePeriod). Account-
@@ -29,10 +29,7 @@ export function createRequestDeletionHandler(opts = {}) {
29
29
  description: "Starts the GDPR Art. 17 deletion of the calling user's own account by arming the grace period from the tenant compliance profile and mailing a confirmation, after which only cancel-deletion can stop the erasure.",
30
30
  agent: { risk: "high" },
31
31
  handler: async (event, ctx) => {
32
- // @cast-boundary engine-payload — queryAs returns unknown, narrowed to
33
- // the compliance-profile shape.
34
- const profile = (await ctx.queryAs(createSystemUser(event.user.tenantId), "compliance-profiles:query:for-tenant", {}));
35
- const res = await startDeletionGracePeriod(ctx, event.user.id, profile.profile.userRights.gracePeriod, ctx.db.unsafeRaw("appends the user lifecycle event on the SYSTEM_TENANT_ID user stream"));
32
+ const res = await startDeletionGracePeriod(ctx, event.user.id, () => resolveGracePeriod(ctx, event.user.tenantId), ctx.db.unsafeRaw("appends the user lifecycle event on the SYSTEM_TENANT_ID user stream"));
36
33
  if (!res.ok)
37
34
  return writeFailure(res.error);
38
35
  const { gracePeriodEnd, userEmail, userLocale } = res;
@@ -26,25 +26,18 @@ const userExecutor = createEventStoreExecutor(userTable, userEntity, { entityNam
26
26
  // longer held (precondition_failed) or a concurrent writer won the race on
27
27
  // the same stream version (version_conflict) — both mean "someone else's
28
28
  // transition already landed", the expected outcome for a caller that opted
29
- // into `expect`. Any other failure, or a failure with no `expect` set (every
30
- // pre-#3024 caller), still throws — those callers never checked a return
31
- // value, so silently dropping their write would be silent data loss.
29
+ // into `expect`. Any other failure, or a failure with no `expect` set, still
30
+ // throws — those callers never checked a return value, so silently dropping
31
+ // their write would be silent data loss.
32
32
  export async function updateUserLifecycle(conn, userId, changes, options) {
33
33
  // user ist systemStream (#497): der Executor-Choke-Point addressiert den
34
34
  // Stream immer auf SYSTEM_TENANT_ID — ein Rescope auf die row-tenant_id
35
35
  // waere wirkungslos. "system"-Mode, damit loadById auch Legacy-Rows findet,
36
36
  // deren tenant_id noch vor dem #762-Backfill-Rebuild steht.
37
- // skipOptimisticLock stays true (kept correct, not just carried over): it
38
- // was set from this function's very first version (#494) because every
39
- // caller here is a server-side business transition (restrict/lift-
40
- // restriction/grace-period/cancel/forget), never a client edit-conflict UI
41
- // round-trip — none of them read-then-hold a row version to pass as
42
- // `payload.version`, so the executor's version gate would reject every
43
- // call outright without it (`update()` requires `payload.version` unless
44
- // skipOptimisticLock is set). #3024 replaces the safety that decision gave
45
- // up with the purpose-built `expect:` precondition above, checked fresh at
46
- // write time — the mechanism now genuinely matches the caller's shape
47
- // (a business-field guard) instead of a repurposed version check.
37
+ // skipOptimisticLock: every caller is a server-side business transition
38
+ // (restrict/lift-restriction/grace-period/cancel/forget) that holds no row
39
+ // version to pass as `payload.version`, which update() requires otherwise.
40
+ // Callers needing a guard use `options.expect` instead.
48
41
  const tenantDb = createTenantDb(conn, SYSTEM_TENANT_ID, "system");
49
42
  const result = await userExecutor.update({ id: userId, changes }, createSystemUser(SYSTEM_TENANT_ID), tenantDb, { skipOptimisticLock: true, ...(options?.expect && { expect: options.expect }) });
50
43
  if (result.isSuccess)
@@ -5,6 +5,7 @@ import type { MetricsHandle } from "@cosmicdrift/kumiko-framework/observability"
5
5
  import type { getTemporal } from "@cosmicdrift/kumiko-framework/time";
6
6
  type Instant = InstanceType<ReturnType<typeof getTemporal>["Instant"]>;
7
7
  export declare const EXPORT_CLEANUP_BACKLOG_AGE_METRIC = "export_cleanup_backlog_age";
8
+ export declare const EXPORT_CLEANUP_LAST_RUN_TIMESTAMP_METRIC = "export_cleanup_last_run_timestamp";
8
9
  export declare const tokenCrud: import("@cosmicdrift/kumiko-types/event-store-executor-types").EventStoreExecutor;
9
10
  /**
10
11
  * Notification-Callback fuer den done-Pfad. Pattern matched
@@ -62,6 +62,10 @@ import { generateDownloadToken } from "./token-helpers.js";
62
62
  // No "_seconds"/"_total" suffix: validateMetricName rejects both on a gauge.
63
63
  // Unit lives in the r.metric() `unit: "seconds"` declaration instead.
64
64
  export const EXPORT_CLEANUP_BACKLOG_AGE_METRIC = "export_cleanup_backlog_age";
65
+ // Unix seconds of the last storage-cleanup pass that ran to completion. A
66
+ // gauge keeps its last value in-process (no TTL), so a dead cron does not make
67
+ // the backlog gauge disappear — alert on `time() - this > interval + buffer`.
68
+ export const EXPORT_CLEANUP_LAST_RUN_TIMESTAMP_METRIC = "export_cleanup_last_run_timestamp";
65
69
  const crud = createEventStoreExecutor(exportJobsTable, exportJobEntity, {
66
70
  entityName: "export-job",
67
71
  });
@@ -429,54 +433,71 @@ async function storageCleanupPass(args) {
429
433
  // via selectMany (db/queries/export-jobs.ts).
430
434
  const candidates = await selectExportJobsForStorageCleanup(db, EXPORT_JOB_STATUS.Done, EXPORT_JOB_STATUS.Failed);
431
435
  const cleaned = [];
432
- // Done-status candidates past expiresAt+grace, keyed by that due-instant —
433
- // used below to report how far cleanup has fallen behind if a candidate
434
- // is still standing (delete threw, or the version-checked null-out lost
435
- // a race) once the pass is done. Failed-status jobs have no expiresAt
436
- // (immediate cleanup, no TTL) so they're out of scope for this signal —
437
- // see EXPORT_CLEANUP_BACKLOG_AGE_METRIC's r.metric() description.
436
+ // Candidates past their due-instant, keyed by that instant — used below to
437
+ // report how far cleanup has fallen behind if a candidate is still standing
438
+ // (delete threw, or the version-checked null-out lost a race) once the pass
439
+ // is done. Done jobs are due at expiresAt+grace; Failed jobs (orphaned ZIPs,
440
+ // no TTL) are due at completedAt ?? startedAt, i.e. from the first pass.
438
441
  const overdueDoneCleanupAfterMs = new Map();
439
- for (const c of candidates) {
440
- if (!c.downloadStorageKey)
441
- continue;
442
- // Done-Jobs brauchen expiresAt+grace-Check. Failed-Jobs gehen direkt
443
- // durch (kein User-Pfad → sofort cleanup).
444
- if (c.status === EXPORT_JOB_STATUS.Done) {
445
- if (!c.expiresAt)
442
+ let passCompleted = false;
443
+ try {
444
+ for (const c of candidates) {
445
+ if (!c.downloadStorageKey)
446
446
  continue;
447
- const profile = await resolveProfileForTenant({
448
- db,
449
- tenantId: c.requestedFromTenantId,
450
- });
451
- const cleanupAfter = c.expiresAt.epochMilliseconds +
452
- profile.profile.userRights.exportStorageCleanupGraceHours * 60 * 60 * 1000;
453
- if (now.epochMilliseconds < cleanupAfter)
447
+ // Done jobs need the expiresAt+grace check. Failed jobs pass straight
448
+ // through (no user path, so clean up immediately).
449
+ if (c.status === EXPORT_JOB_STATUS.Done) {
450
+ if (!c.expiresAt)
451
+ continue;
452
+ // Count the candidate as overdue (lower bound, grace unknown) before the
453
+ // throwing profile lookup, so a failing pass still shows a backlog.
454
+ overdueDoneCleanupAfterMs.set(c.id, c.expiresAt.epochMilliseconds);
455
+ const profile = await resolveProfileForTenant({
456
+ db,
457
+ tenantId: c.requestedFromTenantId,
458
+ });
459
+ const cleanupAfter = c.expiresAt.epochMilliseconds +
460
+ profile.profile.userRights.exportStorageCleanupGraceHours * 60 * 60 * 1000;
461
+ if (now.epochMilliseconds < cleanupAfter) {
462
+ overdueDoneCleanupAfterMs.delete(c.id);
463
+ continue;
464
+ }
465
+ overdueDoneCleanupAfterMs.set(c.id, cleanupAfter);
466
+ }
467
+ // Failed-job branch: no TTL check, clean up immediately.
468
+ else {
469
+ const failedAt = c.completedAt ?? c.startedAt;
470
+ if (failedAt)
471
+ overdueDoneCleanupAfterMs.set(c.id, failedAt.epochMilliseconds);
472
+ }
473
+ // Delete the storage file and null the DB column.
474
+ try {
475
+ const provider = await buildStorageProvider(c.requestedFromTenantId);
476
+ await provider.delete(c.downloadStorageKey);
477
+ }
478
+ catch {
479
+ // best-effort; if the storage delete fails, retry on the next pass
454
480
  continue;
455
- overdueDoneCleanupAfterMs.set(c.id, cleanupAfter);
456
- }
457
- // Failed-Job-Branch: kein TTL-Check, sofort cleanup.
458
- // Storage-Datei loeschen + DB-Spalte nullen.
459
- try {
460
- const provider = await buildStorageProvider(c.requestedFromTenantId);
461
- await provider.delete(c.downloadStorageKey);
462
- }
463
- catch {
464
- // best-effort; wenn Storage-Delete failed, retry beim naechsten Pass
465
- continue;
481
+ }
482
+ const result = await crud.update({
483
+ id: c.id,
484
+ version: c.version,
485
+ changes: { downloadStorageKey: null },
486
+ }, createSystemUser(c.requestedFromTenantId), systemTenantDb(db, c.requestedFromTenantId));
487
+ if (result.isSuccess)
488
+ cleaned.push(c.id);
466
489
  }
467
- const result = await crud.update({
468
- id: c.id,
469
- version: c.version,
470
- changes: { downloadStorageKey: null },
471
- }, createSystemUser(c.requestedFromTenantId), systemTenantDb(db, c.requestedFromTenantId));
472
- if (result.isSuccess)
473
- cleaned.push(c.id);
490
+ passCompleted = true;
491
+ }
492
+ finally {
493
+ reportOldestOverdueAge(metrics, overdueDoneCleanupAfterMs, cleaned, now);
494
+ if (passCompleted)
495
+ reportCleanupLastRun(metrics, now);
474
496
  }
475
- reportOldestOverdueAge(metrics, overdueDoneCleanupAfterMs, cleaned, now);
476
497
  return cleaned;
477
498
  }
478
- // Seconds since the longest-overdue Done-status bundle should have been
479
- // deleted (expiresAt+grace passed) but still isn't — 0 when the pass
499
+ // Seconds since the longest-overdue bundle (Done: expiresAt+grace passed;
500
+ // Failed: completedAt ?? startedAt) should have been deleted but still isn't — 0 when the pass
480
501
  // cleaned everything that was due, or nothing was due.
481
502
  function oldestOverdueAgeSeconds(overdueDoneCleanupAfterMs, cleanedIds, now) {
482
503
  const cleaned = new Set(cleanedIds);
@@ -505,6 +526,18 @@ function reportOldestOverdueAge(metrics, overdueDoneCleanupAfterMs, cleanedIds,
505
526
  console.warn(`[user-data-rights:run-export-jobs] metrics.set(${EXPORT_CLEANUP_BACKLOG_AGE_METRIC}) failed err=${err instanceof Error ? err.message : String(err)}`);
506
527
  }
507
528
  }
529
+ function reportCleanupLastRun(metrics, now) {
530
+ // skip: no meter configured means there is nothing to report.
531
+ if (!metrics)
532
+ return;
533
+ try {
534
+ metrics.set(EXPORT_CLEANUP_LAST_RUN_TIMESTAMP_METRIC, now.epochMilliseconds / 1000);
535
+ }
536
+ catch (err) {
537
+ // biome-ignore lint/suspicious/noConsole: operator-visibility for observability-emit-failure
538
+ console.warn(`[user-data-rights:run-export-jobs] metrics.set(${EXPORT_CLEANUP_LAST_RUN_TIMESTAMP_METRIC}) failed err=${err instanceof Error ? err.message : String(err)}`);
539
+ }
540
+ }
508
541
  // Wrapper: crud.update braucht TenantDb. ExportJob ist tenant-agnostisch
509
542
  // (1 Job pro userId), aber der event-store-Stream-Lookup erwartet einen
510
543
  // Tenant-Context. system-mode TenantDb bypassed den auto-tenant-filter
@@ -35,7 +35,7 @@ import { fetchOne, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
35
35
  import { configuredPiiSubjectKms, subjectIdToKey, } from "@cosmicdrift/kumiko-framework/crypto";
36
36
  import { createTenantDb, entityEventName, executeRawQuery, nullBlindIndexesForSubject, } from "@cosmicdrift/kumiko-framework/db";
37
37
  import { EXT_USER_DATA, EXT_USER_DATA_ORDER, extensionUsageEscapeHatchReason, } from "@cosmicdrift/kumiko-framework/engine";
38
- import { createEscapeHatchReporter } from "@cosmicdrift/kumiko-framework/pipeline";
38
+ import { createEscapeHatchReporter, UNATTRIBUTED_ACTOR, } from "@cosmicdrift/kumiko-framework/pipeline";
39
39
  import { purgeSearchDocumentsForSubject, } from "@cosmicdrift/kumiko-framework/search";
40
40
  import { resolveRetentionPolicyForTenant, resolveTenantRetentionPreset, } from "../data-retention/index.js";
41
41
  import { decryptStoredPii, runInSubTransaction } from "../shared/index.js";
@@ -47,7 +47,7 @@ function buildHookDb(db, tenantId, entry, args) {
47
47
  const report = createEscapeHatchReporter({
48
48
  handler: `${EXT_USER_DATA}:${entry.entityName}`,
49
49
  tenantId,
50
- actor: args.actor ?? "system",
50
+ actor: args.actor ?? UNATTRIBUTED_ACTOR,
51
51
  sink: args.escapeHatchAuditSink,
52
52
  });
53
53
  return createTenantDb(db, tenantId, "tenant", undefined, undefined, undefined, {
@@ -22,15 +22,15 @@
22
22
  import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
23
23
  import { configuredPiiSubjectKms, decryptPiiFieldValues, isPiiCiphertext, } from "@cosmicdrift/kumiko-framework/crypto";
24
24
  import { collectEncryptedFieldNames, configuredEntityFieldEncryption, createTenantDb, decryptEntityFieldValues, } from "@cosmicdrift/kumiko-framework/db";
25
- import { EXT_USER_DATA, extensionUsageEscapeHatchReason, } from "@cosmicdrift/kumiko-framework/engine";
26
- import { createEscapeHatchReporter } from "@cosmicdrift/kumiko-framework/pipeline";
25
+ import { EXT_USER_DATA, extensionUsageEscapeHatchReason, maskWriteOnlyFields, } from "@cosmicdrift/kumiko-framework/engine";
26
+ import { createEscapeHatchReporter, UNATTRIBUTED_ACTOR, } from "@cosmicdrift/kumiko-framework/pipeline";
27
27
  import { tenantMembershipsTable } from "../tenant/index.js";
28
28
  import { buildFileRefZipPath } from "./zip-path.js";
29
29
  function buildHookDb(db, tenantId, entry, args) {
30
30
  const report = createEscapeHatchReporter({
31
31
  handler: `${EXT_USER_DATA}:${entry.entityName}`,
32
32
  tenantId,
33
- actor: args.actor ?? "system",
33
+ actor: args.actor ?? UNATTRIBUTED_ACTOR,
34
34
  sink: args.escapeHatchAuditSink,
35
35
  });
36
36
  return createTenantDb(db, tenantId, "tenant", undefined, undefined, undefined, {
@@ -214,7 +214,11 @@ async function decryptSnippetFields(registry, hookEntityName, snippet) {
214
214
  const entity = registry.getEntity(snippet.entity) ?? registry.getEntity(hookEntityName);
215
215
  const encryptedFields = entity ? collectEncryptedFieldNames(entity) : new Set();
216
216
  const kms = configuredPiiSubjectKms();
217
- const rows = await decryptRecords(snippet.rows, encryptedFields, kms);
217
+ const decryptedRows = await decryptRecords(snippet.rows, encryptedFields, kms);
218
+ // Decrypting would otherwise hand writeOnly secrets out as plaintext.
219
+ const rows = entity
220
+ ? decryptedRows.map((row) => maskWriteOnlyFields(entity, row))
221
+ : decryptedRows;
218
222
  if (!snippet.fileRefs || snippet.fileRefs.length === 0) {
219
223
  return { ...snippet, rows };
220
224
  }
@@ -9,6 +9,7 @@ export declare const downloadAttemptEntity: import("@cosmicdrift/kumiko-types/fi
9
9
  readonly filterable?: boolean;
10
10
  readonly encrypted?: boolean;
11
11
  readonly sensitive?: boolean;
12
+ readonly writeOnly?: boolean;
12
13
  readonly readAsInstruction?: boolean;
13
14
  readonly format?: "email" | "url" | "phone" | "password";
14
15
  readonly default?: string;
@@ -29,6 +30,7 @@ export declare const downloadAttemptEntity: import("@cosmicdrift/kumiko-types/fi
29
30
  readonly filterable?: boolean;
30
31
  readonly encrypted?: boolean;
31
32
  readonly sensitive?: boolean;
33
+ readonly writeOnly?: boolean;
32
34
  readonly readAsInstruction?: boolean;
33
35
  readonly format?: "email" | "url" | "phone" | "password";
34
36
  readonly default?: string;
@@ -49,6 +51,7 @@ export declare const downloadAttemptEntity: import("@cosmicdrift/kumiko-types/fi
49
51
  readonly filterable?: boolean;
50
52
  readonly encrypted?: boolean;
51
53
  readonly sensitive?: boolean;
54
+ readonly writeOnly?: boolean;
52
55
  readonly readAsInstruction?: boolean;
53
56
  readonly format?: "email" | "url" | "phone" | "password";
54
57
  readonly default?: string;
@@ -69,6 +72,7 @@ export declare const downloadAttemptEntity: import("@cosmicdrift/kumiko-types/fi
69
72
  readonly filterable?: boolean;
70
73
  readonly encrypted?: boolean;
71
74
  readonly sensitive?: boolean;
75
+ readonly writeOnly?: boolean;
72
76
  readonly readAsInstruction?: boolean;
73
77
  readonly format?: "email" | "url" | "phone" | "password";
74
78
  readonly default?: string;
@@ -89,6 +93,7 @@ export declare const downloadAttemptEntity: import("@cosmicdrift/kumiko-types/fi
89
93
  readonly filterable?: boolean;
90
94
  readonly encrypted?: boolean;
91
95
  readonly sensitive?: boolean;
96
+ readonly writeOnly?: boolean;
92
97
  readonly readAsInstruction?: boolean;
93
98
  readonly format?: "email" | "url" | "phone" | "password";
94
99
  readonly default?: string;
@@ -109,6 +114,7 @@ export declare const downloadAttemptEntity: import("@cosmicdrift/kumiko-types/fi
109
114
  readonly filterable?: boolean;
110
115
  readonly encrypted?: boolean;
111
116
  readonly sensitive?: boolean;
117
+ readonly writeOnly?: boolean;
112
118
  readonly readAsInstruction?: boolean;
113
119
  readonly format?: "email" | "url" | "phone" | "password";
114
120
  readonly default?: string;
@@ -129,6 +135,7 @@ export declare const downloadAttemptEntity: import("@cosmicdrift/kumiko-types/fi
129
135
  readonly filterable?: boolean;
130
136
  readonly encrypted?: boolean;
131
137
  readonly sensitive?: boolean;
138
+ readonly writeOnly?: boolean;
132
139
  readonly readAsInstruction?: boolean;
133
140
  readonly format?: "email" | "url" | "phone" | "password";
134
141
  readonly default?: string;
@@ -166,6 +173,7 @@ export declare const downloadAttemptsTable: import("@cosmicdrift/kumiko-framewor
166
173
  readonly filterable?: boolean;
167
174
  readonly encrypted?: boolean;
168
175
  readonly sensitive?: boolean;
176
+ readonly writeOnly?: boolean;
169
177
  readonly readAsInstruction?: boolean;
170
178
  readonly format?: "email" | "url" | "phone" | "password";
171
179
  readonly default?: string;
@@ -186,6 +194,7 @@ export declare const downloadAttemptsTable: import("@cosmicdrift/kumiko-framewor
186
194
  readonly filterable?: boolean;
187
195
  readonly encrypted?: boolean;
188
196
  readonly sensitive?: boolean;
197
+ readonly writeOnly?: boolean;
189
198
  readonly readAsInstruction?: boolean;
190
199
  readonly format?: "email" | "url" | "phone" | "password";
191
200
  readonly default?: string;
@@ -206,6 +215,7 @@ export declare const downloadAttemptsTable: import("@cosmicdrift/kumiko-framewor
206
215
  readonly filterable?: boolean;
207
216
  readonly encrypted?: boolean;
208
217
  readonly sensitive?: boolean;
218
+ readonly writeOnly?: boolean;
209
219
  readonly readAsInstruction?: boolean;
210
220
  readonly format?: "email" | "url" | "phone" | "password";
211
221
  readonly default?: string;
@@ -226,6 +236,7 @@ export declare const downloadAttemptsTable: import("@cosmicdrift/kumiko-framewor
226
236
  readonly filterable?: boolean;
227
237
  readonly encrypted?: boolean;
228
238
  readonly sensitive?: boolean;
239
+ readonly writeOnly?: boolean;
229
240
  readonly readAsInstruction?: boolean;
230
241
  readonly format?: "email" | "url" | "phone" | "password";
231
242
  readonly default?: string;
@@ -246,6 +257,7 @@ export declare const downloadAttemptsTable: import("@cosmicdrift/kumiko-framewor
246
257
  readonly filterable?: boolean;
247
258
  readonly encrypted?: boolean;
248
259
  readonly sensitive?: boolean;
260
+ readonly writeOnly?: boolean;
249
261
  readonly readAsInstruction?: boolean;
250
262
  readonly format?: "email" | "url" | "phone" | "password";
251
263
  readonly default?: string;
@@ -266,6 +278,7 @@ export declare const downloadAttemptsTable: import("@cosmicdrift/kumiko-framewor
266
278
  readonly filterable?: boolean;
267
279
  readonly encrypted?: boolean;
268
280
  readonly sensitive?: boolean;
281
+ readonly writeOnly?: boolean;
269
282
  readonly readAsInstruction?: boolean;
270
283
  readonly format?: "email" | "url" | "phone" | "password";
271
284
  readonly default?: string;
@@ -286,6 +299,7 @@ export declare const downloadAttemptsTable: import("@cosmicdrift/kumiko-framewor
286
299
  readonly filterable?: boolean;
287
300
  readonly encrypted?: boolean;
288
301
  readonly sensitive?: boolean;
302
+ readonly writeOnly?: boolean;
289
303
  readonly readAsInstruction?: boolean;
290
304
  readonly format?: "email" | "url" | "phone" | "password";
291
305
  readonly default?: string;
@@ -9,6 +9,7 @@ export declare const exportDownloadTokenEntity: import("@cosmicdrift/kumiko-type
9
9
  readonly filterable?: boolean;
10
10
  readonly encrypted?: boolean;
11
11
  readonly sensitive?: boolean;
12
+ readonly writeOnly?: boolean;
12
13
  readonly readAsInstruction?: boolean;
13
14
  readonly format?: "email" | "url" | "phone" | "password";
14
15
  readonly default?: string;
@@ -29,6 +30,7 @@ export declare const exportDownloadTokenEntity: import("@cosmicdrift/kumiko-type
29
30
  readonly filterable?: boolean;
30
31
  readonly encrypted?: boolean;
31
32
  readonly sensitive?: boolean;
33
+ readonly writeOnly?: boolean;
32
34
  readonly readAsInstruction?: boolean;
33
35
  readonly format?: "email" | "url" | "phone" | "password";
34
36
  readonly default?: string;
@@ -106,6 +108,7 @@ export declare const exportDownloadTokenEntity: import("@cosmicdrift/kumiko-type
106
108
  readonly filterable?: boolean;
107
109
  readonly encrypted?: boolean;
108
110
  readonly sensitive?: boolean;
111
+ readonly writeOnly?: boolean;
109
112
  readonly readAsInstruction?: boolean;
110
113
  readonly format?: "email" | "url" | "phone" | "password";
111
114
  readonly default?: string;
@@ -126,6 +129,7 @@ export declare const exportDownloadTokenEntity: import("@cosmicdrift/kumiko-type
126
129
  readonly filterable?: boolean;
127
130
  readonly encrypted?: boolean;
128
131
  readonly sensitive?: boolean;
132
+ readonly writeOnly?: boolean;
129
133
  readonly readAsInstruction?: boolean;
130
134
  readonly format?: "email" | "url" | "phone" | "password";
131
135
  readonly default?: string;
@@ -148,6 +152,7 @@ export declare const exportDownloadTokensTable: import("@cosmicdrift/kumiko-fram
148
152
  readonly filterable?: boolean;
149
153
  readonly encrypted?: boolean;
150
154
  readonly sensitive?: boolean;
155
+ readonly writeOnly?: boolean;
151
156
  readonly readAsInstruction?: boolean;
152
157
  readonly format?: "email" | "url" | "phone" | "password";
153
158
  readonly default?: string;
@@ -168,6 +173,7 @@ export declare const exportDownloadTokensTable: import("@cosmicdrift/kumiko-fram
168
173
  readonly filterable?: boolean;
169
174
  readonly encrypted?: boolean;
170
175
  readonly sensitive?: boolean;
176
+ readonly writeOnly?: boolean;
171
177
  readonly readAsInstruction?: boolean;
172
178
  readonly format?: "email" | "url" | "phone" | "password";
173
179
  readonly default?: string;
@@ -245,6 +251,7 @@ export declare const exportDownloadTokensTable: import("@cosmicdrift/kumiko-fram
245
251
  readonly filterable?: boolean;
246
252
  readonly encrypted?: boolean;
247
253
  readonly sensitive?: boolean;
254
+ readonly writeOnly?: boolean;
248
255
  readonly readAsInstruction?: boolean;
249
256
  readonly format?: "email" | "url" | "phone" | "password";
250
257
  readonly default?: string;
@@ -265,6 +272,7 @@ export declare const exportDownloadTokensTable: import("@cosmicdrift/kumiko-fram
265
272
  readonly filterable?: boolean;
266
273
  readonly encrypted?: boolean;
267
274
  readonly sensitive?: boolean;
275
+ readonly writeOnly?: boolean;
268
276
  readonly readAsInstruction?: boolean;
269
277
  readonly format?: "email" | "url" | "phone" | "password";
270
278
  readonly default?: string;