@cosmicdrift/kumiko-bundled-features 0.352.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/dist/agent-tools/agent-doc-lint.d.ts +2 -0
- package/dist/agent-tools/agent-doc-lint.js +61 -1
- package/dist/agent-tools/agent-manifest.d.ts +1 -0
- package/dist/agent-tools/agent-manifest.js +17 -5
- package/dist/agent-tools/feature.js +7 -2
- package/dist/agent-tools/index.d.ts +1 -1
- package/dist/agent-tools/index.js +1 -1
- package/dist/agent-tools/manifest-deny-qns.d.ts +3 -0
- package/dist/agent-tools/manifest-deny-qns.js +17 -0
- package/dist/agent-tools/tool-catalog.d.ts +2 -1
- package/dist/agent-tools/tool-catalog.js +4 -1
- package/dist/agent-tools/tool-dispatch.js +1 -1
- package/dist/audit/handlers/list.query.js +13 -8
- package/dist/auth-email-password/handlers/change-password.write.js +1 -0
- package/dist/auth-email-password/handlers/invite-accept-with-login.write.js +2 -2
- package/dist/auth-email-password/handlers/invite-accept.write.js +2 -2
- package/dist/auth-email-password/handlers/invite-create.write.js +17 -0
- package/dist/auth-email-password/handlers/invite-signup-complete.write.js +4 -1
- package/dist/auth-email-password/handlers/login.write.js +1 -0
- package/dist/auth-email-password/handlers/reset-password.write.js +1 -0
- package/dist/auth-email-password/handlers/signup-confirm.write.js +6 -4
- package/dist/auth-email-password/handlers/signup-request.write.js +51 -77
- package/dist/auth-email-password/handlers/token-request-handler.js +1 -0
- package/dist/auth-email-password/handlers/verify-email.write.js +1 -0
- package/dist/auth-email-password/index.d.ts +2 -0
- package/dist/auth-email-password/index.js +2 -0
- package/dist/auth-email-password/seeding.d.ts +2 -0
- package/dist/auth-email-password/seeding.js +30 -8
- package/dist/auth-email-password/signup-activation.d.ts +24 -0
- package/dist/auth-email-password/signup-activation.js +41 -0
- package/dist/auth-email-password/signup-token-store.d.ts +5 -0
- package/dist/auth-email-password/signup-token-store.js +8 -3
- package/dist/auth-email-password/web/auth-gate.d.ts +2 -2
- package/dist/auth-email-password/web/auth-gate.js +5 -5
- package/dist/auth-email-password/web/auth-redirect.d.ts +1 -0
- package/dist/auth-email-password/web/auth-redirect.js +14 -0
- package/dist/auth-email-password/web/login-screen.js +2 -2
- package/dist/auth-email-password/web/user-menu.js +1 -1
- package/dist/auth-mfa/feature.js +5 -0
- package/dist/auth-mfa/handlers/enable-confirm-preauth.write.js +1 -0
- package/dist/auth-mfa/handlers/verify.write.js +1 -0
- package/dist/auth-mfa/schema/user-mfa.d.ts +6 -0
- package/dist/auth-mfa/screens.js +2 -2
- package/dist/auth-mfa/web/mfa-recovery-codes-reveal.js +1 -1
- package/dist/auth-mfa/web/mfa-setup-preauth-screen.d.ts +1 -1
- package/dist/auth-mfa/web/mfa-setup-preauth-screen.js +3 -3
- package/dist/auth-mfa/web/mfa-verify-screen.d.ts +1 -1
- package/dist/auth-mfa/web/mfa-verify-screen.js +1 -1
- package/dist/billing-foundation/billing-info-query.d.ts +5 -2
- package/dist/billing-foundation/checkout-core.d.ts +7 -0
- package/dist/billing-foundation/checkout-core.js +7 -2
- package/dist/billing-foundation/entities.d.ts +8 -0
- package/dist/billing-foundation/handlers/start-plan-checkout.write.js +7 -1
- package/dist/billing-foundation/projection.d.ts +8 -0
- package/dist/cap-counter/entity.d.ts +1 -0
- package/dist/cap-overview/access.js +2 -9
- package/dist/cap-overview/handlers/tenant-caps-list.query.js +2 -2
- package/dist/cap-overview/handlers/tenant-options.query.js +4 -5
- package/dist/channel-in-app/in-app-channel.js +1 -1
- package/dist/channel-in-app/tables.js +4 -2
- package/dist/compliance-profiles/schema/profile-selection.d.ts +10 -8
- package/dist/compliance-profiles-ops/handlers/tenants-missing-profile.query.js +2 -4
- package/dist/config/handlers/values.query.d.ts +3 -1
- package/dist/config/handlers/values.query.js +7 -2
- package/dist/config/table.d.ts +6 -0
- package/dist/crypto-shredding/handlers/forget-subject.write.d.ts +1 -0
- package/dist/crypto-shredding/handlers/forget-subject.write.js +110 -61
- package/dist/custom-fields/entity.d.ts +4 -0
- package/dist/data-retention/schema/tenant-retention-override.d.ts +4 -0
- package/dist/delivery/db/queries/address-opt-outs.d.ts +3 -1
- package/dist/delivery/db/queries/address-opt-outs.js +12 -7
- package/dist/delivery/delivery-service.js +60 -12
- package/dist/delivery/public-names.js +1 -1
- package/dist/delivery/tables.d.ts +6 -0
- package/dist/delivery/unsubscribe.js +66 -14
- package/dist/document-ingest-foundation/entity.d.ts +4 -0
- package/dist/feature-toggles/feature.js +2 -3
- package/dist/feature-toggles/toggle-runtime.d.ts +5 -1
- package/dist/feature-toggles/toggle-runtime.js +30 -6
- package/dist/feature-toggles/toggle-sync-signal.d.ts +3 -2
- package/dist/feature-toggles/toggle-sync-signal.js +29 -10
- package/dist/file-derivatives/handlers/public-variant.query.d.ts +1 -0
- package/dist/file-derivatives/handlers/public-variant.query.js +7 -1
- package/dist/files-tenant-data/handlers/sweep-orphaned-derivatives.job.js +5 -0
- package/dist/files-tenant-data/hooks.js +61 -24
- package/dist/folders/entity.d.ts +8 -0
- package/dist/folders/feature.js +2 -10
- package/dist/folders/handlers/delete-folder.write.js +31 -4
- package/dist/form-draft/db/queries/cleanup.d.ts +1 -1
- package/dist/form-draft/db/queries/cleanup.js +4 -3
- package/dist/form-draft/entity.d.ts +2 -0
- package/dist/form-draft/handlers/cleanup.job.d.ts +5 -1
- package/dist/form-draft/handlers/cleanup.job.js +11 -5
- package/dist/foundation-shared/index.d.ts +1 -1
- package/dist/foundation-shared/index.js +1 -1
- package/dist/foundation-shared/mail-host-policy.d.ts +4 -0
- package/dist/foundation-shared/mail-host-policy.js +13 -4
- package/dist/inbound-mail-foundation/entities.d.ts +25 -0
- package/dist/inbound-mail-foundation/projection.d.ts +20 -0
- package/dist/inbound-provider-imap/feature.js +2 -0
- package/dist/inbound-provider-imap/imap-client.js +3 -4
- package/dist/jobs/constants.d.ts +1 -0
- package/dist/jobs/constants.js +1 -0
- package/dist/jobs/db/queries/retention.d.ts +1 -0
- package/dist/jobs/db/queries/retention.js +5 -0
- package/dist/jobs/db/queries/stale-run-sweep.d.ts +2 -0
- package/dist/jobs/db/queries/stale-run-sweep.js +13 -2
- package/dist/jobs/feature.js +7 -1
- package/dist/jobs/handlers/list.query.d.ts +2 -1
- package/dist/jobs/handlers/list.query.js +40 -15
- package/dist/jobs/handlers/tenant-failures.query.js +1 -1
- package/dist/jobs/handlers/tenant-runs.query.d.ts +17 -0
- package/dist/jobs/handlers/tenant-runs.query.js +72 -0
- package/dist/jobs/i18n.js +1 -0
- package/dist/jobs/index.d.ts +2 -0
- package/dist/jobs/index.js +1 -0
- package/dist/jobs/job-run-logger.d.ts +2 -2
- package/dist/jobs/job-run-logger.js +98 -9
- package/dist/jobs/job-run-table.d.ts +6 -0
- package/dist/jobs/tenant-job-failure-table.js +15 -2
- package/dist/jobs/tenant-job-run-table.d.ts +4 -0
- package/dist/jobs/tenant-job-run-table.js +34 -0
- package/dist/ledger/entity.d.ts +24 -8
- package/dist/ledger/entity.js +17 -5
- package/dist/ledger/feature.js +3 -0
- package/dist/ledger/handlers/confirm-schedule-period.write.js +9 -3
- package/dist/ledger/handlers/create-transaction.write.js +1 -0
- package/dist/ledger/handlers/reverse-transaction.write.js +1 -0
- package/dist/ledger/schedule-subject-pair.d.ts +2 -0
- package/dist/ledger/schedule-subject-pair.js +24 -0
- package/dist/ledger/schemas.js +8 -2
- package/dist/mail-transport-smtp/feature.d.ts +3 -4
- package/dist/mail-transport-smtp/feature.js +4 -13
- package/dist/managed-pages/feature.js +28 -10
- package/dist/managed-pages/table.d.ts +12 -0
- package/dist/notes-history/entity.d.ts +12 -0
- package/dist/notes-history/entity.js +4 -0
- package/dist/notes-history/feature.js +2 -10
- package/dist/personal-access-tokens/handlers/create.write.js +1 -0
- package/dist/personal-access-tokens/schema/api-token.d.ts +6 -0
- package/dist/personal-access-tokens/screens.js +14 -3
- package/dist/secrets/table.d.ts +1 -0
- package/dist/seo/feature.js +28 -6
- package/dist/sessions/feature.d.ts +1 -0
- package/dist/sessions/feature.js +9 -8
- package/dist/sessions/handlers/detail.query.d.ts +1 -1
- package/dist/sessions/handlers/detail.query.js +2 -2
- package/dist/sessions/handlers/list.query.d.ts +1 -1
- package/dist/sessions/handlers/list.query.js +5 -3
- package/dist/sessions/schema/user-session.d.ts +4 -0
- package/dist/sessions/screens.d.ts +2 -2
- package/dist/sessions/screens.js +6 -7
- package/dist/shared/assert-no-where-rule-in-ownership-write.d.ts +2 -0
- package/dist/shared/assert-no-where-rule-in-ownership-write.js +18 -0
- package/dist/shared/encrypt-for-direct-write.js +10 -2
- package/dist/shared/index.d.ts +4 -3
- package/dist/shared/index.js +4 -3
- package/dist/shared/parent-visibility.d.ts +2 -1
- package/dist/shared/parent-visibility.js +21 -0
- package/dist/shared/rate-limited-text-response.d.ts +2 -0
- package/dist/shared/rate-limited-text-response.js +9 -0
- package/dist/shared/row-bound-grant.js +16 -2
- package/dist/shared/session-locale-field.d.ts +1 -0
- package/dist/shared/session-locale-field.js +6 -0
- package/dist/tags/entity.d.ts +9 -0
- package/dist/tags/feature.js +2 -10
- package/dist/template-resolver/api.d.ts +2 -2
- package/dist/template-resolver/handlers/list.query.d.ts +2 -0
- package/dist/template-resolver/handlers/list.query.js +2 -2
- package/dist/template-resolver/table.d.ts +48 -30
- package/dist/template-resolver/user-content-table.d.ts +34 -20
- package/dist/tenant/decrypt-tenant-names.d.ts +3 -0
- package/dist/tenant/decrypt-tenant-names.js +10 -0
- package/dist/tenant/feature.js +7 -1
- package/dist/tenant/handlers/me.query.js +5 -1
- package/dist/tenant/handlers/memberships.query.js +3 -2
- package/dist/tenant/handlers/tenant-directory.query.js +9 -3
- package/dist/tenant/handlers/toggle-enabled.write.js +2 -0
- package/dist/tenant/handlers/update-member-roles.write.js +1 -0
- package/dist/tenant/index.d.ts +1 -0
- package/dist/tenant/index.js +1 -0
- package/dist/tenant/invitation-table.d.ts +54 -8
- package/dist/tenant/invitation-table.js +6 -1
- package/dist/tenant/invited-membership.d.ts +8 -6
- package/dist/tenant/invited-membership.js +28 -7
- package/dist/tenant/membership-table.d.ts +4 -0
- package/dist/tenant/schema/tenant.d.ts +18 -10
- package/dist/tenant/schema/tenant.js +4 -6
- package/dist/tenant/screens.js +1 -1
- package/dist/tenant-handover/move-entity-graph.js +11 -18
- package/dist/tenant-handover/transfer-graph.d.ts +2 -21
- package/dist/tenant-handover/transfer-graph.js +8 -75
- package/dist/tenant-lifecycle/boot-checks.d.ts +1 -0
- package/dist/tenant-lifecycle/boot-checks.js +13 -0
- package/dist/tenant-lifecycle/constants.d.ts +3 -0
- package/dist/tenant-lifecycle/constants.js +5 -0
- package/dist/tenant-lifecycle/events.d.ts +5 -0
- package/dist/tenant-lifecycle/events.js +5 -0
- package/dist/tenant-lifecycle/feature.js +6 -3
- package/dist/tenant-lifecycle/run-tenant-destroy.d.ts +10 -5
- package/dist/tenant-lifecycle/run-tenant-destroy.js +59 -17
- package/dist/tenant-lifecycle/stages.d.ts +8 -1
- package/dist/tenant-lifecycle/stages.js +33 -4
- package/dist/tenant-lifecycle/testing.d.ts +8 -0
- package/dist/tenant-lifecycle/testing.js +42 -0
- package/dist/tier-engine/entity.d.ts +2 -0
- package/dist/tier-engine/handlers/set-tenant-tier.write.js +1 -0
- package/dist/tier-engine/tier-resolver.d.ts +2 -0
- package/dist/user/handlers/detail.query.js +2 -0
- package/dist/user/handlers/list.query.js +6 -2
- package/dist/user/handlers/update.write.js +1 -0
- package/dist/user/schema/user.d.ts +22 -8
- package/dist/user/seeding.d.ts +1 -0
- package/dist/user/seeding.js +20 -0
- package/dist/user-data-rights/boot-checks.js +5 -0
- package/dist/user-data-rights/handlers/confirm-deletion-by-token.write.js +2 -2
- package/dist/user-data-rights/handlers/request-deletion.write.js +1 -0
- package/dist/user-data-rights/index.d.ts +1 -0
- package/dist/user-data-rights/index.js +1 -0
- package/dist/user-data-rights/schema/download-attempt.d.ts +14 -0
- package/dist/user-data-rights/schema/download-attempt.js +17 -9
- package/dist/user-data-rights/schema/download-token.d.ts +8 -0
- package/dist/user-data-rights/schema/export-job.d.ts +16 -8
- package/dist/user-profile/handlers/change-email.write.js +8 -2
- package/dist/waitlist/entity.d.ts +22 -6
- package/dist/workflow-runner/event-trigger.d.ts +1 -0
- package/dist/workflow-runner/event-trigger.js +63 -15
- package/dist/workflow-runner/handlers/resume-run.write.js +8 -2
- package/dist/workflow-runner/runner.d.ts +2 -1
- package/dist/workflow-runner/runner.js +5 -1
- package/package.json +35 -10
- package/src/agent-tools/changes.json +12 -0
- package/src/audit/changes.json +6 -0
- package/src/auth-email-password/changes.json +24 -0
- package/src/auth-mfa/changes.json +7 -0
- package/src/billing-foundation/changes.json +6 -0
- package/src/channel-in-app/changes.json +8 -1
- package/src/crypto-shredding/changes.json +12 -0
- package/src/delivery/changes.json +6 -0
- package/src/files/changes.json +24 -0
- package/src/files-tenant-data/changes.json +5 -0
- package/src/folders/changes.json +6 -0
- package/src/form-draft/changes.json +7 -1
- package/src/inbound-provider-imap/changes.json +6 -0
- package/src/jobs/changes.json +30 -0
- package/src/ledger/changes.json +23 -0
- package/src/managed-pages/changes.json +6 -0
- package/src/notes-history/changes.json +13 -0
- package/src/seo/changes.json +5 -0
- package/src/template-resolver/changes.json +5 -0
- package/src/tenant/changes.json +19 -0
- package/src/tenant-handover/changes.json +12 -0
- package/src/tenant-lifecycle/changes.json +22 -0
- package/src/tier-engine/changes.json +5 -0
- package/src/user/changes.json +15 -0
- package/src/user-data-rights/changes.json +6 -0
- package/src/workflow-runner/changes.json +5 -0
- package/dist/shared/has-where-rule.d.ts +0 -2
- package/dist/shared/has-where-rule.js +0 -10
|
@@ -4,6 +4,7 @@ export declare const AgentDocGapKinds: {
|
|
|
4
4
|
readonly handlerWithoutDescription: "handler-without-description";
|
|
5
5
|
readonly customScreenWithoutDescription: "custom-screen-without-description";
|
|
6
6
|
readonly exposedEntityWithoutDescription: "exposed-entity-without-description";
|
|
7
|
+
readonly handlerWithoutTranslation: "handler-without-translation";
|
|
7
8
|
};
|
|
8
9
|
export type AgentDocGapKind = (typeof AgentDocGapKinds)[keyof typeof AgentDocGapKinds];
|
|
9
10
|
export type AgentDocGap = {
|
|
@@ -13,4 +14,5 @@ export type AgentDocGap = {
|
|
|
13
14
|
readonly message: string;
|
|
14
15
|
};
|
|
15
16
|
export declare function findAgentDocGaps(features: readonly FeatureDefinition[]): readonly AgentDocGap[];
|
|
17
|
+
export declare function findHandlerTranslationGaps(features: readonly FeatureDefinition[]): readonly AgentDocGap[];
|
|
16
18
|
export declare function formatAgentDocGap(gap: AgentDocGap): string;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { isAgentVisibleScreen, QnTypes, qn, resolveAgentExposure, toKebab, } from "@cosmicdrift/kumiko-framework/engine";
|
|
1
|
+
import { handlerFieldLabelKey, handlerTitleKey, isAgentVisibleScreen, QnTypes, qn, resolveAgentExposure, toKebab, } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
2
|
import * as z from "zod";
|
|
3
|
+
import { systemOnlyPropertyNames } from "./agent-manifest.js";
|
|
3
4
|
export const AgentDocGapKinds = {
|
|
4
5
|
handlerSchemaNotExpressible: "handler-schema-not-expressible",
|
|
5
6
|
handlerWithoutDescription: "handler-without-description",
|
|
6
7
|
customScreenWithoutDescription: "custom-screen-without-description",
|
|
7
8
|
exposedEntityWithoutDescription: "exposed-entity-without-description",
|
|
9
|
+
handlerWithoutTranslation: "handler-without-translation",
|
|
8
10
|
};
|
|
9
11
|
// No shared screen-type union exists in the codebase to import (ScreenDefinition
|
|
10
12
|
// is a discriminated union keyed by inline string literals) — name the one
|
|
@@ -107,6 +109,57 @@ function exposedEntityDocGaps(feature) {
|
|
|
107
109
|
}
|
|
108
110
|
return gaps;
|
|
109
111
|
}
|
|
112
|
+
function actionFormHandlerQns(features) {
|
|
113
|
+
const handlerQns = new Set();
|
|
114
|
+
for (const feature of features) {
|
|
115
|
+
for (const screen of Object.values(feature.screens ?? {})) {
|
|
116
|
+
if (screen.type === "actionForm")
|
|
117
|
+
handlerQns.add(screen.handler);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return handlerQns;
|
|
121
|
+
}
|
|
122
|
+
function agentVisibleInputFieldNames(schema) {
|
|
123
|
+
if (!(schema instanceof z.ZodObject))
|
|
124
|
+
return [];
|
|
125
|
+
const fieldNames = Object.keys(schema.shape);
|
|
126
|
+
try {
|
|
127
|
+
const systemOnly = new Set(systemOnlyPropertyNames(z.toJSONSchema(schema, { io: "input" })));
|
|
128
|
+
return fieldNames.filter((field) => !systemOnly.has(field));
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
// skip: a schema without JSON Schema form is reported as its own gap kind
|
|
132
|
+
return fieldNames;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function translationGapsOfFeature(feature, coveredHandlerQns) {
|
|
136
|
+
const gaps = [];
|
|
137
|
+
// The client resolves translation keys verbatim (no feature prefix), so only
|
|
138
|
+
// fully spelled-out keys count.
|
|
139
|
+
const translated = new Set(Object.keys(feature.translations ?? {}));
|
|
140
|
+
for (const [name, def] of Object.entries(feature.writeHandlers ?? {})) {
|
|
141
|
+
if (!resolveAgentExposure(def, "write").expose)
|
|
142
|
+
continue;
|
|
143
|
+
if (feature.handlerEntityMappings?.[name] !== undefined)
|
|
144
|
+
continue;
|
|
145
|
+
const handlerQn = qn(toKebab(feature.name), QnTypes.write, toKebab(name));
|
|
146
|
+
if (coveredHandlerQns.has(handlerQn))
|
|
147
|
+
continue;
|
|
148
|
+
const missing = [
|
|
149
|
+
handlerTitleKey(handlerQn),
|
|
150
|
+
...agentVisibleInputFieldNames(def.schema).map((field) => handlerFieldLabelKey(handlerQn, field)),
|
|
151
|
+
].filter((key) => !translated.has(key));
|
|
152
|
+
if (missing.length === 0)
|
|
153
|
+
continue;
|
|
154
|
+
gaps.push({
|
|
155
|
+
qn: handlerQn,
|
|
156
|
+
feature: feature.name,
|
|
157
|
+
kind: AgentDocGapKinds.handlerWithoutTranslation,
|
|
158
|
+
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(", ")}.`,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
return gaps;
|
|
162
|
+
}
|
|
110
163
|
export function findAgentDocGaps(features) {
|
|
111
164
|
const gaps = [];
|
|
112
165
|
for (const feature of features) {
|
|
@@ -119,6 +172,13 @@ export function findAgentDocGaps(features) {
|
|
|
119
172
|
// depend on it (mirrors agent-manifest.ts's sortedByKey rationale).
|
|
120
173
|
return [...gaps].sort((a, b) => compareByCodePoint(a.qn, b.qn));
|
|
121
174
|
}
|
|
175
|
+
// Kept out of findAgentDocGaps on purpose: the published agent-manifest guard
|
|
176
|
+
// fails on every gap that function returns, and these are advisory.
|
|
177
|
+
export function findHandlerTranslationGaps(features) {
|
|
178
|
+
const coveredHandlerQns = actionFormHandlerQns(features);
|
|
179
|
+
const gaps = features.flatMap((feature) => translationGapsOfFeature(feature, coveredHandlerQns));
|
|
180
|
+
return [...gaps].sort((a, b) => compareByCodePoint(a.qn, b.qn));
|
|
181
|
+
}
|
|
122
182
|
export function formatAgentDocGap(gap) {
|
|
123
183
|
return `${gap.qn} — ${gap.message}`;
|
|
124
184
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AgentManifest, AgentManifestOptions, RegistryManifestView } from "./types.js";
|
|
2
|
+
export declare function systemOnlyPropertyNames(jsonSchema: Record<string, unknown>): readonly string[];
|
|
2
3
|
/** Registry snapshot → AI-agent manifest. Pure, deterministic, no I/O — mirrors
|
|
3
4
|
* `buildToolCatalog`'s contract, just describing the app's shape instead of
|
|
4
5
|
* generating callable tools from it. */
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { hasAccess, isAgentVisibleScreen, resolveAgentExposure, SYSTEM_ONLY_JSON_SCHEMA_KEY, } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
2
|
import { translationValueOtherText } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
3
3
|
import * as z from "zod";
|
|
4
|
+
import { recordManifestDenyQns } from "./manifest-deny-qns.js";
|
|
4
5
|
/** The manifest surfaces one string per locale — a plural entry reads as its
|
|
5
6
|
* CLDR `other` form since the manifest has no `count` to select a category. */
|
|
6
7
|
function entryAsLabels(entry) {
|
|
@@ -118,13 +119,19 @@ function buildFeatures(features) {
|
|
|
118
119
|
}
|
|
119
120
|
// Agent dispatch never runs as a system identity, so properties the engine only
|
|
120
121
|
// honors for system callers would be silently dropped — hide them from the tool.
|
|
121
|
-
function
|
|
122
|
+
export function systemOnlyPropertyNames(jsonSchema) {
|
|
122
123
|
const properties = jsonSchema["properties"];
|
|
123
124
|
if (typeof properties !== "object" || properties === null)
|
|
124
|
-
return
|
|
125
|
-
|
|
125
|
+
return [];
|
|
126
|
+
return Object.entries(properties)
|
|
126
127
|
.filter(([, property]) => property[SYSTEM_ONLY_JSON_SCHEMA_KEY] === true)
|
|
127
128
|
.map(([name]) => name);
|
|
129
|
+
}
|
|
130
|
+
function withoutSystemOnlyProperties(jsonSchema) {
|
|
131
|
+
const properties = jsonSchema["properties"];
|
|
132
|
+
if (typeof properties !== "object" || properties === null)
|
|
133
|
+
return jsonSchema;
|
|
134
|
+
const hidden = systemOnlyPropertyNames(jsonSchema);
|
|
128
135
|
if (hidden.length === 0)
|
|
129
136
|
return jsonSchema;
|
|
130
137
|
const required = jsonSchema["required"];
|
|
@@ -232,6 +239,9 @@ function lastSegment(qualifiedId) {
|
|
|
232
239
|
function detailIdParam(screen) {
|
|
233
240
|
return screen.type === "projectionDetail" ? (screen.idParam ?? "id") : "id";
|
|
234
241
|
}
|
|
242
|
+
function screenTitles(translations, screenId, shortId) {
|
|
243
|
+
return entryAsLabels(translations[`${screenId}.title`] ?? translations[`screen:${shortId}.title`] ?? {});
|
|
244
|
+
}
|
|
235
245
|
function buildScreens(screens, navs, workspaces, translations, roles, visibleNavIds) {
|
|
236
246
|
const allNavs = [...navs.values()];
|
|
237
247
|
const result = [];
|
|
@@ -257,7 +267,7 @@ function buildScreens(screens, navs, workspaces, translations, roles, visibleNav
|
|
|
257
267
|
result.push({
|
|
258
268
|
id: screen.id,
|
|
259
269
|
type: screen.type,
|
|
260
|
-
titles:
|
|
270
|
+
titles: screenTitles(translations, screen.id, shortId),
|
|
261
271
|
// `screen.description` may be a raw i18n key (`registry.getAllTranslations()`
|
|
262
272
|
// double-prefixes it, see build-app-schema.ts:77-81) — match on suffix like
|
|
263
273
|
// `labelsForSuffix` does for entity/field labels, else fall back to the
|
|
@@ -304,7 +314,7 @@ export function buildAgentManifest(registry, options) {
|
|
|
304
314
|
const navs = buildNavs(navMap, workspaceMap, translations, roles, screenMap);
|
|
305
315
|
const screens = buildScreens(screenMap, navMap, workspaceMap, translations, roles, new Set(navs.map((nav) => nav.id)));
|
|
306
316
|
const workspaces = buildWorkspaces(workspaceMap, translations, roles);
|
|
307
|
-
|
|
317
|
+
const manifest = {
|
|
308
318
|
builtForRoles: [...roles],
|
|
309
319
|
features: sortedByKey(features, (f) => f.name),
|
|
310
320
|
entities: sortedByKey(entities, (e) => e.name),
|
|
@@ -317,4 +327,6 @@ export function buildAgentManifest(registry, options) {
|
|
|
317
327
|
...(options.currency && { currency: options.currency }),
|
|
318
328
|
},
|
|
319
329
|
};
|
|
330
|
+
recordManifestDenyQns(manifest, new Set(options.denyQns ?? []));
|
|
331
|
+
return manifest;
|
|
320
332
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
-
import { findAgentDocGaps, formatAgentDocGap } from "./agent-doc-lint.js";
|
|
2
|
+
import { findAgentDocGaps, findHandlerTranslationGaps, formatAgentDocGap, } from "./agent-doc-lint.js";
|
|
3
3
|
export const AGENT_TOOLS_FEATURE_NAME = "agent-tools";
|
|
4
4
|
export function createAgentToolsFeature() {
|
|
5
5
|
return defineFeature(AGENT_TOOLS_FEATURE_NAME, (r) => {
|
|
@@ -14,8 +14,13 @@ export function createAgentToolsFeature() {
|
|
|
14
14
|
// would turn every incrementally-documented app into an outage.
|
|
15
15
|
r.bootCheck(({ features }) => {
|
|
16
16
|
const gaps = findAgentDocGaps(features);
|
|
17
|
+
const warningCount = findHandlerTranslationGaps(features).length;
|
|
18
|
+
if (warningCount > 0) {
|
|
19
|
+
// biome-ignore lint/suspicious/noConsole: operator-visibility at boot for agent-doc gaps
|
|
20
|
+
console.warn(`[agent-tools] ${warningCount} agent-exposed write handler(s) lack title/field-label translations — run \`kumiko agent lint\` for details.`);
|
|
21
|
+
}
|
|
17
22
|
if (gaps.length === 0) {
|
|
18
|
-
// skip: no documentation gaps, nothing to warn about
|
|
23
|
+
// skip: no blocking documentation gaps, nothing more to warn about
|
|
19
24
|
return;
|
|
20
25
|
}
|
|
21
26
|
// biome-ignore lint/suspicious/noConsole: operator-visibility at boot for agent-doc gaps
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type { AgentDocGap, AgentDocGapKind } from "./agent-doc-lint.js";
|
|
2
|
-
export { AgentDocGapKinds, findAgentDocGaps, formatAgentDocGap } from "./agent-doc-lint.js";
|
|
2
|
+
export { AgentDocGapKinds, findAgentDocGaps, findHandlerTranslationGaps, formatAgentDocGap, } from "./agent-doc-lint.js";
|
|
3
3
|
export { buildAgentManifest } from "./agent-manifest.js";
|
|
4
4
|
export { AGENT_TOOLS_FEATURE_NAME, createAgentToolsFeature } from "./feature.js";
|
|
5
5
|
export { buildToolCatalog, OPEN_FORM_TOOL_NAME, toolNameForQn } from "./tool-catalog.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { AgentDocGapKinds, findAgentDocGaps, formatAgentDocGap } from "./agent-doc-lint.js";
|
|
1
|
+
export { AgentDocGapKinds, findAgentDocGaps, findHandlerTranslationGaps, formatAgentDocGap, } from "./agent-doc-lint.js";
|
|
2
2
|
export { buildAgentManifest } from "./agent-manifest.js";
|
|
3
3
|
export { AGENT_TOOLS_FEATURE_NAME, createAgentToolsFeature } from "./feature.js";
|
|
4
4
|
export { buildToolCatalog, OPEN_FORM_TOOL_NAME, toolNameForQn } from "./tool-catalog.js";
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { AgentManifest } from "./types.js";
|
|
2
|
+
export declare function recordManifestDenyQns(manifest: AgentManifest, denyQns: ReadonlySet<string>): void;
|
|
3
|
+
export declare function assertCatalogMirrorsManifestDenyQns(manifest: AgentManifest, catalogDenyQns: ReadonlySet<string>): void;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// The deny list is deliberately not a manifest field (the manifest is prompt
|
|
2
|
+
// payload), so the catalog builder learns it through this side channel to
|
|
3
|
+
// detect a manifest cut that the catalog would silently not mirror.
|
|
4
|
+
const denyQnsByManifest = new WeakMap();
|
|
5
|
+
export function recordManifestDenyQns(manifest, denyQns) {
|
|
6
|
+
denyQnsByManifest.set(manifest, denyQns);
|
|
7
|
+
}
|
|
8
|
+
export function assertCatalogMirrorsManifestDenyQns(manifest, catalogDenyQns) {
|
|
9
|
+
const manifestDenyQns = denyQnsByManifest.get(manifest);
|
|
10
|
+
// skip: manifest has no registered deny list, nothing to mirror
|
|
11
|
+
if (!manifestDenyQns)
|
|
12
|
+
return;
|
|
13
|
+
const missing = [...manifestDenyQns].filter((qn) => !catalogDenyQns.has(qn));
|
|
14
|
+
if (missing.length > 0) {
|
|
15
|
+
throw new Error(`agent denyQns passed to buildAgentManifest but not to buildToolCatalog: ${missing.join(", ")} — the catalog would still expose their entity CRUD tools`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -24,5 +24,6 @@ export declare const OPEN_FORM_TOOL_NAME = "open_form";
|
|
|
24
24
|
* to drop an entity CRUD tool, which no `agent.expose` on the manifest side can reach. It is
|
|
25
25
|
* NOT read off the manifest on purpose — the manifest is prompt payload, and a list of the
|
|
26
26
|
* handlers the model may not call has no business travelling to the provider. Pass the same
|
|
27
|
-
* list to `buildAgentManifest` so the manifest stops describing what the catalog withholds
|
|
27
|
+
* list to `buildAgentManifest` so the manifest stops describing what the catalog withholds;
|
|
28
|
+
* a manifest built with `denyQns` that the catalog lacks throws. */
|
|
28
29
|
export declare function buildToolCatalog(registry: RegistrySearchView, manifest: AgentManifest, options: ToolCatalogOptions): ToolCatalog;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { hasAccess, normalizeAccessEntry } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
2
|
import { FILTER_OPS, isRecord } from "./filter-ops.js";
|
|
3
|
+
import { assertCatalogMirrorsManifestDenyQns } from "./manifest-deny-qns.js";
|
|
3
4
|
const MAX_TOOL_NAME_LENGTH = 64;
|
|
4
5
|
/** Field types that declare `filterable` (per `packages/framework/src/engine/types/fields.ts`).
|
|
5
6
|
* Mapped to the JSON-Schema type an LLM tool-call argument should use. `undefined` = field type
|
|
@@ -466,13 +467,15 @@ function addClientTools(manifest, mode, sink) {
|
|
|
466
467
|
* to drop an entity CRUD tool, which no `agent.expose` on the manifest side can reach. It is
|
|
467
468
|
* NOT read off the manifest on purpose — the manifest is prompt payload, and a list of the
|
|
468
469
|
* handlers the model may not call has no business travelling to the provider. Pass the same
|
|
469
|
-
* list to `buildAgentManifest` so the manifest stops describing what the catalog withholds
|
|
470
|
+
* list to `buildAgentManifest` so the manifest stops describing what the catalog withholds;
|
|
471
|
+
* a manifest built with `denyQns` that the catalog lacks throws. */
|
|
470
472
|
export function buildToolCatalog(registry, manifest, options) {
|
|
471
473
|
const sink = { tools: [], dispatchTable: new Map(), usedNames: new Set() };
|
|
472
474
|
const roleFilter = { roles: manifest.builtForRoles };
|
|
473
475
|
const locale = manifest.tenantSettings.locale;
|
|
474
476
|
const entityByName = new Map(manifest.entities.map((entity) => [entity.name, entity]));
|
|
475
477
|
const denyQns = new Set(options.denyQns ?? []);
|
|
478
|
+
assertCatalogMirrorsManifestDenyQns(manifest, denyQns);
|
|
476
479
|
addRegistrySearchTools(registry, roleFilter, denyQns, sink);
|
|
477
480
|
const { detailQnByEntity, listQnByEntity, entityListDetailQns } = collectEntityHandlerQns(registry, roleFilter, denyQns);
|
|
478
481
|
addGetTools(registry, detailQnByEntity, entityByName, locale, sink);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { runAsDirectCallEntry } from "@cosmicdrift/kumiko-framework/
|
|
1
|
+
import { runAsDirectCallEntry } from "@cosmicdrift/kumiko-framework/internal/request-context";
|
|
2
2
|
import { FILTER_OPS, isRecord } from "./filter-ops.js";
|
|
3
3
|
const SEARCH_RESULT_LIMIT = 10;
|
|
4
4
|
const DEFAULT_LIST_LIMIT = 10;
|
|
@@ -34,7 +34,7 @@ function buildDateRange(from, to) {
|
|
|
34
34
|
function escapeLikePattern(value) {
|
|
35
35
|
return value.replace(/[\\%_]/g, (m) => `\\${m}`);
|
|
36
36
|
}
|
|
37
|
-
function buildAuditWhere(tenantId, p) {
|
|
37
|
+
function buildAuditWhere(tenantId, p, direction) {
|
|
38
38
|
const where = { tenantId };
|
|
39
39
|
if (p.aggregateType)
|
|
40
40
|
where["aggregateType"] = p.aggregateType;
|
|
@@ -50,8 +50,10 @@ function buildAuditWhere(tenantId, p) {
|
|
|
50
50
|
if (range)
|
|
51
51
|
where["createdAt"] = range;
|
|
52
52
|
const cursor = p.cursor ?? p.before;
|
|
53
|
+
// The cursor is an event id, so it only walks pages in id order: ascending
|
|
54
|
+
// pages continue past it, descending pages stop before it.
|
|
53
55
|
if (cursor)
|
|
54
|
-
where["id"] = { lt: BigInt(cursor) };
|
|
56
|
+
where["id"] = direction === "asc" ? { gt: BigInt(cursor) } : { lt: BigInt(cursor) };
|
|
55
57
|
return where;
|
|
56
58
|
}
|
|
57
59
|
export const listQuery = defineQueryHandler({
|
|
@@ -81,15 +83,17 @@ export const listQuery = defineQueryHandler({
|
|
|
81
83
|
handler: async (query, ctx) => {
|
|
82
84
|
const p = query.payload;
|
|
83
85
|
const scopeFilter = resolveAuditScopeFilter(query.user, p.scope);
|
|
86
|
+
const direction = p.sortDirection ?? "desc";
|
|
84
87
|
const where = buildAuditWhere(scopeFilter.tenantId, {
|
|
85
88
|
...p,
|
|
86
89
|
aggregateType: scopeFilter.aggregateType ?? p.aggregateType,
|
|
87
|
-
});
|
|
90
|
+
}, direction);
|
|
88
91
|
const rows = await selectMany(ctx.db, eventsTable, where, {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
92
|
+
// `id` as tie-breaker keeps the order total, so pages never overlap.
|
|
93
|
+
orderBy: [
|
|
94
|
+
{ col: p.sort === "type" ? "type" : "createdAt", direction },
|
|
95
|
+
{ col: "id", direction },
|
|
96
|
+
],
|
|
93
97
|
limit: p.limit,
|
|
94
98
|
});
|
|
95
99
|
const serialised = rows.map((r) => ({
|
|
@@ -104,7 +108,8 @@ export const listQuery = defineQueryHandler({
|
|
|
104
108
|
createdBy: r.createdBy,
|
|
105
109
|
}));
|
|
106
110
|
const last = serialised[serialised.length - 1];
|
|
107
|
-
|
|
111
|
+
// An id cursor cannot resume a type-ordered listing, so that sort is one page.
|
|
112
|
+
const nextId = serialised.length === p.limit && last && p.sort !== "type" ? last.id : null;
|
|
108
113
|
return {
|
|
109
114
|
rows: serialised,
|
|
110
115
|
nextCursor: nextId,
|
|
@@ -16,6 +16,7 @@ export const changePasswordWrite = defineWriteHandler({
|
|
|
16
16
|
}),
|
|
17
17
|
access: { roles: access.authenticated },
|
|
18
18
|
escapeHatch: {
|
|
19
|
+
grants: ["systemIdentity"],
|
|
19
20
|
reason: "Reads the caller's own passwordHash (privileged-only field) via ctx.queryAs(SYSTEM, ...) " +
|
|
20
21
|
"and writes the new hash via ctx.writeAs(SYSTEM, user:update) — the caller's own " +
|
|
21
22
|
"identity can't reach either, field-access on passwordHash is privileged-only.",
|
|
@@ -29,7 +29,7 @@ import { decryptStoredPii, sessionLocaleField, sessionTimezoneField } from "../.
|
|
|
29
29
|
// kumiko-lint-ignore cross-feature-import invite-flow
|
|
30
30
|
import { INVITATION_STATUS, tenantInvitationEntity, tenantInvitationsTable, } from "../../tenant/invitation-table.js";
|
|
31
31
|
// kumiko-lint-ignore cross-feature-import membership grant for a privileged cross-tenant add
|
|
32
|
-
import { grantInvitedMembershipRole
|
|
32
|
+
import { grantInvitedMembershipRole } from "../../tenant/invited-membership.js";
|
|
33
33
|
// kumiko-lint-ignore cross-feature-import global roles granted on invite accept live on the user row
|
|
34
34
|
import { grantInvitedGlobalRoles, parseInvitedGlobalRoles, } from "../../user/invited-global-roles.js";
|
|
35
35
|
// kumiko-lint-ignore cross-feature-import login-style password-check
|
|
@@ -118,7 +118,7 @@ export function createInviteAcceptWithLoginHandler(opts = {}) {
|
|
|
118
118
|
userId,
|
|
119
119
|
tenantId: invitationTenantId,
|
|
120
120
|
role: invitationRole,
|
|
121
|
-
|
|
121
|
+
invitation,
|
|
122
122
|
});
|
|
123
123
|
if (!grant.isSuccess)
|
|
124
124
|
return grant;
|
|
@@ -23,7 +23,7 @@ import { decryptStoredPii } from "../../shared/index.js";
|
|
|
23
23
|
// kumiko-lint-ignore cross-feature-import invite-flow lebt in auth-email-password (Magic-Link), DB-row-owner ist tenant-feature
|
|
24
24
|
import { INVITATION_STATUS, tenantInvitationEntity, tenantInvitationsTable, } from "../../tenant/invitation-table.js";
|
|
25
25
|
// kumiko-lint-ignore cross-feature-import membership grant for a privileged cross-tenant add (like provisionSignupAccount)
|
|
26
|
-
import { grantInvitedMembershipRole
|
|
26
|
+
import { grantInvitedMembershipRole } from "../../tenant/invited-membership.js";
|
|
27
27
|
// kumiko-lint-ignore cross-feature-import global roles granted on invite accept live on the user row
|
|
28
28
|
import { grantInvitedGlobalRoles, parseInvitedGlobalRoles, } from "../../user/invited-global-roles.js";
|
|
29
29
|
// kumiko-lint-ignore cross-feature-import auth handler reads user-row für email-match
|
|
@@ -93,7 +93,7 @@ export function createInviteAcceptHandler() {
|
|
|
93
93
|
userId: event.user.id,
|
|
94
94
|
tenantId: invitationTenantId,
|
|
95
95
|
role: invitationRole,
|
|
96
|
-
|
|
96
|
+
invitation,
|
|
97
97
|
});
|
|
98
98
|
if (!grant.isSuccess)
|
|
99
99
|
return grant;
|
|
@@ -24,10 +24,14 @@ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
|
24
24
|
import * as z from "zod";
|
|
25
25
|
// kumiko-lint-ignore cross-feature-import invite-flow lebt in auth-email-password (Magic-Link-Pattern), DB-row-owner ist tenant-feature
|
|
26
26
|
import { INVITATION_STATUS, tenantInvitationEntity, tenantInvitationsTable, } from "../../tenant/invitation-table.js";
|
|
27
|
+
// kumiko-lint-ignore cross-feature-import membership version read is owned by the tenant feature
|
|
28
|
+
import { membershipVersionOf } from "../../tenant/invited-membership.js";
|
|
27
29
|
// kumiko-lint-ignore cross-feature-import membership-role validation owned by tenant-feature
|
|
28
30
|
import { findForbiddenMembershipRole, findForbiddenRoleAssignment, reservedMembershipRoleError, unassignableMembershipRoleError, } from "../../tenant/membership-roles.js";
|
|
29
31
|
// kumiko-lint-ignore cross-feature-import global-role options are owned by the user feature
|
|
30
32
|
import { GLOBAL_ROLE_OPTIONS } from "../../user/constants.js";
|
|
33
|
+
// kumiko-lint-ignore cross-feature-import invitee lookup by email reads the user row
|
|
34
|
+
import { userTable } from "../../user/schema/user.js";
|
|
31
35
|
import { AUTH_INVITE_DEFAULT_TTL_MINUTES } from "../constants.js";
|
|
32
36
|
import { renderInviteEmail } from "../email-templates.js";
|
|
33
37
|
import { inviteAlreadyAccepted } from "../errors.js";
|
|
@@ -61,6 +65,16 @@ function checkAssignableInviteRole(opts, inviterRoles, role, ctx) {
|
|
|
61
65
|
}
|
|
62
66
|
return undefined;
|
|
63
67
|
}
|
|
68
|
+
// 0 = the invitee has no membership in the tenant yet (or no account at all).
|
|
69
|
+
async function currentMembershipVersion(ctx, tenantId, email) {
|
|
70
|
+
const user = await ctx.db.global(userTable).fetchOne({
|
|
71
|
+
email,
|
|
72
|
+
isDeleted: false,
|
|
73
|
+
});
|
|
74
|
+
if (!user)
|
|
75
|
+
return 0;
|
|
76
|
+
return membershipVersionOf(ctx.db, tenantId, user.id);
|
|
77
|
+
}
|
|
64
78
|
async function issueInvitation(opts, ttlSeconds, request, inviter, ctx, refuseAccepted = false) {
|
|
65
79
|
if (!ctx.redis) {
|
|
66
80
|
return writeFailure(new InternalError({ message: "invite-create requires ctx.redis for token store" }));
|
|
@@ -72,6 +86,7 @@ async function issueInvitation(opts, ttlSeconds, request, inviter, ctx, refuseAc
|
|
|
72
86
|
// The unique index allows one row per (tenantId, email). Whatever its
|
|
73
87
|
// status, a re-invite resets it to pending with a fresh token.
|
|
74
88
|
const existing = await ctx.db.fetchOne(tenantInvitationsTable, { tenantId, email });
|
|
89
|
+
const membershipVersion = await currentMembershipVersion(ctx, tenantId, email);
|
|
75
90
|
let invitationId;
|
|
76
91
|
if (existing) {
|
|
77
92
|
// Provisioning callers (waitlist) must not flip an accepted invite back to
|
|
@@ -96,6 +111,7 @@ async function issueInvitation(opts, ttlSeconds, request, inviter, ctx, refuseAc
|
|
|
96
111
|
globalRoles: [...request.globalRoles],
|
|
97
112
|
status: INVITATION_STATUS.pending,
|
|
98
113
|
invitedBy: inviter.id,
|
|
114
|
+
membershipVersion,
|
|
99
115
|
expiresAt,
|
|
100
116
|
},
|
|
101
117
|
}, inviter, ctx.db);
|
|
@@ -109,6 +125,7 @@ async function issueInvitation(opts, ttlSeconds, request, inviter, ctx, refuseAc
|
|
|
109
125
|
globalRoles: [...request.globalRoles],
|
|
110
126
|
status: INVITATION_STATUS.pending,
|
|
111
127
|
invitedBy: inviter.id,
|
|
128
|
+
membershipVersion,
|
|
112
129
|
expiresAt,
|
|
113
130
|
}, inviter, ctx.db);
|
|
114
131
|
if (!createResult.isSuccess)
|
|
@@ -21,7 +21,7 @@ import { createEventStoreExecutor, createTenantDb, } from "@cosmicdrift/kumiko-f
|
|
|
21
21
|
import { buildSessionRoles, createSystemUser, defineWriteHandler, } from "@cosmicdrift/kumiko-framework/engine";
|
|
22
22
|
import { InternalError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
23
23
|
import * as z from "zod";
|
|
24
|
-
import { decryptStoredPii } from "../../shared/index.js";
|
|
24
|
+
import { decryptStoredPii, registrationLocaleFromRequest } from "../../shared/index.js";
|
|
25
25
|
// kumiko-lint-ignore cross-feature-import invite-flow
|
|
26
26
|
import { INVITATION_STATUS, tenantInvitationEntity, tenantInvitationsTable, } from "../../tenant/invitation-table.js";
|
|
27
27
|
// kumiko-lint-ignore cross-feature-import reserved-role check owned by tenant-feature
|
|
@@ -92,11 +92,13 @@ export function createInviteSignupCompleteHandler(opts = {}) {
|
|
|
92
92
|
// @cast-boundary db-runner — helpers use only the query API that
|
|
93
93
|
// DbConnection and DbTx share.
|
|
94
94
|
const dbConn = ctx.db.unsafeRaw();
|
|
95
|
+
const registrationLocale = registrationLocaleFromRequest();
|
|
95
96
|
const { id: userId } = await seedUserWithPassword(dbConn, {
|
|
96
97
|
email: invitationEmail,
|
|
97
98
|
password: event.payload.password,
|
|
98
99
|
displayName: invitationEmail.split("@")[0] ?? invitationEmail,
|
|
99
100
|
emailVerified: true,
|
|
101
|
+
...registrationLocale,
|
|
100
102
|
...(invitationGlobalRoles.length > 0 && { roles: invitationGlobalRoles }),
|
|
101
103
|
});
|
|
102
104
|
// Membership-Add via seed-helper (gleiches Pattern wie
|
|
@@ -136,6 +138,7 @@ export function createInviteSignupCompleteHandler(opts = {}) {
|
|
|
136
138
|
id: userId,
|
|
137
139
|
tenantId: invitationTenantId,
|
|
138
140
|
roles: mergedRoles,
|
|
141
|
+
...registrationLocale,
|
|
139
142
|
};
|
|
140
143
|
committed = true;
|
|
141
144
|
return {
|
|
@@ -151,6 +151,7 @@ export function createLoginHandler(opts = {}) {
|
|
|
151
151
|
access: { roles: ["anonymous"] },
|
|
152
152
|
rateLimit: { per: "ip+handler", limit: 20, windowSeconds: 60 },
|
|
153
153
|
escapeHatch: {
|
|
154
|
+
grants: ["systemIdentity", "unsafeRaw"],
|
|
154
155
|
reason: "Unauthenticated login has no caller identity yet — it looks up the user row by " +
|
|
155
156
|
"email via ctx.queryAs(SYSTEM, ...) and resolves tenant membership via " +
|
|
156
157
|
"ctx.resolveActiveMembership before a session exists. It also reads the MFA " +
|
|
@@ -23,6 +23,7 @@ export function createResetPasswordHandler(opts) {
|
|
|
23
23
|
access: { roles: ["anonymous"] },
|
|
24
24
|
rateLimit: { per: "ip+handler", limit: 20, windowSeconds: 60 },
|
|
25
25
|
escapeHatch: {
|
|
26
|
+
grants: ["systemIdentity"],
|
|
26
27
|
reason: "Anonymous token holder has no session — runConfirmTokenFlow loads and updates the " +
|
|
27
28
|
"target user via ctx.writeAs(SYSTEM, user:update) to set the new password hash.",
|
|
28
29
|
},
|
|
@@ -24,7 +24,7 @@ import { ConflictError, InternalError, reraiseAsKumikoError, writeFailure, } fro
|
|
|
24
24
|
import { generateUniqueName } from "@cosmicdrift/kumiko-framework/random";
|
|
25
25
|
import { generateId } from "@cosmicdrift/kumiko-framework/utils";
|
|
26
26
|
import * as z from "zod";
|
|
27
|
-
import { findSignupHandoverProvider, SIGNUP_HANDOVER_BENIGN_CLAIM_REJECTION_CODE, } from "../../shared/index.js";
|
|
27
|
+
import { findSignupHandoverProvider, registrationLocaleFromRequest, SIGNUP_HANDOVER_BENIGN_CLAIM_REJECTION_CODE, } from "../../shared/index.js";
|
|
28
28
|
// kumiko-lint-ignore cross-feature-import signup-confirm reads tenants.key for slug-uniqueness check (TOCTOU + DB-unique-index zusammen)
|
|
29
29
|
import { tenantTable } from "../../tenant/schema/tenant.js";
|
|
30
30
|
import { invalidSignupToken, signupEmailAlreadyRegistered } from "../errors.js";
|
|
@@ -116,6 +116,7 @@ export function createSignupConfirmHandler(opts = {}) {
|
|
|
116
116
|
// Display-Name aus email-prefix als sinnvolles Default; User kann
|
|
117
117
|
// den Tenant-Namen + sein eigenes displayName später ändern.
|
|
118
118
|
const displayName = email.split("@")[0] ?? email;
|
|
119
|
+
const registrationLocale = registrationLocaleFromRequest();
|
|
119
120
|
let provisioned;
|
|
120
121
|
try {
|
|
121
122
|
provisioned = await provisionSignupAccount(dbConn, {
|
|
@@ -124,9 +125,9 @@ export function createSignupConfirmHandler(opts = {}) {
|
|
|
124
125
|
displayName,
|
|
125
126
|
tenantId,
|
|
126
127
|
tenantKey,
|
|
127
|
-
//
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
// Generated slug as default name: no personal data (the email) in the tenant name.
|
|
129
|
+
tenantName: tenantKey,
|
|
130
|
+
...registrationLocale,
|
|
130
131
|
},
|
|
131
132
|
// #1463: seedTenant's postSave hooks (tier-engine's auto-default-
|
|
132
133
|
// tier, an app's auto-default-compliance) must fire on self-signup
|
|
@@ -151,6 +152,7 @@ export function createSignupConfirmHandler(opts = {}) {
|
|
|
151
152
|
id: provisioned.userId,
|
|
152
153
|
tenantId: provisioned.tenantId,
|
|
153
154
|
roles: [...INITIAL_SIGNUP_ROLES],
|
|
155
|
+
...registrationLocale,
|
|
154
156
|
};
|
|
155
157
|
// Claim BEFORE spending the signup token or the binding below and
|
|
156
158
|
// before `committed` flips: if the claim throws, the `finally` still
|