@cosmicdrift/kumiko-samples 0.353.0 → 0.354.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/packages/bundled-features/package.json +18 -2
- package/packages/bundled-features/src/agent-tools/agent-doc-lint.ts +66 -0
- package/packages/bundled-features/src/agent-tools/agent-manifest.ts +11 -5
- package/packages/bundled-features/src/agent-tools/feature.ts +13 -2
- package/packages/bundled-features/src/agent-tools/index.ts +6 -1
- package/packages/bundled-features/src/agent-tools/tool-dispatch.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/changes.json +24 -0
- package/packages/bundled-features/src/auth-email-password/handlers/change-password.write.ts +1 -0
- package/packages/bundled-features/src/auth-email-password/handlers/invite-accept-with-login.write.ts +3 -2
- package/packages/bundled-features/src/auth-email-password/handlers/invite-accept.write.ts +3 -2
- package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +21 -0
- package/packages/bundled-features/src/auth-email-password/handlers/invite-signup-complete.write.ts +4 -1
- package/packages/bundled-features/src/auth-email-password/handlers/login.write.ts +1 -0
- package/packages/bundled-features/src/auth-email-password/handlers/reset-password.write.ts +1 -0
- package/packages/bundled-features/src/auth-email-password/handlers/signup-confirm.write.ts +5 -0
- package/packages/bundled-features/src/auth-email-password/handlers/signup-request.write.ts +53 -96
- package/packages/bundled-features/src/auth-email-password/handlers/token-request-handler.ts +1 -0
- package/packages/bundled-features/src/auth-email-password/handlers/verify-email.write.ts +1 -0
- package/packages/bundled-features/src/auth-email-password/index.ts +12 -0
- package/packages/bundled-features/src/auth-email-password/seeding.ts +38 -7
- package/packages/bundled-features/src/auth-email-password/signup-activation.ts +83 -0
- package/packages/bundled-features/src/auth-email-password/signup-token-store.ts +9 -3
- package/packages/bundled-features/src/auth-email-password/web/auth-gate.tsx +7 -7
- package/packages/bundled-features/src/auth-email-password/web/auth-redirect.ts +15 -0
- package/packages/bundled-features/src/auth-email-password/web/login-screen.tsx +2 -2
- package/packages/bundled-features/src/auth-email-password/web/user-menu.tsx +1 -1
- package/packages/bundled-features/src/auth-mfa/changes.json +7 -0
- package/packages/bundled-features/src/auth-mfa/feature.ts +5 -0
- package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +1 -0
- package/packages/bundled-features/src/auth-mfa/handlers/verify.write.ts +1 -0
- package/packages/bundled-features/src/auth-mfa/web/mfa-recovery-codes-reveal.tsx +6 -8
- package/packages/bundled-features/src/auth-mfa/web/mfa-setup-preauth-screen.tsx +18 -21
- package/packages/bundled-features/src/auth-mfa/web/mfa-verify-screen.tsx +2 -2
- package/packages/bundled-features/src/billing-foundation/billing-info-query.ts +4 -4
- package/packages/bundled-features/src/billing-foundation/changes.json +6 -0
- package/packages/bundled-features/src/channel-in-app/changes.json +8 -1
- package/packages/bundled-features/src/channel-in-app/in-app-channel.ts +1 -1
- package/packages/bundled-features/src/channel-in-app/tables.ts +18 -12
- package/packages/bundled-features/src/crypto-shredding/changes.json +12 -0
- package/packages/bundled-features/src/crypto-shredding/handlers/forget-subject.write.ts +154 -69
- package/packages/bundled-features/src/delivery/changes.json +6 -0
- package/packages/bundled-features/src/delivery/db/queries/address-opt-outs.ts +18 -12
- package/packages/bundled-features/src/delivery/delivery-service.ts +103 -16
- package/packages/bundled-features/src/delivery/unsubscribe.ts +81 -13
- package/packages/bundled-features/src/feature-toggles/feature.ts +2 -3
- package/packages/bundled-features/src/feature-toggles/toggle-runtime.ts +34 -5
- package/packages/bundled-features/src/feature-toggles/toggle-sync-signal.ts +31 -8
- package/packages/bundled-features/src/file-derivatives/handlers/public-variant.query.ts +10 -1
- package/packages/bundled-features/src/files/changes.json +24 -0
- package/packages/bundled-features/src/files-tenant-data/changes.json +5 -0
- package/packages/bundled-features/src/files-tenant-data/handlers/sweep-orphaned-derivatives.job.ts +4 -0
- package/packages/bundled-features/src/files-tenant-data/hooks.ts +72 -23
- package/packages/bundled-features/src/folders/changes.json +6 -0
- package/packages/bundled-features/src/folders/handlers/delete-folder.write.ts +41 -9
- package/packages/bundled-features/src/foundation-shared/index.ts +1 -0
- package/packages/bundled-features/src/foundation-shared/mail-host-policy.ts +16 -4
- package/packages/bundled-features/src/inbound-provider-imap/changes.json +6 -0
- package/packages/bundled-features/src/inbound-provider-imap/feature.ts +2 -0
- package/packages/bundled-features/src/inbound-provider-imap/imap-client.ts +3 -4
- package/packages/bundled-features/src/jobs/changes.json +30 -0
- package/packages/bundled-features/src/jobs/constants.ts +1 -0
- package/packages/bundled-features/src/jobs/db/queries/retention.ts +12 -0
- package/packages/bundled-features/src/jobs/db/queries/stale-run-sweep.ts +26 -2
- package/packages/bundled-features/src/jobs/feature.ts +7 -1
- package/packages/bundled-features/src/jobs/handlers/list.query.ts +1 -1
- package/packages/bundled-features/src/jobs/handlers/tenant-failures.query.ts +1 -1
- package/packages/bundled-features/src/jobs/handlers/tenant-runs.query.ts +94 -0
- package/packages/bundled-features/src/jobs/index.ts +2 -0
- package/packages/bundled-features/src/jobs/job-run-logger.ts +177 -10
- package/packages/bundled-features/src/jobs/tenant-job-failure-table.ts +27 -8
- package/packages/bundled-features/src/jobs/tenant-job-run-table.ts +55 -0
- package/packages/bundled-features/src/ledger/changes.json +23 -0
- package/packages/bundled-features/src/ledger/entity.ts +13 -5
- package/packages/bundled-features/src/ledger/feature.ts +3 -0
- package/packages/bundled-features/src/ledger/handlers/confirm-schedule-period.write.ts +15 -3
- package/packages/bundled-features/src/ledger/handlers/create-transaction.write.ts +1 -0
- package/packages/bundled-features/src/ledger/handlers/reverse-transaction.write.ts +1 -0
- package/packages/bundled-features/src/ledger/schedule-subject-pair.ts +27 -0
- package/packages/bundled-features/src/mail-transport-smtp/feature.ts +4 -15
- package/packages/bundled-features/src/notes-history/changes.json +13 -0
- package/packages/bundled-features/src/notes-history/entity.ts +4 -0
- package/packages/bundled-features/src/personal-access-tokens/handlers/create.write.ts +1 -0
- package/packages/bundled-features/src/sessions/feature.ts +21 -8
- package/packages/bundled-features/src/sessions/handlers/detail.query.ts +34 -33
- package/packages/bundled-features/src/sessions/handlers/list.query.ts +68 -65
- package/packages/bundled-features/src/sessions/screens.ts +10 -8
- package/packages/bundled-features/src/shared/encrypt-for-direct-write.ts +15 -8
- package/packages/bundled-features/src/shared/index.ts +2 -1
- package/packages/bundled-features/src/shared/parent-visibility.ts +31 -1
- package/packages/bundled-features/src/shared/row-bound-grant.ts +18 -2
- package/packages/bundled-features/src/shared/session-locale-field.ts +7 -0
- package/packages/bundled-features/src/template-resolver/api.ts +3 -3
- package/packages/bundled-features/src/template-resolver/changes.json +5 -0
- package/packages/bundled-features/src/template-resolver/handlers/list.query.ts +2 -2
- package/packages/bundled-features/src/tenant/changes.json +12 -0
- package/packages/bundled-features/src/tenant/feature.ts +8 -1
- package/packages/bundled-features/src/tenant/handlers/toggle-enabled.write.ts +2 -0
- package/packages/bundled-features/src/tenant/handlers/update-member-roles.write.ts +1 -0
- package/packages/bundled-features/src/tenant/invitation-table.ts +6 -0
- package/packages/bundled-features/src/tenant/invited-membership.ts +45 -12
- package/packages/bundled-features/src/tenant-handover/changes.json +6 -0
- package/packages/bundled-features/src/tenant-handover/transfer-graph.ts +14 -96
- package/packages/bundled-features/src/tenant-lifecycle/boot-checks.ts +16 -0
- package/packages/bundled-features/src/tenant-lifecycle/changes.json +22 -0
- package/packages/bundled-features/src/tenant-lifecycle/constants.ts +8 -0
- package/packages/bundled-features/src/tenant-lifecycle/events.ts +6 -0
- package/packages/bundled-features/src/tenant-lifecycle/feature.ts +13 -1
- package/packages/bundled-features/src/tenant-lifecycle/run-tenant-destroy.ts +100 -29
- package/packages/bundled-features/src/tenant-lifecycle/stages.ts +41 -9
- package/packages/bundled-features/src/tenant-lifecycle/testing.ts +58 -0
- package/packages/bundled-features/src/tier-engine/changes.json +5 -0
- package/packages/bundled-features/src/tier-engine/handlers/set-tenant-tier.write.ts +1 -0
- package/packages/bundled-features/src/user/changes.json +15 -0
- package/packages/bundled-features/src/user/handlers/detail.query.ts +1 -0
- package/packages/bundled-features/src/user/handlers/list.query.ts +3 -0
- package/packages/bundled-features/src/user/handlers/update.write.ts +1 -0
- package/packages/bundled-features/src/user/seeding.ts +34 -0
- package/packages/bundled-features/src/user-data-rights/changes.json +6 -0
- package/packages/bundled-features/src/user-data-rights/handlers/request-deletion.write.ts +1 -0
- package/packages/bundled-features/src/user-data-rights/index.ts +1 -0
- package/packages/bundled-features/src/user-data-rights/schema/download-attempt.ts +17 -9
- package/packages/bundled-features/src/user-profile/handlers/change-email.write.ts +8 -2
- package/packages/bundled-features/src/workflow-runner/event-trigger.ts +72 -14
- package/packages/bundled-features/src/workflow-runner/handlers/resume-run.write.ts +6 -1
- package/packages/bundled-features/src/workflow-runner/runner.ts +8 -2
- package/samples/apps/showcase/src/features/items/feature.ts +2 -1
- package/samples/apps/styleguide/src/features/widgets/feature.ts +83 -0
- package/samples/apps/styleguide/src/features/widgets/i18n.ts +12 -0
- package/samples/apps/styleguide/src/features/widgets/web/Widgets.tsx +4 -0
- package/samples/apps/ui-walkthrough/package.json +3 -0
- package/samples/apps/use-all-bundled/README.md +1 -2
- package/samples/apps/use-all-bundled/feature-manifest.json +1 -1
- package/samples/apps/user-data-rights-demo/README.md +1 -3
- package/samples/recipes/legal-pages/README.md +3 -4
- package/samples/recipes/managed-pages/README.md +2 -2
- 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.
|
|
3
|
+
"version": "0.354.0",
|
|
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.
|
|
3
|
+
"version": "0.354.0",
|
|
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
|
|
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
|
|
202
|
-
|
|
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 {
|
|
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 {
|
|
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 " +
|
package/packages/bundled-features/src/auth-email-password/handlers/invite-accept-with-login.write.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
package/packages/bundled-features/src/auth-email-password/handlers/invite-signup-complete.write.ts
CHANGED
|
@@ -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
|