@cosmicdrift/kumiko-samples 0.353.0 → 0.354.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/package.json +1 -1
  2. package/packages/bundled-features/package.json +18 -2
  3. package/packages/bundled-features/src/agent-tools/agent-doc-lint.ts +66 -0
  4. package/packages/bundled-features/src/agent-tools/agent-manifest.ts +11 -5
  5. package/packages/bundled-features/src/agent-tools/feature.ts +13 -2
  6. package/packages/bundled-features/src/agent-tools/index.ts +6 -1
  7. package/packages/bundled-features/src/agent-tools/tool-dispatch.ts +1 -1
  8. package/packages/bundled-features/src/auth-email-password/changes.json +24 -0
  9. package/packages/bundled-features/src/auth-email-password/handlers/change-password.write.ts +1 -0
  10. package/packages/bundled-features/src/auth-email-password/handlers/invite-accept-with-login.write.ts +3 -2
  11. package/packages/bundled-features/src/auth-email-password/handlers/invite-accept.write.ts +3 -2
  12. package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +21 -0
  13. package/packages/bundled-features/src/auth-email-password/handlers/invite-signup-complete.write.ts +4 -1
  14. package/packages/bundled-features/src/auth-email-password/handlers/login.write.ts +1 -0
  15. package/packages/bundled-features/src/auth-email-password/handlers/reset-password.write.ts +1 -0
  16. package/packages/bundled-features/src/auth-email-password/handlers/signup-confirm.write.ts +5 -0
  17. package/packages/bundled-features/src/auth-email-password/handlers/signup-request.write.ts +53 -96
  18. package/packages/bundled-features/src/auth-email-password/handlers/token-request-handler.ts +1 -0
  19. package/packages/bundled-features/src/auth-email-password/handlers/verify-email.write.ts +1 -0
  20. package/packages/bundled-features/src/auth-email-password/index.ts +12 -0
  21. package/packages/bundled-features/src/auth-email-password/seeding.ts +38 -7
  22. package/packages/bundled-features/src/auth-email-password/signup-activation.ts +83 -0
  23. package/packages/bundled-features/src/auth-email-password/signup-token-store.ts +9 -3
  24. package/packages/bundled-features/src/auth-email-password/web/auth-gate.tsx +7 -7
  25. package/packages/bundled-features/src/auth-email-password/web/auth-redirect.ts +15 -0
  26. package/packages/bundled-features/src/auth-email-password/web/login-screen.tsx +2 -2
  27. package/packages/bundled-features/src/auth-email-password/web/user-menu.tsx +1 -1
  28. package/packages/bundled-features/src/auth-mfa/changes.json +7 -0
  29. package/packages/bundled-features/src/auth-mfa/feature.ts +5 -0
  30. package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +1 -0
  31. package/packages/bundled-features/src/auth-mfa/handlers/verify.write.ts +1 -0
  32. package/packages/bundled-features/src/auth-mfa/web/mfa-recovery-codes-reveal.tsx +6 -8
  33. package/packages/bundled-features/src/auth-mfa/web/mfa-setup-preauth-screen.tsx +18 -21
  34. package/packages/bundled-features/src/auth-mfa/web/mfa-verify-screen.tsx +2 -2
  35. package/packages/bundled-features/src/billing-foundation/billing-info-query.ts +4 -4
  36. package/packages/bundled-features/src/billing-foundation/changes.json +6 -0
  37. package/packages/bundled-features/src/channel-in-app/changes.json +8 -1
  38. package/packages/bundled-features/src/channel-in-app/in-app-channel.ts +1 -1
  39. package/packages/bundled-features/src/channel-in-app/tables.ts +18 -12
  40. package/packages/bundled-features/src/crypto-shredding/changes.json +12 -0
  41. package/packages/bundled-features/src/crypto-shredding/handlers/forget-subject.write.ts +154 -69
  42. package/packages/bundled-features/src/delivery/changes.json +6 -0
  43. package/packages/bundled-features/src/delivery/db/queries/address-opt-outs.ts +18 -12
  44. package/packages/bundled-features/src/delivery/delivery-service.ts +103 -16
  45. package/packages/bundled-features/src/delivery/unsubscribe.ts +81 -13
  46. package/packages/bundled-features/src/feature-toggles/feature.ts +2 -3
  47. package/packages/bundled-features/src/feature-toggles/toggle-runtime.ts +34 -5
  48. package/packages/bundled-features/src/feature-toggles/toggle-sync-signal.ts +31 -8
  49. package/packages/bundled-features/src/file-derivatives/handlers/public-variant.query.ts +10 -1
  50. package/packages/bundled-features/src/files/changes.json +24 -0
  51. package/packages/bundled-features/src/files-tenant-data/changes.json +5 -0
  52. package/packages/bundled-features/src/files-tenant-data/handlers/sweep-orphaned-derivatives.job.ts +4 -0
  53. package/packages/bundled-features/src/files-tenant-data/hooks.ts +72 -23
  54. package/packages/bundled-features/src/folders/changes.json +6 -0
  55. package/packages/bundled-features/src/folders/handlers/delete-folder.write.ts +41 -9
  56. package/packages/bundled-features/src/foundation-shared/index.ts +1 -0
  57. package/packages/bundled-features/src/foundation-shared/mail-host-policy.ts +16 -4
  58. package/packages/bundled-features/src/inbound-provider-imap/changes.json +6 -0
  59. package/packages/bundled-features/src/inbound-provider-imap/feature.ts +2 -0
  60. package/packages/bundled-features/src/inbound-provider-imap/imap-client.ts +3 -4
  61. package/packages/bundled-features/src/jobs/changes.json +30 -0
  62. package/packages/bundled-features/src/jobs/constants.ts +1 -0
  63. package/packages/bundled-features/src/jobs/db/queries/retention.ts +12 -0
  64. package/packages/bundled-features/src/jobs/db/queries/stale-run-sweep.ts +26 -2
  65. package/packages/bundled-features/src/jobs/feature.ts +7 -1
  66. package/packages/bundled-features/src/jobs/handlers/list.query.ts +1 -1
  67. package/packages/bundled-features/src/jobs/handlers/tenant-failures.query.ts +1 -1
  68. package/packages/bundled-features/src/jobs/handlers/tenant-runs.query.ts +94 -0
  69. package/packages/bundled-features/src/jobs/index.ts +2 -0
  70. package/packages/bundled-features/src/jobs/job-run-logger.ts +177 -10
  71. package/packages/bundled-features/src/jobs/tenant-job-failure-table.ts +27 -8
  72. package/packages/bundled-features/src/jobs/tenant-job-run-table.ts +55 -0
  73. package/packages/bundled-features/src/ledger/changes.json +23 -0
  74. package/packages/bundled-features/src/ledger/entity.ts +13 -5
  75. package/packages/bundled-features/src/ledger/feature.ts +3 -0
  76. package/packages/bundled-features/src/ledger/handlers/confirm-schedule-period.write.ts +15 -3
  77. package/packages/bundled-features/src/ledger/handlers/create-transaction.write.ts +1 -0
  78. package/packages/bundled-features/src/ledger/handlers/reverse-transaction.write.ts +1 -0
  79. package/packages/bundled-features/src/ledger/schedule-subject-pair.ts +27 -0
  80. package/packages/bundled-features/src/mail-transport-smtp/feature.ts +4 -15
  81. package/packages/bundled-features/src/notes-history/changes.json +13 -0
  82. package/packages/bundled-features/src/notes-history/entity.ts +4 -0
  83. package/packages/bundled-features/src/personal-access-tokens/handlers/create.write.ts +1 -0
  84. package/packages/bundled-features/src/sessions/feature.ts +21 -8
  85. package/packages/bundled-features/src/sessions/handlers/detail.query.ts +34 -33
  86. package/packages/bundled-features/src/sessions/handlers/list.query.ts +68 -65
  87. package/packages/bundled-features/src/sessions/screens.ts +10 -8
  88. package/packages/bundled-features/src/shared/encrypt-for-direct-write.ts +15 -8
  89. package/packages/bundled-features/src/shared/index.ts +2 -1
  90. package/packages/bundled-features/src/shared/parent-visibility.ts +31 -1
  91. package/packages/bundled-features/src/shared/row-bound-grant.ts +18 -2
  92. package/packages/bundled-features/src/shared/session-locale-field.ts +7 -0
  93. package/packages/bundled-features/src/template-resolver/api.ts +3 -3
  94. package/packages/bundled-features/src/template-resolver/changes.json +5 -0
  95. package/packages/bundled-features/src/template-resolver/handlers/list.query.ts +2 -2
  96. package/packages/bundled-features/src/tenant/changes.json +12 -0
  97. package/packages/bundled-features/src/tenant/feature.ts +8 -1
  98. package/packages/bundled-features/src/tenant/handlers/toggle-enabled.write.ts +2 -0
  99. package/packages/bundled-features/src/tenant/handlers/update-member-roles.write.ts +1 -0
  100. package/packages/bundled-features/src/tenant/invitation-table.ts +6 -0
  101. package/packages/bundled-features/src/tenant/invited-membership.ts +45 -12
  102. package/packages/bundled-features/src/tenant-handover/changes.json +6 -0
  103. package/packages/bundled-features/src/tenant-handover/transfer-graph.ts +14 -96
  104. package/packages/bundled-features/src/tenant-lifecycle/boot-checks.ts +16 -0
  105. package/packages/bundled-features/src/tenant-lifecycle/changes.json +22 -0
  106. package/packages/bundled-features/src/tenant-lifecycle/constants.ts +8 -0
  107. package/packages/bundled-features/src/tenant-lifecycle/events.ts +6 -0
  108. package/packages/bundled-features/src/tenant-lifecycle/feature.ts +13 -1
  109. package/packages/bundled-features/src/tenant-lifecycle/run-tenant-destroy.ts +100 -29
  110. package/packages/bundled-features/src/tenant-lifecycle/stages.ts +41 -9
  111. package/packages/bundled-features/src/tenant-lifecycle/testing.ts +58 -0
  112. package/packages/bundled-features/src/tier-engine/changes.json +5 -0
  113. package/packages/bundled-features/src/tier-engine/handlers/set-tenant-tier.write.ts +1 -0
  114. package/packages/bundled-features/src/user/changes.json +15 -0
  115. package/packages/bundled-features/src/user/handlers/detail.query.ts +1 -0
  116. package/packages/bundled-features/src/user/handlers/list.query.ts +3 -0
  117. package/packages/bundled-features/src/user/handlers/update.write.ts +1 -0
  118. package/packages/bundled-features/src/user/seeding.ts +34 -0
  119. package/packages/bundled-features/src/user-data-rights/changes.json +6 -0
  120. package/packages/bundled-features/src/user-data-rights/handlers/request-deletion.write.ts +1 -0
  121. package/packages/bundled-features/src/user-data-rights/index.ts +1 -0
  122. package/packages/bundled-features/src/user-data-rights/schema/download-attempt.ts +17 -9
  123. package/packages/bundled-features/src/user-profile/handlers/change-email.write.ts +8 -2
  124. package/packages/bundled-features/src/workflow-runner/event-trigger.ts +72 -14
  125. package/packages/bundled-features/src/workflow-runner/handlers/resume-run.write.ts +6 -1
  126. package/packages/bundled-features/src/workflow-runner/runner.ts +8 -2
  127. package/samples/apps/showcase/src/features/items/feature.ts +2 -1
  128. package/samples/apps/styleguide/src/features/widgets/feature.ts +83 -0
  129. package/samples/apps/styleguide/src/features/widgets/i18n.ts +12 -0
  130. package/samples/apps/styleguide/src/features/widgets/web/Widgets.tsx +4 -0
  131. package/samples/apps/ui-walkthrough/package.json +3 -0
  132. package/samples/apps/use-all-bundled/README.md +1 -2
  133. package/samples/apps/use-all-bundled/feature-manifest.json +1 -1
  134. package/samples/apps/user-data-rights-demo/README.md +1 -3
  135. package/samples/recipes/legal-pages/README.md +3 -4
  136. package/samples/recipes/managed-pages/README.md +2 -2
  137. package/samples/recipes/seed-migration/README.md +0 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-samples",
3
- "version": "0.353.0",
3
+ "version": "0.354.1",
4
4
  "description": "Source trees of the Kumiko sample recipes, sample apps and bundled features in repo layout, for tooling such as few-shot corpus builds.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-bundled-features",
3
- "version": "0.353.0",
3
+ "version": "0.354.1",
4
4
  "description": "Built-in features — tenant, user, auth, delivery. The stuff you'd rewrite anyway, already typed.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -17,7 +17,8 @@
17
17
  "kumiko": {
18
18
  "runtime": "runtime",
19
19
  "bunOnlyExports": [
20
- "./template-resolver/testing"
20
+ "./template-resolver/testing",
21
+ "./tenant-lifecycle/testing"
21
22
  ]
22
23
  },
23
24
  "exports": {
@@ -81,12 +82,14 @@
81
82
  "./files-tenant-data": "./src/files-tenant-data/index.ts",
82
83
  "./user-data-rights": "./src/user-data-rights/index.ts",
83
84
  "./user-data-rights/web": "./src/user-data-rights/web/index.ts",
85
+ "./user-data-rights/email-templates": "./src/user-data-rights/email-templates.ts",
84
86
  "./user-data-rights-defaults": "./src/user-data-rights-defaults/index.ts",
85
87
  "./tenant": "./src/tenant/index.ts",
86
88
  "./tenant/constants": "./src/tenant/constants.ts",
87
89
  "./tenant/web": "./src/tenant/web/index.ts",
88
90
  "./tenant/seeding": "./src/tenant/seeding.ts",
89
91
  "./tenant-lifecycle": "./src/tenant-lifecycle/index.ts",
92
+ "./tenant-lifecycle/testing": "./src/tenant-lifecycle/testing.ts",
90
93
  "./tenant-handover": "./src/tenant-handover/index.ts",
91
94
  "./tenant/testing": "./src/tenant/testing.ts",
92
95
  "./user": "./src/user/index.ts",
@@ -96,6 +99,7 @@
96
99
  "./user-profile/web": "./src/user-profile/web/index.ts",
97
100
  "./auth-email-password": "./src/auth-email-password/index.ts",
98
101
  "./auth-email-password/constants": "./src/auth-email-password/constants.ts",
102
+ "./auth-email-password/email-templates": "./src/auth-email-password/email-templates.ts",
99
103
  "./auth-email-password/seeding": "./src/auth-email-password/seeding.ts",
100
104
  "./auth-email-password/testing": "./src/auth-email-password/testing.ts",
101
105
  "./auth-email-password/web": "./src/auth-email-password/web/index.ts",
@@ -411,6 +415,10 @@
411
415
  "types": "./dist/user-data-rights/index.d.ts",
412
416
  "default": "./dist/user-data-rights/index.js"
413
417
  },
418
+ "./user-data-rights/email-templates": {
419
+ "types": "./dist/user-data-rights/email-templates.d.ts",
420
+ "default": "./dist/user-data-rights/email-templates.js"
421
+ },
414
422
  "./user-data-rights/web": {
415
423
  "types": "./dist/user-data-rights/web/index.d.ts",
416
424
  "default": "./dist/user-data-rights/web/index.js"
@@ -439,6 +447,10 @@
439
447
  "types": "./dist/tenant-lifecycle/index.d.ts",
440
448
  "default": "./dist/tenant-lifecycle/index.js"
441
449
  },
450
+ "./tenant-lifecycle/testing": {
451
+ "types": "./dist/tenant-lifecycle/testing.d.ts",
452
+ "default": "./dist/tenant-lifecycle/testing.js"
453
+ },
442
454
  "./tenant-handover": {
443
455
  "types": "./dist/tenant-handover/index.d.ts",
444
456
  "default": "./dist/tenant-handover/index.js"
@@ -471,6 +483,10 @@
471
483
  "types": "./dist/auth-email-password/index.d.ts",
472
484
  "default": "./dist/auth-email-password/index.js"
473
485
  },
486
+ "./auth-email-password/email-templates": {
487
+ "types": "./dist/auth-email-password/email-templates.d.ts",
488
+ "default": "./dist/auth-email-password/email-templates.js"
489
+ },
474
490
  "./auth-email-password/constants": {
475
491
  "types": "./dist/auth-email-password/constants.d.ts",
476
492
  "default": "./dist/auth-email-password/constants.js"
@@ -4,6 +4,8 @@ import type {
4
4
  WriteHandlerDef,
5
5
  } from "@cosmicdrift/kumiko-framework/engine";
6
6
  import {
7
+ handlerFieldLabelKey,
8
+ handlerTitleKey,
7
9
  isAgentVisibleScreen,
8
10
  QnTypes,
9
11
  qn,
@@ -11,12 +13,14 @@ import {
11
13
  toKebab,
12
14
  } from "@cosmicdrift/kumiko-framework/engine";
13
15
  import * as z from "zod";
16
+ import { systemOnlyPropertyNames } from "./agent-manifest.js";
14
17
 
15
18
  export const AgentDocGapKinds = {
16
19
  handlerSchemaNotExpressible: "handler-schema-not-expressible",
17
20
  handlerWithoutDescription: "handler-without-description",
18
21
  customScreenWithoutDescription: "custom-screen-without-description",
19
22
  exposedEntityWithoutDescription: "exposed-entity-without-description",
23
+ handlerWithoutTranslation: "handler-without-translation",
20
24
  } as const;
21
25
 
22
26
  export type AgentDocGapKind = (typeof AgentDocGapKinds)[keyof typeof AgentDocGapKinds];
@@ -144,6 +148,58 @@ function exposedEntityDocGaps(feature: FeatureDefinition): readonly AgentDocGap[
144
148
  return gaps;
145
149
  }
146
150
 
151
+ function actionFormHandlerQns(features: readonly FeatureDefinition[]): ReadonlySet<string> {
152
+ const handlerQns = new Set<string>();
153
+ for (const feature of features) {
154
+ for (const screen of Object.values(feature.screens ?? {})) {
155
+ if (screen.type === "actionForm") handlerQns.add(screen.handler);
156
+ }
157
+ }
158
+ return handlerQns;
159
+ }
160
+
161
+ function agentVisibleInputFieldNames(schema: z.ZodType): readonly string[] {
162
+ if (!(schema instanceof z.ZodObject)) return [];
163
+ const fieldNames = Object.keys(schema.shape);
164
+ try {
165
+ const systemOnly = new Set(systemOnlyPropertyNames(z.toJSONSchema(schema, { io: "input" })));
166
+ return fieldNames.filter((field) => !systemOnly.has(field));
167
+ } catch {
168
+ // skip: a schema without JSON Schema form is reported as its own gap kind
169
+ return fieldNames;
170
+ }
171
+ }
172
+
173
+ function translationGapsOfFeature(
174
+ feature: FeatureDefinition,
175
+ coveredHandlerQns: ReadonlySet<string>,
176
+ ): readonly AgentDocGap[] {
177
+ const gaps: AgentDocGap[] = [];
178
+ // The client resolves translation keys verbatim (no feature prefix), so only
179
+ // fully spelled-out keys count.
180
+ const translated = new Set(Object.keys(feature.translations ?? {}));
181
+ for (const [name, def] of Object.entries(feature.writeHandlers ?? {})) {
182
+ if (!resolveAgentExposure(def, "write").expose) continue;
183
+ if (feature.handlerEntityMappings?.[name] !== undefined) continue;
184
+ const handlerQn = qn(toKebab(feature.name), QnTypes.write, toKebab(name));
185
+ if (coveredHandlerQns.has(handlerQn)) continue;
186
+ const missing = [
187
+ handlerTitleKey(handlerQn),
188
+ ...agentVisibleInputFieldNames(def.schema).map((field) =>
189
+ handlerFieldLabelKey(handlerQn, field),
190
+ ),
191
+ ].filter((key) => !translated.has(key));
192
+ if (missing.length === 0) continue;
193
+ gaps.push({
194
+ qn: handlerQn,
195
+ feature: feature.name,
196
+ kind: AgentDocGapKinds.handlerWithoutTranslation,
197
+ message: `This write handler has no entity and no actionForm screen, so its title and field labels can only come from translations — missing keys: ${missing.join(", ")}.`,
198
+ });
199
+ }
200
+ return gaps;
201
+ }
202
+
147
203
  export function findAgentDocGaps(features: readonly FeatureDefinition[]): readonly AgentDocGap[] {
148
204
  const gaps: AgentDocGap[] = [];
149
205
  for (const feature of features) {
@@ -161,6 +217,16 @@ export function findAgentDocGaps(features: readonly FeatureDefinition[]): readon
161
217
  return [...gaps].sort((a, b) => compareByCodePoint(a.qn, b.qn));
162
218
  }
163
219
 
220
+ // Kept out of findAgentDocGaps on purpose: the published agent-manifest guard
221
+ // fails on every gap that function returns, and these are advisory.
222
+ export function findHandlerTranslationGaps(
223
+ features: readonly FeatureDefinition[],
224
+ ): readonly AgentDocGap[] {
225
+ const coveredHandlerQns = actionFormHandlerQns(features);
226
+ const gaps = features.flatMap((feature) => translationGapsOfFeature(feature, coveredHandlerQns));
227
+ return [...gaps].sort((a, b) => compareByCodePoint(a.qn, b.qn));
228
+ }
229
+
164
230
  export function formatAgentDocGap(gap: AgentDocGap): string {
165
231
  return `${gap.qn} — ${gap.message}`;
166
232
  }
@@ -194,16 +194,22 @@ function buildFeatures(
194
194
 
195
195
  // Agent dispatch never runs as a system identity, so properties the engine only
196
196
  // honors for system callers would be silently dropped — hide them from the tool.
197
- function withoutSystemOnlyProperties(
198
- jsonSchema: Record<string, unknown>,
199
- ): Readonly<Record<string, unknown>> {
197
+ export function systemOnlyPropertyNames(jsonSchema: Record<string, unknown>): readonly string[] {
200
198
  const properties = jsonSchema["properties"];
201
- if (typeof properties !== "object" || properties === null) return jsonSchema;
202
- const hidden = Object.entries(properties)
199
+ if (typeof properties !== "object" || properties === null) return [];
200
+ return Object.entries(properties)
203
201
  .filter(
204
202
  ([, property]) => (property as Record<string, unknown>)[SYSTEM_ONLY_JSON_SCHEMA_KEY] === true,
205
203
  )
206
204
  .map(([name]) => name);
205
+ }
206
+
207
+ function withoutSystemOnlyProperties(
208
+ jsonSchema: Record<string, unknown>,
209
+ ): Readonly<Record<string, unknown>> {
210
+ const properties = jsonSchema["properties"];
211
+ if (typeof properties !== "object" || properties === null) return jsonSchema;
212
+ const hidden = systemOnlyPropertyNames(jsonSchema);
207
213
  if (hidden.length === 0) return jsonSchema;
208
214
  const required = jsonSchema["required"];
209
215
  return {
@@ -1,5 +1,9 @@
1
1
  import { defineFeature, type FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
2
- import { findAgentDocGaps, formatAgentDocGap } from "./agent-doc-lint.js";
2
+ import {
3
+ findAgentDocGaps,
4
+ findHandlerTranslationGaps,
5
+ formatAgentDocGap,
6
+ } from "./agent-doc-lint.js";
3
7
 
4
8
  export const AGENT_TOOLS_FEATURE_NAME = "agent-tools";
5
9
 
@@ -19,8 +23,15 @@ export function createAgentToolsFeature(): FeatureDefinition {
19
23
  // would turn every incrementally-documented app into an outage.
20
24
  r.bootCheck(({ features }) => {
21
25
  const gaps = findAgentDocGaps(features);
26
+ const warningCount = findHandlerTranslationGaps(features).length;
27
+ if (warningCount > 0) {
28
+ // biome-ignore lint/suspicious/noConsole: operator-visibility at boot for agent-doc gaps
29
+ console.warn(
30
+ `[agent-tools] ${warningCount} agent-exposed write handler(s) lack title/field-label translations — run \`kumiko agent lint\` for details.`,
31
+ );
32
+ }
22
33
  if (gaps.length === 0) {
23
- // skip: no documentation gaps, nothing to warn about
34
+ // skip: no blocking documentation gaps, nothing more to warn about
24
35
  return;
25
36
  }
26
37
  // biome-ignore lint/suspicious/noConsole: operator-visibility at boot for agent-doc gaps
@@ -1,5 +1,10 @@
1
1
  export type { AgentDocGap, AgentDocGapKind } from "./agent-doc-lint.js";
2
- export { AgentDocGapKinds, findAgentDocGaps, formatAgentDocGap } from "./agent-doc-lint.js";
2
+ export {
3
+ AgentDocGapKinds,
4
+ findAgentDocGaps,
5
+ findHandlerTranslationGaps,
6
+ formatAgentDocGap,
7
+ } from "./agent-doc-lint.js";
3
8
  export { buildAgentManifest } from "./agent-manifest.js";
4
9
  export { AGENT_TOOLS_FEATURE_NAME, createAgentToolsFeature } from "./feature.js";
5
10
  export { buildToolCatalog, OPEN_FORM_TOOL_NAME, toolNameForQn } from "./tool-catalog.js";
@@ -1,5 +1,5 @@
1
- import { runAsDirectCallEntry } from "@cosmicdrift/kumiko-framework/api";
2
1
  import type { SessionUser, WriteResult } from "@cosmicdrift/kumiko-framework/engine";
2
+ import { runAsDirectCallEntry } from "@cosmicdrift/kumiko-framework/internal/request-context";
3
3
  import { FILTER_OPS, isRecord } from "./filter-ops.js";
4
4
  import type { ToolDispatchDescriptor } from "./types.js";
5
5
 
@@ -1,4 +1,28 @@
1
1
  [
2
+ {
3
+ "version": "0.354.0",
4
+ "type": "breaking",
5
+ "title": "Invitations pin the invitee's membership version; superseded accept no longer relies on timestamps",
6
+ "migration": "New nullable column read_tenant_invitations.membership_version: run `kumiko schema generate` and apply the migration before deploying. Invitations issued before the migration keep the timestamp comparison."
7
+ },
8
+ {
9
+ "version": "0.354.0",
10
+ "type": "fix",
11
+ "title": "Login, MFA verify and MFA preauth setup follow the server landingPath when no valid next parameter is present",
12
+ "detail": "AuthGate onSuccess receives the landing path. A validated next parameter still wins; a landing path equal to the current path is not followed, so the screen does not reload itself."
13
+ },
14
+ {
15
+ "version": "0.354.0",
16
+ "type": "fix",
17
+ "title": "Signup-confirm and invite-signup-complete store the request locale on the new user and in the session"
18
+ },
19
+ {
20
+ "version": "0.354.0",
21
+ "type": "fix",
22
+ "title": "INITIAL_SIGNUP_ROLES stays [\"TenantAdmin\", \"Admin\"] as a transition",
23
+ "detail": "New self-signup owners hold TenantAdmin plus the legacy Admin literal, because handlers that gate on the Admin literal would otherwise lock them out. Both roles show in Team, Members until those handlers gate on access.admin. This supersedes the earlier note that the owner role is only TenantAdmin.",
24
+ "migration": "Move your own handlers that gate on the Admin literal to access.admin; after that the Admin entry can be dropped from the constant."
25
+ },
2
26
  {
3
27
  "version": "0.348.0",
4
28
  "type": "fix",
@@ -22,6 +22,7 @@ export const changePasswordWrite = defineWriteHandler({
22
22
  }),
23
23
  access: { roles: access.authenticated },
24
24
  escapeHatch: {
25
+ grants: ["systemIdentity"],
25
26
  reason:
26
27
  "Reads the caller's own passwordHash (privileged-only field) via ctx.queryAs(SYSTEM, ...) " +
27
28
  "and writes the new hash via ctx.writeAs(SYSTEM, user:update) — the caller's own " +
@@ -41,7 +41,7 @@ import {
41
41
  tenantInvitationsTable,
42
42
  } from "../../tenant/invitation-table.js";
43
43
  // kumiko-lint-ignore cross-feature-import membership grant for a privileged cross-tenant add
44
- import { grantInvitedMembershipRole, invitationIssuedAt } from "../../tenant/invited-membership.js";
44
+ import { grantInvitedMembershipRole } from "../../tenant/invited-membership.js";
45
45
  // kumiko-lint-ignore cross-feature-import global roles granted on invite accept live on the user row
46
46
  import {
47
47
  grantInvitedGlobalRoles,
@@ -150,6 +150,7 @@ export function createInviteAcceptWithLoginHandler(opts: InviteAcceptWithLoginOp
150
150
  readonly role: string;
151
151
  readonly globalRoles: unknown;
152
152
  readonly version: number;
153
+ readonly membershipVersion: number | null;
153
154
  readonly insertedAt: Temporal.Instant;
154
155
  readonly modifiedAt: Temporal.Instant | null;
155
156
  };
@@ -224,7 +225,7 @@ export function createInviteAcceptWithLoginHandler(opts: InviteAcceptWithLoginOp
224
225
  userId,
225
226
  tenantId: invitationTenantId,
226
227
  role: invitationRole,
227
- invitationIssuedAt: invitationIssuedAt(invitation),
228
+ invitation,
228
229
  });
229
230
  if (!grant.isSuccess) return grant;
230
231
 
@@ -33,7 +33,7 @@ import {
33
33
  tenantInvitationsTable,
34
34
  } from "../../tenant/invitation-table.js";
35
35
  // kumiko-lint-ignore cross-feature-import membership grant for a privileged cross-tenant add (like provisionSignupAccount)
36
- import { grantInvitedMembershipRole, invitationIssuedAt } from "../../tenant/invited-membership.js";
36
+ import { grantInvitedMembershipRole } from "../../tenant/invited-membership.js";
37
37
  // kumiko-lint-ignore cross-feature-import global roles granted on invite accept live on the user row
38
38
  import {
39
39
  grantInvitedGlobalRoles,
@@ -110,6 +110,7 @@ export function createInviteAcceptHandler() {
110
110
  readonly role: string;
111
111
  readonly globalRoles: unknown;
112
112
  readonly version: number;
113
+ readonly membershipVersion: number | null;
113
114
  readonly insertedAt: Temporal.Instant;
114
115
  readonly modifiedAt: Temporal.Instant | null;
115
116
  };
@@ -154,7 +155,7 @@ export function createInviteAcceptHandler() {
154
155
  userId: event.user.id,
155
156
  tenantId: invitationTenantId,
156
157
  role: invitationRole,
157
- invitationIssuedAt: invitationIssuedAt(invitation),
158
+ invitation,
158
159
  });
159
160
  if (!grant.isSuccess) return grant;
160
161
  const { alreadyMember } = grant.data;
@@ -40,6 +40,8 @@ import {
40
40
  tenantInvitationEntity,
41
41
  tenantInvitationsTable,
42
42
  } from "../../tenant/invitation-table.js";
43
+ // kumiko-lint-ignore cross-feature-import membership version read is owned by the tenant feature
44
+ import { membershipVersionOf } from "../../tenant/invited-membership.js";
43
45
  // kumiko-lint-ignore cross-feature-import membership-role validation owned by tenant-feature
44
46
  import {
45
47
  findForbiddenMembershipRole,
@@ -49,6 +51,8 @@ import {
49
51
  } from "../../tenant/membership-roles.js";
50
52
  // kumiko-lint-ignore cross-feature-import global-role options are owned by the user feature
51
53
  import { GLOBAL_ROLE_OPTIONS } from "../../user/constants.js";
54
+ // kumiko-lint-ignore cross-feature-import invitee lookup by email reads the user row
55
+ import { userTable } from "../../user/schema/user.js";
52
56
  import { AUTH_INVITE_DEFAULT_TTL_MINUTES } from "../constants.js";
53
57
  import type { AuthMailLocale } from "../email-templates.js";
54
58
  import { renderInviteEmail } from "../email-templates.js";
@@ -144,6 +148,20 @@ function checkAssignableInviteRole(
144
148
  return undefined;
145
149
  }
146
150
 
151
+ // 0 = the invitee has no membership in the tenant yet (or no account at all).
152
+ async function currentMembershipVersion(
153
+ ctx: HandlerContext,
154
+ tenantId: string,
155
+ email: string,
156
+ ): Promise<number> {
157
+ const user = await ctx.db.global(userTable).fetchOne<{ readonly id: string }>({
158
+ email,
159
+ isDeleted: false,
160
+ });
161
+ if (!user) return 0;
162
+ return membershipVersionOf(ctx.db, tenantId, user.id);
163
+ }
164
+
147
165
  async function issueInvitation(
148
166
  opts: InviteCreateOptions,
149
167
  ttlSeconds: number,
@@ -166,6 +184,7 @@ async function issueInvitation(
166
184
  // The unique index allows one row per (tenantId, email). Whatever its
167
185
  // status, a re-invite resets it to pending with a fresh token.
168
186
  const existing = await ctx.db.fetchOne(tenantInvitationsTable, { tenantId, email });
187
+ const membershipVersion = await currentMembershipVersion(ctx, tenantId, email);
169
188
 
170
189
  let invitationId: string;
171
190
  if (existing) {
@@ -193,6 +212,7 @@ async function issueInvitation(
193
212
  globalRoles: [...request.globalRoles],
194
213
  status: INVITATION_STATUS.pending,
195
214
  invitedBy: inviter.id,
215
+ membershipVersion,
196
216
  expiresAt,
197
217
  },
198
218
  },
@@ -208,6 +228,7 @@ async function issueInvitation(
208
228
  globalRoles: [...request.globalRoles],
209
229
  status: INVITATION_STATUS.pending,
210
230
  invitedBy: inviter.id,
231
+ membershipVersion,
211
232
  expiresAt,
212
233
  },
213
234
  inviter,
@@ -32,7 +32,7 @@ import {
32
32
  } from "@cosmicdrift/kumiko-framework/engine";
33
33
  import { InternalError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
34
34
  import * as z from "zod";
35
- import { decryptStoredPii } from "../../shared/index.js";
35
+ import { decryptStoredPii, registrationLocaleFromRequest } from "../../shared/index.js";
36
36
  // kumiko-lint-ignore cross-feature-import invite-flow
37
37
  import {
38
38
  INVITATION_STATUS,
@@ -161,11 +161,13 @@ export function createInviteSignupCompleteHandler(opts: InviteSignupCompleteOpti
161
161
  // @cast-boundary db-runner — helpers use only the query API that
162
162
  // DbConnection and DbTx share.
163
163
  const dbConn = ctx.db.unsafeRaw() as DbConnection;
164
+ const registrationLocale = registrationLocaleFromRequest();
164
165
  const { id: userId } = await seedUserWithPassword(dbConn, {
165
166
  email: invitationEmail,
166
167
  password: event.payload.password,
167
168
  displayName: invitationEmail.split("@")[0] ?? invitationEmail,
168
169
  emailVerified: true,
170
+ ...registrationLocale,
169
171
  ...(invitationGlobalRoles.length > 0 && { roles: invitationGlobalRoles }),
170
172
  });
171
173
 
@@ -221,6 +223,7 @@ export function createInviteSignupCompleteHandler(opts: InviteSignupCompleteOpti
221
223
  id: userId,
222
224
  tenantId: invitationTenantId,
223
225
  roles: mergedRoles,
226
+ ...registrationLocale,
224
227
  };
225
228
 
226
229
  committed = true;
@@ -277,6 +277,7 @@ export function createLoginHandler(opts: LoginHandlerOptions = {}) {
277
277
  access: { roles: ["anonymous"] },
278
278
  rateLimit: { per: "ip+handler", limit: 20, windowSeconds: 60 },
279
279
  escapeHatch: {
280
+ grants: ["systemIdentity", "unsafeRaw"],
280
281
  reason:
281
282
  "Unauthenticated login has no caller identity yet — it looks up the user row by " +
282
283
  "email via ctx.queryAs(SYSTEM, ...) and resolves tenant membership via " +
@@ -28,6 +28,7 @@ export function createResetPasswordHandler(opts: ResetPasswordOptions) {
28
28
  access: { roles: ["anonymous"] },
29
29
  rateLimit: { per: "ip+handler", limit: 20, windowSeconds: 60 },
30
30
  escapeHatch: {
31
+ grants: ["systemIdentity"],
31
32
  reason:
32
33
  "Anonymous token holder has no session — runConfirmTokenFlow loads and updates the " +
33
34
  "target user via ctx.writeAs(SYSTEM, user:update) to set the new password hash.",
@@ -39,6 +39,7 @@ import { generateId } from "@cosmicdrift/kumiko-framework/utils";
39
39
  import * as z from "zod";
40
40
  import {
41
41
  findSignupHandoverProvider,
42
+ registrationLocaleFromRequest,
42
43
  SIGNUP_HANDOVER_BENIGN_CLAIM_REJECTION_CODE,
43
44
  type SignupHandoverBinding,
44
45
  } from "../../shared/index.js";
@@ -186,6 +187,8 @@ export function createSignupConfirmHandler(opts: SignupConfirmOptions = {}) {
186
187
  // den Tenant-Namen + sein eigenes displayName später ändern.
187
188
  const displayName = email.split("@")[0] ?? email;
188
189
 
190
+ const registrationLocale = registrationLocaleFromRequest();
191
+
189
192
  let provisioned: { readonly userId: string; readonly tenantId: TenantId };
190
193
  try {
191
194
  provisioned = await provisionSignupAccount(
@@ -198,6 +201,7 @@ export function createSignupConfirmHandler(opts: SignupConfirmOptions = {}) {
198
201
  tenantKey,
199
202
  // Generated slug as default name: no personal data (the email) in the tenant name.
200
203
  tenantName: tenantKey,
204
+ ...registrationLocale,
201
205
  },
202
206
  // #1463: seedTenant's postSave hooks (tier-engine's auto-default-
203
207
  // tier, an app's auto-default-compliance) must fire on self-signup
@@ -222,6 +226,7 @@ export function createSignupConfirmHandler(opts: SignupConfirmOptions = {}) {
222
226
  id: provisioned.userId,
223
227
  tenantId: provisioned.tenantId,
224
228
  roles: [...INITIAL_SIGNUP_ROLES],
229
+ ...registrationLocale,
225
230
  };
226
231
 
227
232
  // Claim BEFORE spending the signup token or the binding below and