@cosmicdrift/kumiko-samples 0.350.0 → 0.352.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 (119) hide show
  1. package/package.json +1 -1
  2. package/packages/bundled-features/package.json +2 -2
  3. package/packages/bundled-features/src/audit/handlers/list.query.ts +1 -1
  4. package/packages/bundled-features/src/auth-email-password/email-templates.ts +1 -1
  5. package/packages/bundled-features/src/auth-email-password/handlers/invite-accept-with-login.write.ts +1 -1
  6. package/packages/bundled-features/src/auth-email-password/handlers/invite-accept.write.ts +1 -1
  7. package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +1 -1
  8. package/packages/bundled-features/src/auth-email-password/handlers/signup-request.write.ts +1 -1
  9. package/packages/bundled-features/src/auth-email-password/handlers/token-request-handler.ts +1 -1
  10. package/packages/bundled-features/src/auth-email-password/reset-token.ts +1 -1
  11. package/packages/bundled-features/src/auth-email-password/unlock-token.ts +1 -1
  12. package/packages/bundled-features/src/auth-email-password/verification-token.ts +1 -1
  13. package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +1 -1
  14. package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm.write.ts +1 -1
  15. package/packages/bundled-features/src/auth-mfa/hmac-token-codec.ts +1 -1
  16. package/packages/bundled-features/src/auth-mfa/mfa-challenge-token.ts +1 -1
  17. package/packages/bundled-features/src/auth-mfa/mfa-preauth-setup-token.ts +1 -1
  18. package/packages/bundled-features/src/auth-mfa/mfa-setup-token.ts +1 -1
  19. package/packages/bundled-features/src/billing-foundation/constants.ts +3 -17
  20. package/packages/bundled-features/src/billing-foundation/consumer-protection/confirmation-mail.ts +1 -1
  21. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-mail.ts +1 -1
  22. package/packages/bundled-features/src/billing-foundation/feature.ts +2 -9
  23. package/packages/bundled-features/src/billing-foundation/handlers/sync-subscription.write.ts +3 -11
  24. package/packages/bundled-features/src/cap-counter/book-cap-usage.ts +96 -55
  25. package/packages/bundled-features/src/cap-counter/cap-reservation.ts +247 -0
  26. package/packages/bundled-features/src/cap-counter/changes.json +28 -0
  27. package/packages/bundled-features/src/cap-counter/constants.ts +8 -0
  28. package/packages/bundled-features/src/cap-counter/enforce-cap.ts +1 -9
  29. package/packages/bundled-features/src/cap-counter/feature.ts +7 -0
  30. package/packages/bundled-features/src/cap-counter/handlers/increment-rolling.write.ts +5 -0
  31. package/packages/bundled-features/src/cap-counter/index.ts +9 -0
  32. package/packages/bundled-features/src/cap-counter/payload-field.ts +10 -0
  33. package/packages/bundled-features/src/cap-counter/reservation-chain.ts +51 -0
  34. package/packages/bundled-features/src/cap-counter/stock-cap-guard.ts +3 -0
  35. package/packages/bundled-features/src/cap-counter/tables.ts +53 -0
  36. package/packages/bundled-features/src/cap-counter/value-cap-guard.ts +70 -0
  37. package/packages/bundled-features/src/cap-counter/with-cap-enforcement.ts +63 -70
  38. package/packages/bundled-features/src/channel-discord/feature.ts +1 -0
  39. package/packages/bundled-features/src/channel-slack/feature.ts +1 -0
  40. package/packages/bundled-features/src/channel-teams/feature.ts +1 -0
  41. package/packages/bundled-features/src/delivery/attempt-log-retention.ts +40 -0
  42. package/packages/bundled-features/src/delivery/attempt-log.ts +26 -3
  43. package/packages/bundled-features/src/delivery/changes.json +12 -0
  44. package/packages/bundled-features/src/delivery/constants.ts +1 -0
  45. package/packages/bundled-features/src/delivery/events.ts +3 -0
  46. package/packages/bundled-features/src/delivery/feature.ts +50 -2
  47. package/packages/bundled-features/src/delivery/handlers/log.query.ts +1 -1
  48. package/packages/bundled-features/src/delivery/index.ts +6 -0
  49. package/packages/bundled-features/src/delivery/mask-recipient-address.ts +40 -0
  50. package/packages/bundled-features/src/delivery/public-names.ts +5 -0
  51. package/packages/bundled-features/src/delivery/types.ts +12 -2
  52. package/packages/bundled-features/src/derivatives-sharp/changes.json +6 -0
  53. package/packages/bundled-features/src/feature-toggles/handlers/set.write.ts +1 -1
  54. package/packages/bundled-features/src/form-draft/db/queries/cleanup.ts +1 -1
  55. package/packages/bundled-features/src/form-draft/db/queries/owned-file-refs.ts +1 -1
  56. package/packages/bundled-features/src/form-draft/lookup.ts +1 -1
  57. package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message.write.ts +1 -1
  58. package/packages/bundled-features/src/inbound-mail-foundation/oauth-access-token.ts +1 -1
  59. package/packages/bundled-features/src/inbound-mail-foundation/oauth-state.ts +1 -1
  60. package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +1 -1
  61. package/packages/bundled-features/src/inbound-provider-imap/feature.ts +1 -1
  62. package/packages/bundled-features/src/inbound-provider-imap/imap-client.ts +1 -1
  63. package/packages/bundled-features/src/jobs/job-instant.ts +1 -1
  64. package/packages/bundled-features/src/legal-pages/README.md +3 -3
  65. package/packages/bundled-features/src/legal-pages/changes.json +6 -0
  66. package/packages/bundled-features/src/metrics/compute.ts +1 -1
  67. package/packages/bundled-features/src/metrics/feature.ts +1 -1
  68. package/packages/bundled-features/src/metrics/types.ts +1 -1
  69. package/packages/bundled-features/src/metrics/window.ts +1 -1
  70. package/packages/bundled-features/src/personal-access-tokens/expiry.ts +1 -1
  71. package/packages/bundled-features/src/personal-access-tokens/handlers/create.write.ts +1 -1
  72. package/packages/bundled-features/src/personal-access-tokens/handlers/revoke.write.ts +1 -1
  73. package/packages/bundled-features/src/personal-access-tokens/revoke-for-user.ts +1 -1
  74. package/packages/bundled-features/src/renderer-foundation/types.ts +1 -1
  75. package/packages/bundled-features/src/renderer-simple/changes.json +6 -0
  76. package/packages/bundled-features/src/renderer-simple/feature.ts +4 -6
  77. package/packages/bundled-features/src/renderer-simple/index.ts +1 -0
  78. package/packages/bundled-features/src/renderer-simple/mail-blocks.ts +138 -0
  79. package/packages/bundled-features/src/renderer-simple/simple-renderer.ts +33 -4
  80. package/packages/bundled-features/src/sessions/handlers/revoke-all-for-user.write.ts +1 -1
  81. package/packages/bundled-features/src/sessions/handlers/revoke-all-others.write.ts +1 -1
  82. package/packages/bundled-features/src/sessions/handlers/revoke.write.ts +1 -1
  83. package/packages/bundled-features/src/sessions/session-callbacks.ts +1 -1
  84. package/packages/bundled-features/src/shared/grace-period.ts +3 -10
  85. package/packages/bundled-features/src/shared/row-bound-grant.ts +1 -1
  86. package/packages/bundled-features/src/shared/signed-token.ts +1 -1
  87. package/packages/bundled-features/src/subscription-mollie/verify-webhook.ts +1 -1
  88. package/packages/bundled-features/src/subscription-stripe/verify-webhook.ts +1 -1
  89. package/packages/bundled-features/src/template-resolver/api.ts +1 -1
  90. package/packages/bundled-features/src/template-resolver/handlers/by-tenant.query.ts +1 -1
  91. package/packages/bundled-features/src/template-resolver/handlers/collection-shared.ts +1 -1
  92. package/packages/bundled-features/src/template-resolver/table.ts +1 -1
  93. package/packages/bundled-features/src/tenant/handlers/team-list.query.ts +1 -1
  94. package/packages/bundled-features/src/tenant/invited-membership.ts +1 -1
  95. package/packages/bundled-features/src/tenant-handover/grant.ts +1 -1
  96. package/packages/bundled-features/src/tier-engine/changes.json +6 -0
  97. package/packages/bundled-features/src/tier-engine/index.ts +7 -0
  98. package/packages/bundled-features/src/tier-engine/tier-option-gate.ts +148 -0
  99. package/packages/bundled-features/src/user-data-rights/db/queries/export-jobs.ts +1 -1
  100. package/packages/bundled-features/src/user-data-rights/db/queries/forget-cleanup.ts +3 -9
  101. package/packages/bundled-features/src/user-data-rights/deletion-token.ts +1 -1
  102. package/packages/bundled-features/src/user-data-rights/email-templates.ts +1 -1
  103. package/packages/bundled-features/src/user-data-rights/handlers/list-download-attempts.query.ts +1 -1
  104. package/packages/bundled-features/src/user-data-rights/handlers/my-audit-log.query.ts +1 -1
  105. package/packages/bundled-features/src/user-data-rights-defaults/hooks/delivery-attempt.userdata-hook.ts +36 -6
  106. package/packages/bundled-features/src/waitlist/handlers/invite.write.ts +1 -1
  107. package/packages/bundled-features/src/waitlist/handlers/submit.write.ts +1 -1
  108. package/samples/apps/marketing-demo/package.json +1 -0
  109. package/samples/apps/marketing-demo/src/app/seed-data.ts +2 -2
  110. package/samples/apps/marketing-demo/src/app/seed.ts +1 -1
  111. package/samples/apps/styleguide/src/app/shell.tsx +1 -1
  112. package/samples/apps/styleguide/src/features/gallery/web/Gallery.tsx +9 -3
  113. package/samples/apps/ui-walkthrough/src/app/assistant-action.tsx +6 -5
  114. package/samples/apps/ui-walkthrough/src/app/shell.tsx +3 -2
  115. package/samples/apps/ui-walkthrough/src/features/tasks/i18n.ts +8 -0
  116. package/samples/apps/use-all-bundled/package.json +1 -0
  117. package/samples/apps/use-all-bundled/src/app/seed.ts +1 -1
  118. package/samples/recipes/files-post-processing/package.json +1 -1
  119. package/samples/recipes/seed-migration/README.md +13 -1
@@ -29,6 +29,13 @@ export {
29
29
  createTierEngineFeature,
30
30
  tierEngineFeature,
31
31
  } from "./feature.js";
32
+ export {
33
+ createTierOptionGate,
34
+ type TierOptionAvailability,
35
+ type TierOptionGate,
36
+ type TierOptionGateDeps,
37
+ type TierOptionSpec,
38
+ } from "./tier-option-gate.js";
32
39
  export {
33
40
  createTierResolver,
34
41
  type TierResolver,
@@ -0,0 +1,148 @@
1
+ // Tier-gated options of a select field: one spec drives the availability query the
2
+ // renderer shows (disabled option + "from <tier>" hint) and the write-path check that
3
+ // actually enforces it, so the two cannot drift apart.
4
+
5
+ import type { EntityTableMeta, SchemaTable, TenantDb } from "@cosmicdrift/kumiko-framework/db";
6
+ import type { WriteHandlerDef } from "@cosmicdrift/kumiko-framework/engine";
7
+ import {
8
+ UnprocessableError,
9
+ type WriteFailure,
10
+ writeFailure,
11
+ } from "@cosmicdrift/kumiko-framework/errors";
12
+ import { isPlainObject } from "@cosmicdrift/kumiko-framework/utils";
13
+ import {
14
+ type CapGuardContext,
15
+ type CapLimitContext,
16
+ readPayloadField,
17
+ } from "../cap-counter/index.js";
18
+
19
+ export type TierOptionSpec<TCaps> = {
20
+ readonly field: string;
21
+ readonly options: readonly string[];
22
+ readonly isOptionAllowed: (option: string, caps: TCaps) => boolean;
23
+ readonly code: string;
24
+ readonly i18nKey: string;
25
+ /** Lets an update that resends the unchanged stored value of a no longer allowed
26
+ * option pass (edit forms submit the whole row). Without it the stored value is not looked up. */
27
+ readonly table?: SchemaTable | EntityTableMeta;
28
+ };
29
+
30
+ export type TierOptionAvailability = {
31
+ readonly value: string;
32
+ readonly disabled: boolean;
33
+ readonly hint?: string;
34
+ };
35
+
36
+ export type TierOptionGateDeps<TTier extends string, TCaps> = {
37
+ /** Ascending: the first tier that allows an option is the one named as required. */
38
+ readonly tierOrder: readonly TTier[];
39
+ readonly capsForTier: (tier: TTier, context: CapLimitContext) => TCaps | Promise<TCaps>;
40
+ readonly resolveTier: (db: TenantDb) => Promise<TTier>;
41
+ };
42
+
43
+ export type TierOptionGate<TTier extends string, TCaps> = {
44
+ readonly optionAvailability: (
45
+ db: TenantDb,
46
+ context: CapLimitContext,
47
+ spec: Pick<TierOptionSpec<TCaps>, "options" | "isOptionAllowed">,
48
+ hintFor: (requiredTier: TTier) => string,
49
+ ) => Promise<readonly TierOptionAvailability[]>;
50
+ /** `recordId` is the id of the edited row; with `spec.table` it enables the unchanged-value pass. */
51
+ readonly checkTierOption: (
52
+ ctx: CapGuardContext,
53
+ value: unknown,
54
+ spec: TierOptionSpec<TCaps>,
55
+ recordId?: string,
56
+ ) => Promise<WriteFailure | null>;
57
+ readonly withTierOptionGate: (
58
+ handler: WriteHandlerDef,
59
+ spec: TierOptionSpec<TCaps>,
60
+ ) => WriteHandlerDef;
61
+ };
62
+
63
+ function limitContextOf(ctx: CapGuardContext): CapLimitContext {
64
+ return ctx.config ? { config: ctx.config } : {};
65
+ }
66
+
67
+ function recordIdOf(payload: unknown): string | undefined {
68
+ return isPlainObject(payload) && typeof payload["id"] === "string" ? payload["id"] : undefined;
69
+ }
70
+
71
+ export function createTierOptionGate<TTier extends string, TCaps>(
72
+ deps: TierOptionGateDeps<TTier, TCaps>,
73
+ ): TierOptionGate<TTier, TCaps> {
74
+ async function capsPerTier(
75
+ context: CapLimitContext,
76
+ ): Promise<readonly { readonly tier: TTier; readonly caps: TCaps }[]> {
77
+ return Promise.all(
78
+ deps.tierOrder.map(async (tier) => ({ tier, caps: await deps.capsForTier(tier, context) })),
79
+ );
80
+ }
81
+
82
+ async function optionAvailability(
83
+ db: TenantDb,
84
+ context: CapLimitContext,
85
+ spec: Pick<TierOptionSpec<TCaps>, "options" | "isOptionAllowed">,
86
+ hintFor: (requiredTier: TTier) => string,
87
+ ): Promise<readonly TierOptionAvailability[]> {
88
+ const currentCaps = await deps.capsForTier(await deps.resolveTier(db), context);
89
+ const tiers = await capsPerTier(context);
90
+ return spec.options.map((value) => {
91
+ if (spec.isOptionAllowed(value, currentCaps)) return { value, disabled: false };
92
+ const required = tiers.find(({ caps }) => spec.isOptionAllowed(value, caps));
93
+ return required === undefined
94
+ ? { value, disabled: true }
95
+ : { value, disabled: true, hint: hintFor(required.tier) };
96
+ });
97
+ }
98
+
99
+ async function storedValueMatches(
100
+ ctx: CapGuardContext,
101
+ spec: TierOptionSpec<TCaps>,
102
+ value: string,
103
+ recordId: string | undefined,
104
+ ): Promise<boolean> {
105
+ if (spec.table === undefined || recordId === undefined) return false;
106
+ const stored = await ctx.db.fetchOne<Record<string, unknown>>(spec.table, { id: recordId });
107
+ return stored?.[spec.field] === value;
108
+ }
109
+
110
+ async function checkTierOption(
111
+ ctx: CapGuardContext,
112
+ value: unknown,
113
+ spec: TierOptionSpec<TCaps>,
114
+ recordId?: string,
115
+ ): Promise<WriteFailure | null> {
116
+ // skip: an absent or non-string value is the schema's business, not a tier violation.
117
+ if (typeof value !== "string") return null;
118
+ const context = limitContextOf(ctx);
119
+ const currentCaps = await deps.capsForTier(await deps.resolveTier(ctx.db), context);
120
+ if (spec.isOptionAllowed(value, currentCaps)) return null;
121
+ if (await storedValueMatches(ctx, spec, value, recordId)) return null;
122
+ const required = (await capsPerTier(context)).find(({ caps }) =>
123
+ spec.isOptionAllowed(value, caps),
124
+ );
125
+ return writeFailure(
126
+ new UnprocessableError(spec.code, {
127
+ i18nKey: spec.i18nKey,
128
+ details: { field: spec.field, value, requiredTier: required?.tier },
129
+ }),
130
+ );
131
+ }
132
+
133
+ function withTierOptionGate(
134
+ handler: WriteHandlerDef,
135
+ spec: TierOptionSpec<TCaps>,
136
+ ): WriteHandlerDef {
137
+ return {
138
+ ...handler,
139
+ handler: async (event, ctx) => {
140
+ const value = readPayloadField(event.payload, spec.field);
141
+ const failure = await checkTierOption(ctx, value, spec, recordIdOf(event.payload));
142
+ return failure ?? handler.handler(event, ctx);
143
+ },
144
+ };
145
+ }
146
+
147
+ return { optionAvailability, checkTierOption, withTierOptionGate };
148
+ }
@@ -1,7 +1,7 @@
1
1
  import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
3
3
  import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
4
- import type { Temporal } from "temporal-polyfill";
4
+ import type { Temporal } from "@cosmicdrift/kumiko-types/temporal";
5
5
  import { exportJobsTable } from "../../schema/export-job.js";
6
6
 
7
7
  export type ExportJobCleanupCandidate = {
@@ -1,22 +1,16 @@
1
1
  import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
3
- // Value-only import, aliased to avoid shadowing the ambient global
4
- // `Temporal` TYPE that gracePeriodEndCutoff's parameter type resolves
5
- // against (same #1438 dual-package-hazard pattern as event-store.ts).
6
- import { Temporal as TemporalPolyfill } from "temporal-polyfill";
3
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
7
4
  import { userTable } from "../../../user/index.js";
8
5
 
9
6
  export async function selectUsersDueForForgetCleanup(
10
7
  db: DbConnection,
11
8
  status: string,
12
- gracePeriodEndCutoff: InstanceType<typeof TemporalPolyfill.Instant> | string,
9
+ gracePeriodEndCutoff: Temporal.Instant | string,
13
10
  ): Promise<readonly { id: string }[]> {
14
- // @cast-boundary temporal-polyfill-vs-ambient: same TC39 Temporal.Instant
15
- // at runtime, distinct nominal types across the two .d.ts sources (see
16
- // event-store.ts). Only ever consumed via the `lte` comparison below.
17
11
  const cutoff =
18
12
  typeof gracePeriodEndCutoff === "string"
19
- ? TemporalPolyfill.Instant.from(gracePeriodEndCutoff)
13
+ ? Temporal.Instant.from(gracePeriodEndCutoff)
20
14
  : gracePeriodEndCutoff;
21
15
  return selectMany<{ id: string }>(db, userTable, {
22
16
  status,
@@ -7,7 +7,7 @@
7
7
  // The purpose string and the anchoring are unchanged from the hand-rolled
8
8
  // version this replaced, so tokens stay byte-compatible.
9
9
 
10
- import type { Temporal } from "temporal-polyfill";
10
+ import type { Temporal } from "@cosmicdrift/kumiko-types/temporal";
11
11
  import {
12
12
  type RowBoundGrantResult,
13
13
  redeemRowBoundGrant,
@@ -5,7 +5,7 @@
5
5
 
6
6
  import { mailT, registerMailTranslations } from "@cosmicdrift/kumiko-framework/i18n";
7
7
  import { escapeHtml, escapeHtmlAttr } from "@cosmicdrift/kumiko-headless";
8
- import { Temporal } from "temporal-polyfill";
8
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
9
9
 
10
10
  export type GdprMailLocale = string;
11
11
 
@@ -1,6 +1,6 @@
1
1
  import { selectMany, type WhereObject } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
3
- import { Temporal } from "temporal-polyfill";
3
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
4
4
  import * as z from "zod";
5
5
  import { downloadAttemptsTable } from "../schema/download-attempt.js";
6
6
 
@@ -1,7 +1,7 @@
1
1
  import { selectMany, type WhereObject } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
3
3
  import { eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
4
- import { Temporal } from "temporal-polyfill";
4
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
5
5
  import * as z from "zod";
6
6
 
7
7
  // DSGVO Art. 15 Selbstauskunft — User reads HIS OWN audit-log.
@@ -1,5 +1,12 @@
1
1
  import type { UserDataDeleteHook, UserDataExportHook } from "@cosmicdrift/kumiko-framework/engine";
2
- import { deliveryAttemptsTable } from "../../delivery/index.js";
2
+ import {
3
+ appendEventInTenantDb,
4
+ getStreamVersionInTenantDb,
5
+ } from "@cosmicdrift/kumiko-framework/event-store";
6
+ import {
7
+ DELIVERY_ATTEMPT_ADDRESS_ERASED_EVENT,
8
+ deliveryAttemptsTable,
9
+ } from "../../delivery/index.js";
3
10
  import { featureMounted } from "./feature-mounted.js";
4
11
 
5
12
  // userData-Hooks for delivery's attempt log (deferred from #797, closed by
@@ -28,9 +35,32 @@ export const deliveryAttemptExportHook: UserDataExportHook = async (ctx) => {
28
35
  };
29
36
  };
30
37
 
31
- export const deliveryAttemptDeleteHook: UserDataDeleteHook = async () => {
32
- // Deliberate no-op: erasure runs via crypto-shredding — the forget pipeline
33
- // erases the recipient's DEK, which makes recipientAddress unreadable in
34
- // BOTH the append-only events and the projected rows (#799). A read-side
35
- // UPDATE here would be wiped on the next projection rebuild anyway.
38
+ // Both strategies erase the address. The DEK shredding of a forget covers KMS deployments; the
39
+ // erase event also covers plaintext mode, and unlike a read-side UPDATE it survives a projection
40
+ // rebuild. Rows without an address are skipped, which makes a second run a no-op.
41
+ export const deliveryAttemptDeleteHook: UserDataDeleteHook = async (ctx) => {
42
+ // skip: delivery not mounted — its table doesn't exist, nothing to erase.
43
+ if (!featureMounted(ctx, "delivery")) return;
44
+ const rows = await ctx.db.selectMany<{ id: string; recipientAddress: string | null }>(
45
+ deliveryAttemptsTable,
46
+ {
47
+ tenantId: ctx.tenantId,
48
+ recipientId: ctx.userId,
49
+ },
50
+ );
51
+ for (const row of rows) {
52
+ if (row.recipientAddress === null) continue;
53
+ await appendEventInTenantDb(
54
+ ctx.db,
55
+ {
56
+ aggregateType: "deliveryAttempt",
57
+ aggregateId: row.id,
58
+ expectedVersion: await getStreamVersionInTenantDb(ctx.db, row.id),
59
+ type: DELIVERY_ATTEMPT_ADDRESS_ERASED_EVENT,
60
+ payload: {},
61
+ metadata: { userId: "system" },
62
+ },
63
+ { registry: ctx.registry },
64
+ );
65
+ }
36
66
  };
@@ -13,7 +13,7 @@ import {
13
13
  type WriteFailure,
14
14
  writeFailure,
15
15
  } from "@cosmicdrift/kumiko-framework/errors";
16
- import { Temporal } from "temporal-polyfill";
16
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
17
17
  import { AuthErrors, AuthHandlers } from "../../auth-email-password/index.js";
18
18
  import { TenantHandlers, tenantTable } from "../../tenant/index.js";
19
19
  import { DEFAULT_OWN_TENANT_INVITE_ROLE, WAITLIST_STATUS, WaitlistErrors } from "../constants.js";
@@ -6,7 +6,7 @@ import {
6
6
  type WriteResult,
7
7
  } from "@cosmicdrift/kumiko-framework/engine";
8
8
  import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
9
- import { Temporal } from "temporal-polyfill";
9
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
10
10
  import { WAITLIST_NOTIFICATION_TYPES, WAITLIST_STATUS } from "../constants.js";
11
11
  import { normalizeEmail, platformActor, waitlistDb, waitlistExecutor } from "../lib.js";
12
12
  import {
@@ -15,6 +15,7 @@
15
15
  "@cosmicdrift/kumiko-framework": "workspace:*",
16
16
  "@cosmicdrift/kumiko-headless": "workspace:*",
17
17
  "@cosmicdrift/kumiko-renderer-web": "workspace:*",
18
+ "@cosmicdrift/kumiko-types": "workspace:*",
18
19
  "react": "^19.2.6",
19
20
  "react-dom": "^19.2.6",
20
21
  "@cosmicdrift/kumiko-locale-de": "workspace:*"
@@ -306,7 +306,7 @@ export const ASSET_TEMPLATES: ReadonlyArray<AssetTemplate> = [
306
306
  // treffen ohne Sonderfall. Wird nur für status==="lent" konsultiert.
307
307
  export const OWNERS: ReadonlyArray<string> = [
308
308
  "",
309
- "Marc Frost",
309
+ "Alex Morgan",
310
310
  "Anna Weber",
311
311
  "Lars Bergmann",
312
312
  "Sina Klein",
@@ -435,7 +435,7 @@ export const REPORTERS: ReadonlyArray<string> = [
435
435
  // unzugewiesen damit die Liste nicht zu uniform aussieht.
436
436
  export const ASSIGNEES: ReadonlyArray<string> = [
437
437
  "",
438
- "Marc Frost",
438
+ "Alex Morgan",
439
439
  "IT-Team",
440
440
  "Sysadmin",
441
441
  "Helpdesk-L1",
@@ -10,7 +10,7 @@
10
10
 
11
11
  import type { SeedFn } from "@cosmicdrift/kumiko-dev-server";
12
12
  import { TestUsers } from "@cosmicdrift/kumiko-framework/stack";
13
- import { Temporal } from "temporal-polyfill";
13
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
14
14
  import { countAssetsForTenant, countTicketsForTenant } from "../db/queries/seed-counts";
15
15
  import { ASSET_STATUSES } from "../features/assets/schema";
16
16
  import { TICKET_SEVERITIES, TICKET_STATUSES } from "../features/helpdesk/schema";
@@ -58,7 +58,7 @@ export function AppShell({
58
58
  ],
59
59
  ])
60
60
  }
61
- sidebarFooter={<SidebarUser name="Marc Frost" email="marc@cosmicdrift.dev" />}
61
+ sidebarFooter={<SidebarUser name="Alex Morgan" email="alex@example.com" />}
62
62
  >
63
63
  {activeTarget !== undefined ? <EditorPanel resolvers={resolvers} /> : children}
64
64
  </DefaultAppShell>
@@ -606,20 +606,26 @@ export function Gallery(): ReactNode {
606
606
  <div className="flex flex-col gap-5 px-6 py-6">
607
607
  <div className="flex items-center gap-4">
608
608
  <div className="bg-muted text-foreground flex size-16 items-center justify-center rounded-full text-lg font-semibold">
609
- MF
609
+ AM
610
610
  </div>
611
611
  <Button variant="secondary">Change avatar</Button>
612
612
  </div>
613
613
  <div className="grid gap-4 sm:grid-cols-2">
614
614
  <Field id="pf-name" label="Full name">
615
- <Input kind="text" id="pf-name" name="pf-name" value="Marc Frost" onChange={noop} />
615
+ <Input
616
+ kind="text"
617
+ id="pf-name"
618
+ name="pf-name"
619
+ value="Alex Morgan"
620
+ onChange={noop}
621
+ />
616
622
  </Field>
617
623
  <Field id="pf-email" label="Email">
618
624
  <Input
619
625
  kind="text"
620
626
  id="pf-email"
621
627
  name="pf-email"
622
- value="marc@cosmicdrift.dev"
628
+ value="alex@example.com"
623
629
  onChange={noop}
624
630
  />
625
631
  </Field>
@@ -1,4 +1,4 @@
1
- import { usePrimitives } from "@cosmicdrift/kumiko-renderer";
1
+ import { usePrimitives, useTranslation } from "@cosmicdrift/kumiko-renderer";
2
2
  import { WandSparkles } from "lucide-react";
3
3
  import { type ReactNode, useEffect, useState } from "react";
4
4
 
@@ -6,6 +6,7 @@ const noop = (): void => {};
6
6
 
7
7
  export function AssistantAction(): ReactNode {
8
8
  const { Dialog } = usePrimitives();
9
+ const t = useTranslation();
9
10
  const [open, setOpen] = useState(false);
10
11
 
11
12
  // Registered here on purpose: an overflow menu that unmounts this component
@@ -30,7 +31,7 @@ export function AssistantAction(): ReactNode {
30
31
  className="inline-flex h-9 items-center gap-2 rounded-md border border-border bg-card px-3 text-sm font-medium text-foreground hover:bg-accent"
31
32
  >
32
33
  <WandSparkles className="h-4 w-4" aria-hidden="true" />
33
- Assistent
34
+ {t("tasks.assistant.label")}
34
35
  <kbd className="rounded border border-border px-1 font-sans text-xs text-muted-foreground">
35
36
  ⌘K
36
37
  </kbd>
@@ -38,9 +39,9 @@ export function AssistantAction(): ReactNode {
38
39
  <Dialog
39
40
  open={open}
40
41
  onOpenChange={setOpen}
41
- title="Assistent"
42
- description="Der Assistent ist im Walkthrough nicht angebunden."
43
- confirmLabel="Schließen"
42
+ title={t("tasks.assistant.label")}
43
+ description={t("tasks.assistant.description")}
44
+ confirmLabel={t("tasks.assistant.close")}
44
45
  onConfirm={noop}
45
46
  testId="assistant-dialog"
46
47
  />
@@ -13,6 +13,7 @@ import {
13
13
  SidebarBrand,
14
14
  ThemeMenuItem,
15
15
  ThemeToggle,
16
+ useTranslation,
16
17
  } from "@cosmicdrift/kumiko-renderer-web";
17
18
  import { MoonStar, Sun } from "lucide-react";
18
19
  import type { ReactNode } from "react";
@@ -20,7 +21,6 @@ import { AssistantAction } from "./assistant-action";
20
21
  import { BETA_TENANT_ID, DEV_TENANT_ID } from "./auth-constants";
21
22
 
22
23
  const APP_NAME = "Kumiko Walkthrough";
23
- const APP_TAGLINE = "Design-Referenz";
24
24
 
25
25
  const tenantName = (tenantId: string): string => {
26
26
  if (tenantId === DEV_TENANT_ID) return "Dev Tenant";
@@ -40,9 +40,10 @@ export function AppShell({
40
40
  readonly children: ReactNode;
41
41
  readonly schema: AppSchema;
42
42
  }): ReactNode {
43
+ const t = useTranslation();
43
44
  return (
44
45
  <DefaultAppShell
45
- brand={<SidebarBrand name={APP_NAME} plan={APP_TAGLINE} />}
46
+ brand={<SidebarBrand name={APP_NAME} plan={t("tasks.shell.tagline")} />}
46
47
  schema={schema}
47
48
  headerActions={
48
49
  <>
@@ -5,6 +5,10 @@ import type { TranslationsByLocale } from "@cosmicdrift/kumiko-renderer";
5
5
 
6
6
  export const tasksTranslations: TranslationsByLocale = {
7
7
  de: {
8
+ "tasks.shell.tagline": "Design-Referenz",
9
+ "tasks.assistant.label": "Assistent",
10
+ "tasks.assistant.description": "Der Assistent ist im Walkthrough nicht angebunden.",
11
+ "tasks.assistant.close": "Schließen",
8
12
  "tasks.nav.group": "Allgemein",
9
13
  "tasks.nav.list": "Aufgaben",
10
14
  "tasks.nav.new": "Neue Aufgabe",
@@ -21,6 +25,10 @@ export const tasksTranslations: TranslationsByLocale = {
21
25
  "tasks:entity:task:field:notes": "Notizen",
22
26
  },
23
27
  en: {
28
+ "tasks.shell.tagline": "Design reference",
29
+ "tasks.assistant.label": "Assistant",
30
+ "tasks.assistant.description": "The assistant is not connected in the walkthrough.",
31
+ "tasks.assistant.close": "Close",
24
32
  "tasks.nav.group": "General",
25
33
  "tasks.nav.list": "Tasks",
26
34
  "tasks.nav.new": "New task",
@@ -22,6 +22,7 @@
22
22
  "@cosmicdrift/kumiko-headless": "workspace:*",
23
23
  "@cosmicdrift/kumiko-renderer-web": "workspace:*",
24
24
  "@cosmicdrift/kumiko-server-runtime": "workspace:*",
25
+ "@cosmicdrift/kumiko-types": "workspace:*",
25
26
  "react": "^19.2.6",
26
27
  "react-dom": "^19.2.6",
27
28
  "zod": "^4.4.3",
@@ -34,7 +34,7 @@ import {
34
34
  } from "@cosmicdrift/kumiko-framework/engine";
35
35
  import { TestUsers, unsafeCreateEntityTable } from "@cosmicdrift/kumiko-framework/stack";
36
36
  import { generateId } from "@cosmicdrift/kumiko-framework/utils";
37
- import { Temporal } from "temporal-polyfill";
37
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
38
38
  import { ADMIN_EMAIL, BETA_TENANT_ID, DEMO_NOTE_ID, DEV_TENANT_ID } from "./auth-constants";
39
39
  import { noteEntity } from "./notes-feature";
40
40
 
@@ -7,6 +7,6 @@
7
7
  "@cosmicdrift/kumiko-framework": "workspace:*"
8
8
  },
9
9
  "devDependencies": {
10
- "sharp": "^0.35.3"
10
+ "sharp": "^0.35.5"
11
11
  }
12
12
  }
@@ -5,7 +5,7 @@ File-based migrations for Event-Sourcing operations — like `kumiko schema appl
5
5
  ## What it shows
6
6
 
7
7
  - **`seedsDir`-Option** in `runProdApp({ seedsDir: "./seeds" })` — beim Boot werden pending Files in chronologischer Reihenfolge angewendet und in `kumiko_es_operations` markiert.
8
- - **`SeedMigration`-Interface** — default-Export einer seed-File mit `description` + `run(ctx)`. ctx liefert `systemWriteAs` (System-User bypassed Access-Check) + Read-Helpers (`findUserByEmail`, `findMembershipsOfUser`, `findTenants`).
8
+ - **`SeedMigration`-Interface** — default-Export einer seed-File mit `description` + `run(ctx)`. ctx liefert `systemWriteAs` (System-User bypassed Access-Check) + Read-Helpers (`findUserByEmail`, `findMembershipsOfUser`, `findTenants`, `findTemplateResources`).
9
9
  - **Idempotency** — Marker landet nach Erfolg in der Tracking-Tabelle, zweiter Boot skipped applied Seeds.
10
10
  - **Tx-Atomicity** — Jede Migration läuft in eigener Transaction; Failure rollt zurück + bricht Boot ab (kein Partial-Apply).
11
11
  - **Chronologische File-IDs** — Filename `<date>-<slug>.ts` (z.B. `2026-05-20-fix-admin-roles.ts`) ist die ID.
@@ -35,6 +35,18 @@ seed: 2026-05-20-fix-admin-roles.ts
35
35
 
36
36
  Bei typo / drift fail-t mit klarer message + exit-code 1 — CI nutzt es als prä-build-step. App-Author muss in `smoke.ts` den `features`-Array gegen die eigene App-Feature-Set tauschen (siehe TODO im File).
37
37
 
38
+ ### Archive template rows
39
+
40
+ `findTemplateResources({ slug?, kind?, status?, locale?, tenantId? })` replaces a raw `SELECT … FROM read_template_resources`. It defaults to the system tenant and returns `[]` when template-resolver is not mounted:
41
+
42
+ ```ts
43
+ for (const t of await ctx.findTemplateResources({ kind: "notification", status: "active" })) {
44
+ await ctx.systemWriteAs("template-resolver:write:archive", { id: t.id }, SYSTEM_TENANT_ID, [
45
+ "SystemAdmin",
46
+ ]);
47
+ }
48
+ ```
49
+
38
50
  ## When to reach for it
39
51
 
40
52
  Du hast einen idempotent-Seeder der bei initialer Erstellung „Insert wenn nicht existiert" macht (z.B. `auth.admin.memberships`), und dann ändern sich die Soll-Daten — neue Rolle, anderer Tier, korrigierte Bezeichnung. Idempotent-Seeder skippen die existing Rows → DB driftet vom Code-Stand ab.