@cosmicdrift/kumiko-bundled-features 0.333.0 → 0.335.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin-shell/overview-panels.js +2 -2
- package/dist/agent-tools/agent-doc-lint.d.ts +1 -0
- package/dist/agent-tools/agent-doc-lint.js +33 -3
- package/dist/agent-tools/agent-manifest.js +47 -6
- package/dist/agent-tools/filter-ops.d.ts +2 -0
- package/dist/agent-tools/filter-ops.js +4 -0
- package/dist/agent-tools/tool-catalog.js +1 -4
- package/dist/agent-tools/tool-dispatch.js +1 -4
- package/dist/audit/constants.d.ts +1 -1
- package/dist/audit/constants.js +1 -1
- package/dist/audit/handlers/list.query.js +4 -3
- package/dist/auth-email-password/handlers/change-password.write.js +2 -0
- package/dist/auth-email-password/handlers/invite-create.write.js +12 -2
- package/dist/auth-email-password/handlers/login.write.js +2 -0
- package/dist/auth-email-password/handlers/self-registration-status.query.js +1 -1
- package/dist/auth-email-password/i18n.js +2 -0
- package/dist/auth-email-password/web/auth-client.d.ts +1 -0
- package/dist/auth-email-password/web/auth-client.js +1 -1
- package/dist/auth-email-password/web/auth-gate.js +6 -8
- package/dist/auth-email-password/web/index.d.ts +1 -1
- package/dist/auth-email-password/web/index.js +1 -1
- package/dist/auth-email-password/web/invite-accept-screen.js +16 -12
- package/dist/auth-email-password/web/login-screen.js +1 -1
- package/dist/auth-email-password/web/session-bootstrap-error.d.ts +5 -1
- package/dist/auth-email-password/web/session-bootstrap-error.js +2 -2
- package/dist/auth-email-password/web/session.d.ts +4 -0
- package/dist/auth-email-password/web/session.js +15 -0
- package/dist/auth-mfa/feature.js +3 -2
- package/dist/auth-mfa/handlers/enable-confirm-preauth.write.js +2 -2
- package/dist/auth-mfa/handlers/verify.write.js +2 -0
- package/dist/auth-mfa/hmac-token-codec.js +3 -0
- package/dist/auth-mfa/mfa-challenge-token.js +4 -0
- package/dist/auth-mfa/mfa-preauth-setup-token.js +3 -0
- package/dist/auth-mfa/schema/user-mfa.d.ts +6 -0
- package/dist/auth-mfa/web/mfa-client.d.ts +1 -1
- package/dist/auth-mfa/web/mfa-client.js +4 -10
- package/dist/billing-foundation/billing-info-query.js +1 -1
- package/dist/billing-foundation/checkout-core.d.ts +5 -5
- package/dist/billing-foundation/checkout-core.js +10 -6
- package/dist/billing-foundation/entities.d.ts +8 -0
- package/dist/billing-foundation/handlers/switch-plan.write.js +5 -8
- package/dist/billing-foundation/projection.d.ts +8 -0
- package/dist/cap-counter/book-cap-usage.js +15 -5
- package/dist/cap-counter/enforce-cap.js +9 -3
- package/dist/cap-counter/entity.d.ts +1 -0
- package/dist/cap-counter/feature.js +3 -2
- package/dist/cap-counter/i18n.d.ts +1 -0
- package/dist/cap-counter/i18n.js +6 -5
- package/dist/cap-overview/handlers/caps-usage.query.js +9 -16
- package/dist/cap-overview/handlers/tenant-caps-list.query.js +41 -15
- package/dist/cap-overview/i18n.js +5 -0
- package/dist/cap-overview/web/cap-cards-panel.js +4 -1
- package/dist/cap-overview/web/cap-usage-bar.js +1 -1
- package/dist/channel-email/email-channel.js +12 -1
- package/dist/channel-in-app/tables.js +2 -6
- package/dist/compliance-profiles/handlers/sub-processors.query.js +1 -1
- package/dist/compliance-profiles/schema/profile-selection.d.ts +2 -2
- package/dist/config/db/queries/resolver.js +3 -1
- package/dist/config/handlers/set.write.js +8 -4
- package/dist/config/table.d.ts +6 -0
- package/dist/config/web/i18n.js +13 -4
- package/dist/config/write-helpers.d.ts +1 -1
- package/dist/config/write-helpers.js +11 -5
- package/dist/crypto-shredding/handlers/forget-subject.write.js +15 -8
- package/dist/custom-fields/constants.js +1 -4
- package/dist/custom-fields/entity.d.ts +4 -0
- package/dist/data-retention/resolve-for-tenant.d.ts +2 -3
- package/dist/data-retention/run-retention-cleanup.js +100 -44
- package/dist/data-retention/schema/tenant-retention-override.d.ts +4 -0
- package/dist/delivery/tables.d.ts +6 -0
- package/dist/delivery/types.d.ts +2 -1
- package/dist/delivery/types.js +7 -0
- package/dist/delivery/unsubscribe.js +5 -11
- package/dist/derivatives-sharp/render.js +27 -9
- package/dist/document-ingest-foundation/entity.d.ts +4 -0
- package/dist/document-ingest-foundation/feature.js +5 -5
- package/dist/document-ingest-foundation/providers.d.ts +1 -0
- package/dist/document-ingest-foundation/providers.js +6 -0
- package/dist/document-ingest-foundation/write-document-extract.d.ts +2 -2
- package/dist/document-ingest-foundation/write-document-extract.js +10 -0
- package/dist/feature-toggles/global-feature-state-table.js +2 -7
- package/dist/feature-toggles/toggle-sync-signal.d.ts +2 -1
- package/dist/feature-toggles/toggle-sync-signal.js +3 -2
- package/dist/file-provider-s3/feature.js +1 -1
- package/dist/files-tenant-data/index.js +3 -2
- package/dist/folders/entity.d.ts +8 -0
- package/dist/folders/handlers/clear-folder.write.js +4 -11
- package/dist/folders/handlers/set-folder.write.js +4 -10
- package/dist/form-draft/entity.d.ts +2 -0
- package/dist/form-draft/feature.js +3 -2
- package/dist/form-draft/handlers/discard.write.js +7 -8
- package/dist/form-draft/handlers/get.query.js +7 -1
- package/dist/foundation-shared/host-allowlist.d.ts +4 -0
- package/dist/foundation-shared/host-allowlist.js +14 -0
- package/dist/foundation-shared/index.d.ts +1 -0
- package/dist/foundation-shared/index.js +1 -0
- package/dist/foundation-shared/mail-host-policy.d.ts +0 -2
- package/dist/foundation-shared/mail-host-policy.js +3 -13
- package/dist/inbound-mail-foundation/connect-routes.js +7 -5
- package/dist/inbound-mail-foundation/entities.d.ts +25 -0
- package/dist/inbound-mail-foundation/projection.d.ts +20 -0
- package/dist/inbound-mail-foundation/watch-supervisor.js +47 -43
- package/dist/jobs/handlers/list.query.d.ts +7 -2
- package/dist/jobs/handlers/list.query.js +9 -2
- package/dist/jobs/handlers/retry.write.js +3 -2
- package/dist/jobs/handlers/tenant-failures.query.js +3 -9
- package/dist/jobs/job-run-logger.js +18 -2
- package/dist/jobs/job-run-table.d.ts +6 -0
- package/dist/jobs/tenant-job-failure-table.js +2 -6
- package/dist/ledger/entity.d.ts +16 -3
- package/dist/ledger/handlers/confirm-schedule-period.write.js +2 -2
- package/dist/ledger/handlers/reverse-transaction.write.js +2 -2
- package/dist/ledger/subject-reference.d.ts +4 -0
- package/dist/ledger/subject-reference.js +7 -0
- package/dist/legal-pages/feature.js +8 -1
- package/dist/managed-pages/handlers/branding.query.js +1 -1
- package/dist/managed-pages/handlers/by-slug.query.js +1 -1
- package/dist/managed-pages/handlers/by-tenant-published.query.js +1 -1
- package/dist/managed-pages/table.d.ts +12 -0
- package/dist/metrics/compute.js +7 -1
- package/dist/metrics/window.js +5 -0
- package/dist/notes-history/entity.d.ts +12 -0
- package/dist/notes-history/handlers/add-note.write.js +4 -9
- package/dist/notes-history-user-data/hooks.js +8 -1
- package/dist/personal-access-tokens/constants.d.ts +1 -0
- package/dist/personal-access-tokens/constants.js +1 -0
- package/dist/personal-access-tokens/feature.js +2 -2
- package/dist/personal-access-tokens/handlers/create.write.js +2 -2
- package/dist/personal-access-tokens/handlers/revoke.write.js +2 -2
- package/dist/personal-access-tokens/i18n.js +12 -13
- package/dist/personal-access-tokens/schema/api-token.d.ts +6 -0
- package/dist/personal-access-tokens/schema/api-token.js +2 -1
- package/dist/personal-access-tokens/screens.js +5 -1
- package/dist/rate-limiting/feature.js +2 -0
- package/dist/rate-limiting/handlers/bucket-access.js +15 -7
- package/dist/rate-limiting/i18n.d.ts +10 -0
- package/dist/rate-limiting/i18n.js +10 -0
- package/dist/secrets/feature.js +3 -2
- package/dist/secrets/table.d.ts +1 -0
- package/dist/seo/handlers/seo-config.query.js +1 -1
- package/dist/sessions/constants.d.ts +1 -0
- package/dist/sessions/constants.js +1 -0
- package/dist/sessions/feature.js +5 -3
- package/dist/sessions/handlers/revoke-all-for-user.write.d.ts +1 -0
- package/dist/sessions/handlers/revoke-all-for-user.write.js +12 -2
- package/dist/sessions/handlers/revoke.write.js +2 -2
- package/dist/sessions/schema/user-session.d.ts +4 -0
- package/dist/sessions/schema/user-session.js +2 -1
- package/dist/sessions/screens.js +5 -1
- package/dist/sessions/session-callbacks.js +2 -2
- package/dist/shared/index.d.ts +1 -1
- package/dist/shared/index.js +1 -1
- package/dist/shared/lockout-counter.js +1 -1
- package/dist/shared/parent-visibility.d.ts +5 -0
- package/dist/shared/parent-visibility.js +17 -1
- package/dist/shared/row-bound-grant.js +20 -9
- package/dist/shared/single-use-token-store.js +5 -13
- package/dist/step-dispatcher/webhook-runner.d.ts +0 -2
- package/dist/step-dispatcher/webhook-runner.js +18 -17
- package/dist/subscription-mollie/plugin-methods.d.ts +3 -2
- package/dist/subscription-mollie/plugin-methods.js +7 -1
- package/dist/subscription-mollie/verify-webhook.js +6 -0
- package/dist/subscription-stripe/feature.js +3 -3
- package/dist/subscription-stripe/plugin-methods.js +25 -6
- package/dist/subscription-stripe/verify-webhook.js +13 -6
- package/dist/tags/entity.d.ts +9 -0
- package/dist/tags/handlers/assign-tag.write.js +4 -10
- package/dist/tags/handlers/remove-tag.write.js +4 -11
- package/dist/template-resolver/api.d.ts +2 -1
- package/dist/template-resolver/api.js +1 -1
- package/dist/template-resolver/handlers/by-slug.query.d.ts +1 -1
- package/dist/template-resolver/handlers/by-slug.query.js +2 -2
- package/dist/template-resolver/handlers/by-tenant.query.d.ts +3 -2
- package/dist/template-resolver/handlers/by-tenant.query.js +2 -2
- package/dist/template-resolver/handlers/collection-item.query.d.ts +1 -1
- package/dist/template-resolver/handlers/collection-list.query.d.ts +1 -1
- package/dist/template-resolver/handlers/collection-shared.d.ts +3 -2
- package/dist/template-resolver/handlers/collection-shared.js +1 -1
- package/dist/template-resolver/handlers/find-by-id.query.d.ts +1 -1
- package/dist/template-resolver/handlers/find-by-id.query.js +1 -1
- package/dist/template-resolver/handlers/list.query.d.ts +1 -1
- package/dist/template-resolver/handlers/list.query.js +1 -1
- package/dist/template-resolver/seeding.js +41 -39
- package/dist/template-resolver/table.d.ts +26 -13
- package/dist/template-resolver/testing.js +2 -2
- package/dist/template-resolver/user-content-table.d.ts +16 -9
- package/dist/template-resolver/web/client-plugin.d.ts +1 -1
- package/dist/template-resolver-user-data/hooks.js +1 -1
- package/dist/tenant/cancel-pending-invitation.d.ts +8 -0
- package/dist/tenant/cancel-pending-invitation.js +22 -0
- package/dist/tenant/feature.js +4 -2
- package/dist/tenant/handlers/cancel-invitation.write.js +6 -21
- package/dist/tenant/handlers/member-directory.query.d.ts +1 -0
- package/dist/tenant/handlers/member-directory.query.js +17 -4
- package/dist/tenant/handlers/remove-member.write.js +9 -14
- package/dist/tenant/handlers/team-list.query.js +2 -2
- package/dist/tenant/handlers/tenant-directory.query.d.ts +1 -0
- package/dist/tenant/handlers/tenant-directory.query.js +13 -3
- package/dist/tenant/handlers/update-member-roles.write.d.ts +1 -1
- package/dist/tenant/handlers/update-member-roles.write.js +28 -12
- package/dist/tenant/i18n.js +4 -0
- package/dist/tenant/index.d.ts +1 -1
- package/dist/tenant/index.js +1 -1
- package/dist/tenant/invitation-table.d.ts +8 -2
- package/dist/tenant/last-tenant-admin.d.ts +1 -1
- package/dist/tenant/last-tenant-admin.js +5 -3
- package/dist/tenant/membership-table.d.ts +4 -0
- package/dist/tenant/schema/tenant.d.ts +8 -2
- package/dist/tenant/seeding.d.ts +4 -0
- package/dist/tenant/seeding.js +9 -2
- package/dist/tenant-lifecycle/boot-checks.d.ts +1 -0
- package/dist/tenant-lifecycle/boot-checks.js +27 -1
- package/dist/tenant-lifecycle/feature.js +2 -1
- package/dist/tenant-lifecycle/stages.js +2 -2
- package/dist/tenant-settings/feature.js +9 -6
- package/dist/tier-engine/entity.d.ts +2 -0
- package/dist/tier-engine/feature.js +3 -2
- package/dist/tier-engine/tier-resolver.d.ts +2 -0
- package/dist/user/command-schemas.d.ts +2 -2
- package/dist/user/handlers/create.write.d.ts +1 -1
- package/dist/user/handlers/create.write.js +8 -2
- package/dist/user/handlers/update.write.d.ts +1 -1
- package/dist/user/handlers/update.write.js +8 -2
- package/dist/user/schema/user.d.ts +14 -2
- package/dist/user-data-rights/db/queries/export-jobs.d.ts +2 -0
- package/dist/user-data-rights/feature.js +22 -9
- package/dist/user-data-rights/handlers/confirm-deletion-by-token.write.js +4 -7
- package/dist/user-data-rights/handlers/deletion-grace-period.d.ts +3 -2
- package/dist/user-data-rights/handlers/deletion-grace-period.js +30 -8
- package/dist/user-data-rights/handlers/download-by-token.query.js +1 -1
- package/dist/user-data-rights/handlers/request-deletion-by-email.write.js +1 -1
- package/dist/user-data-rights/handlers/request-deletion.write.js +3 -6
- package/dist/user-data-rights/lib/update-user-lifecycle.js +7 -14
- package/dist/user-data-rights/run-export-jobs.d.ts +1 -0
- package/dist/user-data-rights/run-export-jobs.js +74 -41
- package/dist/user-data-rights/run-forget-cleanup.js +2 -2
- package/dist/user-data-rights/run-user-export.js +8 -4
- package/dist/user-data-rights/schema/download-attempt.d.ts +14 -0
- package/dist/user-data-rights/schema/download-token.d.ts +8 -0
- package/dist/user-data-rights/schema/export-job.d.ts +8 -2
- package/dist/user-data-rights-defaults/hooks/file-ref.userdata-hook.js +4 -1
- package/dist/user-profile/feature.js +6 -1
- package/dist/user-profile/handlers/change-email.write.js +2 -0
- package/dist/workflow-runner/db/queries/due-runs.js +6 -9
- package/dist/workflow-runner/event-subscriber.js +29 -9
- package/dist/workflow-runner/handlers/resume-run.write.js +8 -5
- package/dist/workflow-runner/pending-projection.js +8 -1
- package/package.json +11 -9
- package/src/agent-tools/changes.json +12 -0
- package/src/auth-email-password/changes.json +18 -0
- package/src/auth-mfa/changes.json +6 -0
- package/src/billing-foundation/changes.json +7 -0
- package/src/cap-counter/changes.json +7 -0
- package/src/cap-overview/changes.json +12 -0
- package/src/channel-email/changes.json +6 -0
- package/src/config/changes.json +6 -0
- package/src/crypto-shredding/changes.json +6 -0
- package/src/data-retention/changes.json +12 -0
- package/src/delivery/changes.json +7 -0
- package/src/derivatives-sharp/changes.json +11 -0
- package/src/document-ingest-foundation/changes.json +12 -0
- package/src/inbound-mail-foundation/changes.json +12 -0
- package/src/jobs/changes.json +13 -0
- package/src/legal-pages/changes.json +8 -1
- package/src/managed-pages/changes.json +5 -0
- package/src/metrics/changes.json +6 -0
- package/src/rate-limiting/changes.json +5 -0
- package/src/secrets/changes.json +7 -0
- package/src/sessions/changes.json +5 -0
- package/src/shared/changes.json +6 -0
- package/src/step-dispatcher/changes.json +5 -0
- package/src/subscription-mollie/changes.json +7 -0
- package/src/subscription-stripe/changes.json +11 -0
- package/src/template-resolver/changes.json +7 -0
- package/src/tenant/changes.json +29 -0
- package/src/user-data-rights/changes.json +22 -0
- package/src/workflow-runner/changes.json +6 -0
|
@@ -3,5 +3,5 @@ import { writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
|
3
3
|
export declare const LAST_TENANT_ADMIN_LOCK_NAMESPACE = 1952539757;
|
|
4
4
|
type TenantDb = Parameters<typeof acquireNamespacedAdvisoryLock>[0];
|
|
5
5
|
/** Refuse demoting/removing the last TenantAdmin for `tenantId`. */
|
|
6
|
-
export declare function assertNotLastTenantAdmin(db: TenantDb, tenantId: string, userId: string): Promise<ReturnType<typeof writeFailure> | undefined>;
|
|
6
|
+
export declare function assertNotLastTenantAdmin(db: TenantDb, tenantId: string, userId: string, action?: "demote" | "remove"): Promise<ReturnType<typeof writeFailure> | undefined>;
|
|
7
7
|
export {};
|
|
@@ -8,14 +8,16 @@ import { tenantMembershipsTable } from "./membership-table.js";
|
|
|
8
8
|
// (dispatcher batch wraps handlers in transaction — xact lock holds through update).
|
|
9
9
|
export const LAST_TENANT_ADMIN_LOCK_NAMESPACE = 0x7461646d; // 'tadm'
|
|
10
10
|
/** Refuse demoting/removing the last TenantAdmin for `tenantId`. */
|
|
11
|
-
export async function assertNotLastTenantAdmin(db, tenantId, userId) {
|
|
11
|
+
export async function assertNotLastTenantAdmin(db, tenantId, userId, action = "demote") {
|
|
12
12
|
await acquireNamespacedAdvisoryLock(db, LAST_TENANT_ADMIN_LOCK_NAMESPACE, tenantId);
|
|
13
13
|
const allMemberships = await selectMany(db, tenantMembershipsTable, { tenantId });
|
|
14
14
|
const adminCount = allMemberships.filter((m) => parseRoles(m["roles"]).includes("TenantAdmin")).length;
|
|
15
15
|
if (adminCount <= 1) {
|
|
16
16
|
return writeFailure(new ConflictError({
|
|
17
|
-
message:
|
|
18
|
-
i18nKey: "
|
|
17
|
+
message: `cannot ${action} the last tenant admin`,
|
|
18
|
+
i18nKey: action === "remove"
|
|
19
|
+
? "tenant.errors.cannotRemoveLastTenantAdmin"
|
|
20
|
+
: "tenant.errors.cannotDemoteLastTenantAdmin",
|
|
19
21
|
details: {
|
|
20
22
|
reason: TenantErrors.lastTenantAdmin,
|
|
21
23
|
userId,
|
|
@@ -9,6 +9,7 @@ export declare const tenantMembershipEntity: import("@cosmicdrift/kumiko-types/f
|
|
|
9
9
|
readonly filterable?: boolean;
|
|
10
10
|
readonly encrypted?: boolean;
|
|
11
11
|
readonly sensitive?: boolean;
|
|
12
|
+
readonly writeOnly?: boolean;
|
|
12
13
|
readonly readAsInstruction?: boolean;
|
|
13
14
|
readonly format?: "email" | "url" | "phone" | "password";
|
|
14
15
|
readonly default?: string;
|
|
@@ -29,6 +30,7 @@ export declare const tenantMembershipEntity: import("@cosmicdrift/kumiko-types/f
|
|
|
29
30
|
readonly filterable?: boolean;
|
|
30
31
|
readonly encrypted?: boolean;
|
|
31
32
|
readonly sensitive?: boolean;
|
|
33
|
+
readonly writeOnly?: boolean;
|
|
32
34
|
readonly readAsInstruction?: boolean;
|
|
33
35
|
readonly format?: "email" | "url" | "phone" | "password";
|
|
34
36
|
readonly default?: string;
|
|
@@ -51,6 +53,7 @@ export declare const tenantMembershipsTable: import("@cosmicdrift/kumiko-framewo
|
|
|
51
53
|
readonly filterable?: boolean;
|
|
52
54
|
readonly encrypted?: boolean;
|
|
53
55
|
readonly sensitive?: boolean;
|
|
56
|
+
readonly writeOnly?: boolean;
|
|
54
57
|
readonly readAsInstruction?: boolean;
|
|
55
58
|
readonly format?: "email" | "url" | "phone" | "password";
|
|
56
59
|
readonly default?: string;
|
|
@@ -71,6 +74,7 @@ export declare const tenantMembershipsTable: import("@cosmicdrift/kumiko-framewo
|
|
|
71
74
|
readonly filterable?: boolean;
|
|
72
75
|
readonly encrypted?: boolean;
|
|
73
76
|
readonly sensitive?: boolean;
|
|
77
|
+
readonly writeOnly?: boolean;
|
|
74
78
|
readonly readAsInstruction?: boolean;
|
|
75
79
|
readonly format?: "email" | "url" | "phone" | "password";
|
|
76
80
|
readonly default?: string;
|
|
@@ -11,6 +11,7 @@ export declare const tenantEntity: import("@cosmicdrift/kumiko-types/fields").En
|
|
|
11
11
|
readonly filterable?: boolean;
|
|
12
12
|
readonly encrypted?: boolean;
|
|
13
13
|
readonly sensitive?: boolean;
|
|
14
|
+
readonly writeOnly?: boolean;
|
|
14
15
|
readonly readAsInstruction?: boolean;
|
|
15
16
|
readonly format?: "email" | "url" | "phone" | "password";
|
|
16
17
|
readonly default?: string;
|
|
@@ -31,6 +32,7 @@ export declare const tenantEntity: import("@cosmicdrift/kumiko-types/fields").En
|
|
|
31
32
|
readonly filterable?: boolean;
|
|
32
33
|
readonly encrypted?: boolean;
|
|
33
34
|
readonly sensitive?: boolean;
|
|
35
|
+
readonly writeOnly?: boolean;
|
|
34
36
|
readonly readAsInstruction?: boolean;
|
|
35
37
|
readonly format?: "email" | "url" | "phone" | "password";
|
|
36
38
|
readonly default?: string;
|
|
@@ -63,7 +65,7 @@ export declare const tenantEntity: import("@cosmicdrift/kumiko-types/fields").En
|
|
|
63
65
|
readonly destroying?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
|
|
64
66
|
} | undefined;
|
|
65
67
|
readonly optionsQuery?: string;
|
|
66
|
-
readonly optionsQueryPayload?:
|
|
68
|
+
readonly optionsQueryPayload?: import("@cosmicdrift/kumiko-types/fields").OptionsQueryPayload;
|
|
67
69
|
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
68
70
|
required: true;
|
|
69
71
|
};
|
|
@@ -92,6 +94,7 @@ export declare const tenantEntity: import("@cosmicdrift/kumiko-types/fields").En
|
|
|
92
94
|
readonly filterable?: boolean;
|
|
93
95
|
readonly encrypted?: boolean;
|
|
94
96
|
readonly sensitive?: boolean;
|
|
97
|
+
readonly writeOnly?: boolean;
|
|
95
98
|
readonly readAsInstruction?: boolean;
|
|
96
99
|
readonly format?: "email" | "url" | "phone" | "password";
|
|
97
100
|
readonly default?: string;
|
|
@@ -159,6 +162,7 @@ export declare const tenantTable: import("@cosmicdrift/kumiko-framework/db").Ent
|
|
|
159
162
|
readonly filterable?: boolean;
|
|
160
163
|
readonly encrypted?: boolean;
|
|
161
164
|
readonly sensitive?: boolean;
|
|
165
|
+
readonly writeOnly?: boolean;
|
|
162
166
|
readonly readAsInstruction?: boolean;
|
|
163
167
|
readonly format?: "email" | "url" | "phone" | "password";
|
|
164
168
|
readonly default?: string;
|
|
@@ -179,6 +183,7 @@ export declare const tenantTable: import("@cosmicdrift/kumiko-framework/db").Ent
|
|
|
179
183
|
readonly filterable?: boolean;
|
|
180
184
|
readonly encrypted?: boolean;
|
|
181
185
|
readonly sensitive?: boolean;
|
|
186
|
+
readonly writeOnly?: boolean;
|
|
182
187
|
readonly readAsInstruction?: boolean;
|
|
183
188
|
readonly format?: "email" | "url" | "phone" | "password";
|
|
184
189
|
readonly default?: string;
|
|
@@ -211,7 +216,7 @@ export declare const tenantTable: import("@cosmicdrift/kumiko-framework/db").Ent
|
|
|
211
216
|
readonly destroying?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
|
|
212
217
|
} | undefined;
|
|
213
218
|
readonly optionsQuery?: string;
|
|
214
|
-
readonly optionsQueryPayload?:
|
|
219
|
+
readonly optionsQueryPayload?: import("@cosmicdrift/kumiko-types/fields").OptionsQueryPayload;
|
|
215
220
|
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
216
221
|
required: true;
|
|
217
222
|
};
|
|
@@ -240,6 +245,7 @@ export declare const tenantTable: import("@cosmicdrift/kumiko-framework/db").Ent
|
|
|
240
245
|
readonly filterable?: boolean;
|
|
241
246
|
readonly encrypted?: boolean;
|
|
242
247
|
readonly sensitive?: boolean;
|
|
248
|
+
readonly writeOnly?: boolean;
|
|
243
249
|
readonly readAsInstruction?: boolean;
|
|
244
250
|
readonly format?: "email" | "url" | "phone" | "password";
|
|
245
251
|
readonly default?: string;
|
package/dist/tenant/seeding.d.ts
CHANGED
|
@@ -38,6 +38,10 @@ export declare function fireEntityPostSave(hooks: SeedTenantHooks | undefined, p
|
|
|
38
38
|
* a second call for the same `id` is a no-op (no update path). Same
|
|
39
39
|
* TX-semantics as the real `TenantHandlers.create`, minus the SystemAdmin-
|
|
40
40
|
* access-check and minus ConflictError-on-duplicate.
|
|
41
|
+
*
|
|
42
|
+
* `hooks` run only for the call that actually creates the tenant: a call that
|
|
43
|
+
* finds the tenant/stream already present (or loses a concurrent create race)
|
|
44
|
+
* returns without firing postSave.
|
|
41
45
|
*/
|
|
42
46
|
export declare function seedTenant(db: DbRunner, options: SeedTenantOptions, hooks?: SeedTenantHooks): Promise<{
|
|
43
47
|
id: TenantId;
|
package/dist/tenant/seeding.js
CHANGED
|
@@ -124,6 +124,10 @@ target) {
|
|
|
124
124
|
* a second call for the same `id` is a no-op (no update path). Same
|
|
125
125
|
* TX-semantics as the real `TenantHandlers.create`, minus the SystemAdmin-
|
|
126
126
|
* access-check and minus ConflictError-on-duplicate.
|
|
127
|
+
*
|
|
128
|
+
* `hooks` run only for the call that actually creates the tenant: a call that
|
|
129
|
+
* finds the tenant/stream already present (or loses a concurrent create race)
|
|
130
|
+
* returns without firing postSave.
|
|
127
131
|
*/
|
|
128
132
|
export async function seedTenant(db, options, hooks) {
|
|
129
133
|
const by = options.by ?? TestUsers.systemAdmin;
|
|
@@ -146,8 +150,11 @@ export async function seedTenant(db, options, hooks) {
|
|
|
146
150
|
const result = await tenantExecutor.create({ id: options.id, key: options.key, name: options.name }, by, tdb);
|
|
147
151
|
if (!result.isSuccess) {
|
|
148
152
|
// Same idempotency case as the stream-version check above, only detected
|
|
149
|
-
// one instruction later: a
|
|
150
|
-
//
|
|
153
|
+
// one instruction later: a concurrent writer (e.g. a parallel seedTenant
|
|
154
|
+
// for the same id) created the stream between the version read above and
|
|
155
|
+
// this create; the executor confines the failed append to a savepoint, so
|
|
156
|
+
// the surrounding TX stays usable. Both paths return without firing
|
|
157
|
+
// postSave — hooks only run for the call that actually created the tenant.
|
|
151
158
|
if (result.error.code === "version_conflict")
|
|
152
159
|
return { id: options.id };
|
|
153
160
|
throw new Error(`seedTenant failed: ${result.error.code} — ${JSON.stringify(result.error.details ?? {})}`);
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
import { type FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
2
|
export declare function validateTenantDataHookCoverage(features: readonly FeatureDefinition[]): void;
|
|
3
|
+
export declare function validateTenantDestroyHookShapes(features: readonly FeatureDefinition[]): void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EXT_TENANT_DATA, } from "@cosmicdrift/kumiko-framework/engine";
|
|
1
|
+
import { EXT_EXTERNAL_RESOURCE, EXT_INFRA_RESOURCE, EXT_SEARCH_ADAPTER, EXT_STORAGE_PROVIDER, EXT_TENANT_DATA, isTenantDataExtensionHooks, isTenantResourceExtensionHooks, } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
2
|
import { entitiesOf } from "../shared/index.js";
|
|
3
3
|
// V4: tenantOwned-entity-without-hook gate. Mirrors user-data-rights' V3
|
|
4
4
|
// (validateGdprPiiHookCoverage) but for EXT_TENANT_DATA. Registered as this
|
|
@@ -34,3 +34,29 @@ export function validateTenantDataHookCoverage(features) {
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
|
+
const TENANT_RESOURCE_EXTENSIONS = new Set([
|
|
38
|
+
EXT_STORAGE_PROVIDER,
|
|
39
|
+
EXT_SEARCH_ADAPTER,
|
|
40
|
+
EXT_EXTERNAL_RESOURCE,
|
|
41
|
+
EXT_INFRA_RESOURCE,
|
|
42
|
+
]);
|
|
43
|
+
// Malformed destroy registrations would otherwise only throw when a tenant
|
|
44
|
+
// destruction reaches the stage, possibly months after deploy, and stall the
|
|
45
|
+
// whole pipeline (stages.ts keeps its own throw as defense in depth). JS
|
|
46
|
+
// plugins and `as` casts slip past the type check.
|
|
47
|
+
export function validateTenantDestroyHookShapes(features) {
|
|
48
|
+
for (const feature of features) {
|
|
49
|
+
for (const usage of feature.extensionUsages) {
|
|
50
|
+
const isTenantData = usage.extensionName === EXT_TENANT_DATA;
|
|
51
|
+
if (!isTenantData && !TENANT_RESOURCE_EXTENSIONS.has(usage.extensionName))
|
|
52
|
+
continue;
|
|
53
|
+
const isValid = isTenantData
|
|
54
|
+
? isTenantDataExtensionHooks(usage.options)
|
|
55
|
+
: isTenantResourceExtensionHooks(usage.options);
|
|
56
|
+
if (isValid)
|
|
57
|
+
continue;
|
|
58
|
+
const requiredFn = isTenantData ? "destroy" : "destroyTenant";
|
|
59
|
+
throw new Error(`[kumiko:boot] ${usage.extensionName} registration for "${usage.entityName}" (feature "${feature.name}") has no ${requiredFn} function — tenant destroy would fail at this stage.`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineFeature, EXT_EXTERNAL_RESOURCE, EXT_INFRA_RESOURCE, EXT_SEARCH_ADAPTER, EXT_STORAGE_PROVIDER, EXT_TENANT_DATA, EXT_TENANT_LIFECYCLE_STATUS, } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
-
import { validateTenantDataHookCoverage } from "./boot-checks.js";
|
|
2
|
+
import { validateTenantDataHookCoverage, validateTenantDestroyHookShapes } from "./boot-checks.js";
|
|
3
3
|
import { DESTRUCTION_CANCELLED_EVENT_SHORT, DESTRUCTION_REQUESTED_EVENT_SHORT, TENANT_DESTRUCTION_COMPLETED_EVENT_SHORT, TENANT_DESTRUCTION_FAILED_EVENT_SHORT, TENANT_DESTRUCTION_STAGE_ABANDONED_EVENT_SHORT, TENANT_DESTRUCTION_STAGE_FAILED_EVENT_SHORT, TENANT_DESTRUCTION_STAGE_STARTED_EVENT_SHORT, TENANT_DESTRUCTION_STAGE_SUCCEEDED_EVENT_SHORT, TENANT_DESTRUCTION_STARTED_EVENT_SHORT, } from "./constants.js";
|
|
4
4
|
import { destructionCancelledSchema, destructionRequestedSchema, tenantDestructionCompletedSchema, tenantDestructionFailedSchema, tenantDestructionStageAbandonedSchema, tenantDestructionStageFailedSchema, tenantDestructionStageStartedSchema, tenantDestructionStageSucceededSchema, tenantDestructionStartedSchema, } from "./events.js";
|
|
5
5
|
import { cancelDestructionWrite } from "./handlers/cancel-destruction.write.js";
|
|
@@ -36,6 +36,7 @@ export function createTenantLifecycleFeature() {
|
|
|
36
36
|
// EXT_TENANT_DATA: its own mount is the trigger, matching the original
|
|
37
37
|
// guard's "tenant-lifecycle mounted" gate exactly.
|
|
38
38
|
r.bootCheck(({ features }) => validateTenantDataHookCoverage(features));
|
|
39
|
+
r.bootCheck(({ features }) => validateTenantDestroyHookShapes(features));
|
|
39
40
|
r.defineEvent(DESTRUCTION_REQUESTED_EVENT_SHORT, destructionRequestedSchema, {
|
|
40
41
|
piiFields: "none",
|
|
41
42
|
});
|
|
@@ -2,7 +2,7 @@ import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
|
2
2
|
import { configuredPiiSubjectKms } from "@cosmicdrift/kumiko-framework/crypto";
|
|
3
3
|
import { createEventStoreExecutor, createTenantDb, } from "@cosmicdrift/kumiko-framework/db";
|
|
4
4
|
import { createSystemUser, EXT_EXTERNAL_RESOURCE, EXT_INFRA_RESOURCE, EXT_SEARCH_ADAPTER, EXT_STORAGE_PROVIDER, EXT_TENANT_DATA, extensionUsageEscapeHatchReason, isTenantDataExtensionHooks, isTenantResourceExtensionHooks, } from "@cosmicdrift/kumiko-framework/engine";
|
|
5
|
-
import { createEscapeHatchReporter } from "@cosmicdrift/kumiko-framework/pipeline";
|
|
5
|
+
import { createEscapeHatchReporter, UNATTRIBUTED_ACTOR, } from "@cosmicdrift/kumiko-framework/pipeline";
|
|
6
6
|
import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
|
|
7
7
|
import { tenantEntity, tenantMembershipEntity, tenantMembershipsTable, tenantTable, } from "../tenant/index.js";
|
|
8
8
|
import { invalidateTenantLifecycleGate } from "./lifecycle-gate.js";
|
|
@@ -30,7 +30,7 @@ async function runTenantDataHooks(ctx) {
|
|
|
30
30
|
const report = createEscapeHatchReporter({
|
|
31
31
|
handler: `${EXT_TENANT_DATA}:${usage.entityName}`,
|
|
32
32
|
tenantId: ctx.tenantId,
|
|
33
|
-
actor: ctx.actor ??
|
|
33
|
+
actor: ctx.actor ?? UNATTRIBUTED_ACTOR,
|
|
34
34
|
sink: ctx.escapeHatchAuditSink,
|
|
35
35
|
});
|
|
36
36
|
const hookCtx = {
|
|
@@ -21,21 +21,24 @@ export function createTenantSettingsFeature(opts = {}) {
|
|
|
21
21
|
r.config({ keys: buildTenantSettingsKeys(opts) });
|
|
22
22
|
r.translations({
|
|
23
23
|
keys: {
|
|
24
|
-
"tenant-settings.currency": { en: "Default
|
|
25
|
-
"tenant-settings.locale": { en: "Default
|
|
24
|
+
"tenant-settings.currency": { en: "Default currency" },
|
|
25
|
+
"tenant-settings.locale": { en: "Default locale" },
|
|
26
26
|
// Settings-Hub-derived configEdit screen title (buildConfigFeatureSchema) —
|
|
27
27
|
// required by the i18n boot-validator whenever a feature masks config keys.
|
|
28
|
-
"screen:tenant-settings-system.title": { en: "Tenant
|
|
29
|
-
"screen:tenant-settings-tenant.title": { en: "Tenant
|
|
28
|
+
"screen:tenant-settings-system.title": { en: "Tenant defaults" },
|
|
29
|
+
"screen:tenant-settings-tenant.title": { en: "Tenant settings" },
|
|
30
30
|
// Settings-Hub nav label + configEdit section heading (same key, both
|
|
31
31
|
// scopes fall back here — see buildConfigFeatureSchema).
|
|
32
|
-
"tenant-settings.settings": { en: "Tenant
|
|
32
|
+
"tenant-settings.settings": { en: "Tenant settings" },
|
|
33
|
+
// Section heading below the page title (the page title is the same words as
|
|
34
|
+
// `.settings`, which RenderEdit would drop as a repeat).
|
|
35
|
+
"tenant-settings.settings.section": { en: "Currency and language" },
|
|
33
36
|
// Both currency/locale are tenant-home with admin write access, which
|
|
34
37
|
// cascades a SystemAdmin-only "set the platform default" screen up to
|
|
35
38
|
// the system audience — scoped override so its nav entry reads
|
|
36
39
|
// differently from the tenant-home entry instead of repeating the
|
|
37
40
|
// same words under both "Platform" and "Organization".
|
|
38
|
-
"tenant-settings.settings.system": { en: "Tenant
|
|
41
|
+
"tenant-settings.settings.system": { en: "Tenant defaults" },
|
|
39
42
|
},
|
|
40
43
|
});
|
|
41
44
|
});
|
|
@@ -9,6 +9,7 @@ export declare const tierAssignmentEntity: import("@cosmicdrift/kumiko-types/fie
|
|
|
9
9
|
readonly filterable?: boolean;
|
|
10
10
|
readonly encrypted?: boolean;
|
|
11
11
|
readonly sensitive?: boolean;
|
|
12
|
+
readonly writeOnly?: boolean;
|
|
12
13
|
readonly readAsInstruction?: boolean;
|
|
13
14
|
readonly format?: "email" | "url" | "phone" | "password";
|
|
14
15
|
readonly default?: string;
|
|
@@ -29,6 +30,7 @@ export declare const tierAssignmentEntity: import("@cosmicdrift/kumiko-types/fie
|
|
|
29
30
|
readonly filterable?: boolean;
|
|
30
31
|
readonly encrypted?: boolean;
|
|
31
32
|
readonly sensitive?: boolean;
|
|
33
|
+
readonly writeOnly?: boolean;
|
|
32
34
|
readonly readAsInstruction?: boolean;
|
|
33
35
|
readonly format?: "email" | "url" | "phone" | "password";
|
|
34
36
|
readonly default?: string;
|
|
@@ -129,11 +129,12 @@ export function createTierEngineFeature(opts = {}) {
|
|
|
129
129
|
// Standard-CRUD via Helper.
|
|
130
130
|
r.writeHandler(defineEntityCreateHandler("tier-assignment", tierAssignmentEntity, {
|
|
131
131
|
...writeAccess,
|
|
132
|
-
|
|
132
|
+
// Bypasses the feature-gate refresh that set-tenant-tier performs.
|
|
133
|
+
agent: { expose: false },
|
|
133
134
|
}));
|
|
134
135
|
r.writeHandler(defineEntityUpdateHandler("tier-assignment", tierAssignmentEntity, {
|
|
135
136
|
...writeAccess,
|
|
136
|
-
|
|
137
|
+
agent: { expose: false },
|
|
137
138
|
}));
|
|
138
139
|
// Reads.
|
|
139
140
|
r.queryHandler(defineEntityListHandler("tier-assignment", tierAssignmentEntity, {
|
|
@@ -17,6 +17,7 @@ export declare function createTierResolver<TTier extends string, TCaps>(deps: Ti
|
|
|
17
17
|
readonly filterable?: boolean;
|
|
18
18
|
readonly encrypted?: boolean;
|
|
19
19
|
readonly sensitive?: boolean;
|
|
20
|
+
readonly writeOnly?: boolean;
|
|
20
21
|
readonly readAsInstruction?: boolean;
|
|
21
22
|
readonly format?: "email" | "url" | "phone" | "password";
|
|
22
23
|
readonly default?: string;
|
|
@@ -37,6 +38,7 @@ export declare function createTierResolver<TTier extends string, TCaps>(deps: Ti
|
|
|
37
38
|
readonly filterable?: boolean;
|
|
38
39
|
readonly encrypted?: boolean;
|
|
39
40
|
readonly sensitive?: boolean;
|
|
41
|
+
readonly writeOnly?: boolean;
|
|
40
42
|
readonly readAsInstruction?: boolean;
|
|
41
43
|
readonly format?: "email" | "url" | "phone" | "password";
|
|
42
44
|
readonly default?: string;
|
|
@@ -3,7 +3,7 @@ export declare const UserCommandSchemas: {
|
|
|
3
3
|
email: import("zod").ZodEmail;
|
|
4
4
|
passwordHash: import("zod").ZodOptional<import("zod").ZodString>;
|
|
5
5
|
displayName: import("zod").ZodString;
|
|
6
|
-
locale: import("zod").ZodOptional<import("zod").ZodString
|
|
6
|
+
locale: import("zod").ZodOptional<import("zod").ZodPipe<import("zod").ZodString, import("zod").ZodTransform<string, string>>>;
|
|
7
7
|
timezone: import("zod").ZodOptional<import("zod").ZodString>;
|
|
8
8
|
roles: import("zod").ZodOptional<import("zod").ZodUnion<readonly [import("zod").ZodArray<import("zod").ZodString>, import("zod").ZodPipe<import("zod").ZodString, import("zod").ZodTransform<string[], string>>]>>;
|
|
9
9
|
}, import("zod/v4/core").$strip>;
|
|
@@ -12,7 +12,7 @@ export declare const UserCommandSchemas: {
|
|
|
12
12
|
version: import("zod").ZodNumber;
|
|
13
13
|
changes: import("zod").ZodObject<{
|
|
14
14
|
displayName: import("zod").ZodOptional<import("zod").ZodString>;
|
|
15
|
-
locale: import("zod").ZodOptional<import("zod").ZodString
|
|
15
|
+
locale: import("zod").ZodOptional<import("zod").ZodPipe<import("zod").ZodString, import("zod").ZodTransform<string, string>>>;
|
|
16
16
|
timezone: import("zod").ZodOptional<import("zod").ZodString>;
|
|
17
17
|
email: import("zod").ZodOptional<import("zod").ZodEmail>;
|
|
18
18
|
passwordHash: import("zod").ZodOptional<import("zod").ZodString>;
|
|
@@ -3,7 +3,7 @@ export declare const createWrite: import("@cosmicdrift/kumiko-types/define-handl
|
|
|
3
3
|
email: z.ZodEmail;
|
|
4
4
|
passwordHash: z.ZodOptional<z.ZodString>;
|
|
5
5
|
displayName: z.ZodString;
|
|
6
|
-
locale: z.ZodOptional<z.ZodString
|
|
6
|
+
locale: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
7
7
|
timezone: z.ZodOptional<z.ZodString>;
|
|
8
8
|
roles: z.ZodOptional<z.ZodUnion<readonly [z.ZodArray<z.ZodString>, z.ZodPipe<z.ZodString, z.ZodTransform<string[], string>>]>>;
|
|
9
9
|
}, z.core.$strip>, import("@cosmicdrift/kumiko-types/hooks").SaveContext, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
@@ -2,7 +2,7 @@ import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
|
2
2
|
import { createEventStoreExecutor } from "@cosmicdrift/kumiko-framework/db";
|
|
3
3
|
import { defineWriteHandler, findForbiddenRoleAssignment, } from "@cosmicdrift/kumiko-framework/engine";
|
|
4
4
|
import { AccessDeniedError, ConflictError, InternalError, writeFailure, } from "@cosmicdrift/kumiko-framework/errors";
|
|
5
|
-
import { isValidLocaleTag } from "@cosmicdrift/kumiko-framework/i18n";
|
|
5
|
+
import { canonicalizeLocaleTag, isValidLocaleTag } from "@cosmicdrift/kumiko-framework/i18n";
|
|
6
6
|
import { isValidIanaTimeZone } from "@cosmicdrift/kumiko-framework/time";
|
|
7
7
|
import { parseRoles } from "@cosmicdrift/kumiko-framework/utils";
|
|
8
8
|
import * as z from "zod";
|
|
@@ -28,7 +28,13 @@ export const createWrite = defineWriteHandler({
|
|
|
28
28
|
email: z.email(),
|
|
29
29
|
passwordHash: z.string().optional(),
|
|
30
30
|
displayName: z.string().min(1).max(100),
|
|
31
|
-
locale: z
|
|
31
|
+
locale: z
|
|
32
|
+
.string()
|
|
33
|
+
.min(2)
|
|
34
|
+
.max(10)
|
|
35
|
+
.refine(isValidLocaleTag, "invalid locale tag")
|
|
36
|
+
.transform(canonicalizeLocaleTag)
|
|
37
|
+
.optional(),
|
|
32
38
|
timezone: z.string().max(64).refine(isValidIanaTimeZone, "invalid IANA time zone").optional(),
|
|
33
39
|
// Field-level write access (privileged) is defense-in-depth — create is
|
|
34
40
|
// already system/SystemAdmin-only.
|
|
@@ -4,7 +4,7 @@ export declare const updateWrite: import("@cosmicdrift/kumiko-types/define-handl
|
|
|
4
4
|
version: z.ZodNumber;
|
|
5
5
|
changes: z.ZodObject<{
|
|
6
6
|
displayName: z.ZodOptional<z.ZodString>;
|
|
7
|
-
locale: z.ZodOptional<z.ZodString
|
|
7
|
+
locale: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
8
8
|
timezone: z.ZodOptional<z.ZodString>;
|
|
9
9
|
email: z.ZodOptional<z.ZodEmail>;
|
|
10
10
|
passwordHash: z.ZodOptional<z.ZodString>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createEventStoreExecutor } from "@cosmicdrift/kumiko-framework/db";
|
|
2
2
|
import { access, defineWriteHandler, hasAccess } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
3
|
import { AccessDeniedError, InternalError, writeFailure, } from "@cosmicdrift/kumiko-framework/errors";
|
|
4
|
-
import { isValidLocaleTag } from "@cosmicdrift/kumiko-framework/i18n";
|
|
4
|
+
import { canonicalizeLocaleTag, isValidLocaleTag } from "@cosmicdrift/kumiko-framework/i18n";
|
|
5
5
|
import { isValidIanaTimeZone } from "@cosmicdrift/kumiko-framework/time";
|
|
6
6
|
import * as z from "zod";
|
|
7
7
|
import { UserErrors } from "../constants.js";
|
|
@@ -24,7 +24,13 @@ export const updateWrite = defineWriteHandler({
|
|
|
24
24
|
version: z.number(),
|
|
25
25
|
changes: z.object({
|
|
26
26
|
displayName: z.string().min(1).max(100).optional(),
|
|
27
|
-
locale: z
|
|
27
|
+
locale: z
|
|
28
|
+
.string()
|
|
29
|
+
.min(2)
|
|
30
|
+
.max(10)
|
|
31
|
+
.refine(isValidLocaleTag, "invalid locale tag")
|
|
32
|
+
.transform(canonicalizeLocaleTag)
|
|
33
|
+
.optional(),
|
|
28
34
|
timezone: z.string().max(64).refine(isValidIanaTimeZone, "invalid IANA time zone").optional(),
|
|
29
35
|
email: z.email().optional(),
|
|
30
36
|
passwordHash: z.string().optional(),
|
|
@@ -38,6 +38,7 @@ export declare const userEntity: import("@cosmicdrift/kumiko-types/fields").Enti
|
|
|
38
38
|
readonly filterable?: boolean;
|
|
39
39
|
readonly encrypted?: boolean;
|
|
40
40
|
readonly sensitive?: boolean;
|
|
41
|
+
readonly writeOnly?: boolean;
|
|
41
42
|
readonly readAsInstruction?: boolean;
|
|
42
43
|
readonly format?: "email" | "url" | "phone" | "password";
|
|
43
44
|
readonly default?: string;
|
|
@@ -58,6 +59,7 @@ export declare const userEntity: import("@cosmicdrift/kumiko-types/fields").Enti
|
|
|
58
59
|
readonly filterable?: boolean;
|
|
59
60
|
readonly encrypted?: boolean;
|
|
60
61
|
readonly sensitive?: boolean;
|
|
62
|
+
readonly writeOnly?: boolean;
|
|
61
63
|
readonly readAsInstruction?: boolean;
|
|
62
64
|
readonly format?: "email" | "url" | "phone" | "password";
|
|
63
65
|
readonly default?: string;
|
|
@@ -78,6 +80,7 @@ export declare const userEntity: import("@cosmicdrift/kumiko-types/fields").Enti
|
|
|
78
80
|
readonly filterable?: boolean;
|
|
79
81
|
readonly encrypted?: boolean;
|
|
80
82
|
readonly sensitive?: boolean;
|
|
83
|
+
readonly writeOnly?: boolean;
|
|
81
84
|
readonly readAsInstruction?: boolean;
|
|
82
85
|
readonly format?: "email" | "url" | "phone" | "password";
|
|
83
86
|
readonly default?: string;
|
|
@@ -98,6 +101,7 @@ export declare const userEntity: import("@cosmicdrift/kumiko-types/fields").Enti
|
|
|
98
101
|
readonly filterable?: boolean;
|
|
99
102
|
readonly encrypted?: boolean;
|
|
100
103
|
readonly sensitive?: boolean;
|
|
104
|
+
readonly writeOnly?: boolean;
|
|
101
105
|
readonly readAsInstruction?: boolean;
|
|
102
106
|
readonly format?: "email" | "url" | "phone" | "password";
|
|
103
107
|
readonly default?: string;
|
|
@@ -127,6 +131,7 @@ export declare const userEntity: import("@cosmicdrift/kumiko-types/fields").Enti
|
|
|
127
131
|
readonly filterable?: boolean;
|
|
128
132
|
readonly encrypted?: boolean;
|
|
129
133
|
readonly sensitive?: boolean;
|
|
134
|
+
readonly writeOnly?: boolean;
|
|
130
135
|
readonly readAsInstruction?: boolean;
|
|
131
136
|
readonly format?: "email" | "url" | "phone" | "password";
|
|
132
137
|
readonly default?: string;
|
|
@@ -159,7 +164,7 @@ export declare const userEntity: import("@cosmicdrift/kumiko-types/fields").Enti
|
|
|
159
164
|
readonly restricted?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
|
|
160
165
|
} | undefined;
|
|
161
166
|
readonly optionsQuery?: string;
|
|
162
|
-
readonly optionsQueryPayload?:
|
|
167
|
+
readonly optionsQueryPayload?: import("@cosmicdrift/kumiko-types/fields").OptionsQueryPayload;
|
|
163
168
|
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
164
169
|
required: true;
|
|
165
170
|
};
|
|
@@ -188,6 +193,7 @@ export declare const userEntity: import("@cosmicdrift/kumiko-types/fields").Enti
|
|
|
188
193
|
readonly filterable?: boolean;
|
|
189
194
|
readonly encrypted?: boolean;
|
|
190
195
|
readonly sensitive?: boolean;
|
|
196
|
+
readonly writeOnly?: boolean;
|
|
191
197
|
readonly readAsInstruction?: boolean;
|
|
192
198
|
readonly format?: "email" | "url" | "phone" | "password";
|
|
193
199
|
readonly default?: string;
|
|
@@ -210,6 +216,7 @@ export declare const userTable: import("@cosmicdrift/kumiko-framework/db").Entit
|
|
|
210
216
|
readonly filterable?: boolean;
|
|
211
217
|
readonly encrypted?: boolean;
|
|
212
218
|
readonly sensitive?: boolean;
|
|
219
|
+
readonly writeOnly?: boolean;
|
|
213
220
|
readonly readAsInstruction?: boolean;
|
|
214
221
|
readonly format?: "email" | "url" | "phone" | "password";
|
|
215
222
|
readonly default?: string;
|
|
@@ -230,6 +237,7 @@ export declare const userTable: import("@cosmicdrift/kumiko-framework/db").Entit
|
|
|
230
237
|
readonly filterable?: boolean;
|
|
231
238
|
readonly encrypted?: boolean;
|
|
232
239
|
readonly sensitive?: boolean;
|
|
240
|
+
readonly writeOnly?: boolean;
|
|
233
241
|
readonly readAsInstruction?: boolean;
|
|
234
242
|
readonly format?: "email" | "url" | "phone" | "password";
|
|
235
243
|
readonly default?: string;
|
|
@@ -250,6 +258,7 @@ export declare const userTable: import("@cosmicdrift/kumiko-framework/db").Entit
|
|
|
250
258
|
readonly filterable?: boolean;
|
|
251
259
|
readonly encrypted?: boolean;
|
|
252
260
|
readonly sensitive?: boolean;
|
|
261
|
+
readonly writeOnly?: boolean;
|
|
253
262
|
readonly readAsInstruction?: boolean;
|
|
254
263
|
readonly format?: "email" | "url" | "phone" | "password";
|
|
255
264
|
readonly default?: string;
|
|
@@ -270,6 +279,7 @@ export declare const userTable: import("@cosmicdrift/kumiko-framework/db").Entit
|
|
|
270
279
|
readonly filterable?: boolean;
|
|
271
280
|
readonly encrypted?: boolean;
|
|
272
281
|
readonly sensitive?: boolean;
|
|
282
|
+
readonly writeOnly?: boolean;
|
|
273
283
|
readonly readAsInstruction?: boolean;
|
|
274
284
|
readonly format?: "email" | "url" | "phone" | "password";
|
|
275
285
|
readonly default?: string;
|
|
@@ -299,6 +309,7 @@ export declare const userTable: import("@cosmicdrift/kumiko-framework/db").Entit
|
|
|
299
309
|
readonly filterable?: boolean;
|
|
300
310
|
readonly encrypted?: boolean;
|
|
301
311
|
readonly sensitive?: boolean;
|
|
312
|
+
readonly writeOnly?: boolean;
|
|
302
313
|
readonly readAsInstruction?: boolean;
|
|
303
314
|
readonly format?: "email" | "url" | "phone" | "password";
|
|
304
315
|
readonly default?: string;
|
|
@@ -331,7 +342,7 @@ export declare const userTable: import("@cosmicdrift/kumiko-framework/db").Entit
|
|
|
331
342
|
readonly restricted?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
|
|
332
343
|
} | undefined;
|
|
333
344
|
readonly optionsQuery?: string;
|
|
334
|
-
readonly optionsQueryPayload?:
|
|
345
|
+
readonly optionsQueryPayload?: import("@cosmicdrift/kumiko-types/fields").OptionsQueryPayload;
|
|
335
346
|
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
336
347
|
required: true;
|
|
337
348
|
};
|
|
@@ -360,6 +371,7 @@ export declare const userTable: import("@cosmicdrift/kumiko-framework/db").Entit
|
|
|
360
371
|
readonly filterable?: boolean;
|
|
361
372
|
readonly encrypted?: boolean;
|
|
362
373
|
readonly sensitive?: boolean;
|
|
374
|
+
readonly writeOnly?: boolean;
|
|
363
375
|
readonly readAsInstruction?: boolean;
|
|
364
376
|
readonly format?: "email" | "url" | "phone" | "password";
|
|
365
377
|
readonly default?: string;
|
|
@@ -8,5 +8,7 @@ export type ExportJobCleanupCandidate = {
|
|
|
8
8
|
readonly requestedFromTenantId: TenantId;
|
|
9
9
|
readonly downloadStorageKey: string | null;
|
|
10
10
|
readonly expiresAt: Temporal.Instant | null;
|
|
11
|
+
readonly completedAt: Temporal.Instant | null;
|
|
12
|
+
readonly startedAt: Temporal.Instant | null;
|
|
11
13
|
};
|
|
12
14
|
export declare function selectExportJobsForStorageCleanup(db: DbConnection, doneStatus: string, failedStatus: string): Promise<readonly ExportJobCleanupCandidate[]>;
|
|
@@ -25,7 +25,7 @@ import { isMailTransportAvailable, makeDefaultDeletionExecutedEmail, makeDefault
|
|
|
25
25
|
import { makeTenantMailTransportResolver } from "./lib/mail-transport-resolver.js";
|
|
26
26
|
import { resolveAppTenantModel } from "./lib/resolve-tenant-model.js";
|
|
27
27
|
import { makeTenantStorageProviderResolver } from "./lib/storage-provider-resolver.js";
|
|
28
|
-
import { EXPORT_CLEANUP_BACKLOG_AGE_METRIC, runExportJobs, } from "./run-export-jobs.js";
|
|
28
|
+
import { EXPORT_CLEANUP_BACKLOG_AGE_METRIC, EXPORT_CLEANUP_LAST_RUN_TIMESTAMP_METRIC, runExportJobs, } from "./run-export-jobs.js";
|
|
29
29
|
import { runForgetCleanup, sanitizeReasonForLog, } from "./run-forget-cleanup.js";
|
|
30
30
|
import { downloadAttemptEntity } from "./schema/download-attempt.js";
|
|
31
31
|
import { exportDownloadTokenEntity } from "./schema/download-token.js";
|
|
@@ -156,12 +156,18 @@ export function createUserDataRightsFeature(opts = {}) {
|
|
|
156
156
|
"userDataRights.privacyCenter.field.gracePeriodEnd": { en: "Scheduled deletion date" },
|
|
157
157
|
"userDataRights.privacyCenter.export.title": { en: "Export your data (Art. 20)" },
|
|
158
158
|
"userDataRights.privacyCenter.restriction.title": { en: "Restrict processing (Art. 18)" },
|
|
159
|
+
"userDataRights.privacyCenter.restriction.explainer": {
|
|
160
|
+
en: "Freeze your account: processing of your data is paused and you are signed out. Lifting the restriction afterwards is only possible via support.",
|
|
161
|
+
},
|
|
159
162
|
"userDataRights.privacyCenter.restriction.restrict": { en: "Restrict account" },
|
|
160
163
|
"userDataRights.privacyCenter.restriction.dialogTitle": { en: "Restrict your account?" },
|
|
161
164
|
"userDataRights.privacyCenter.restriction.dialogDescription": {
|
|
162
165
|
en: "You will be signed out immediately and cannot sign in again until support lifts the restriction.",
|
|
163
166
|
},
|
|
164
167
|
"userDataRights.privacyCenter.deletion.title": { en: "Delete account (Art. 17)" },
|
|
168
|
+
"userDataRights.privacyCenter.deletion.explainer": {
|
|
169
|
+
en: "Request deletion of your account. Until the grace period ends you can cancel the deletion.",
|
|
170
|
+
},
|
|
165
171
|
"userDataRights.privacyCenter.deletion.delete": { en: "Delete account" },
|
|
166
172
|
"userDataRights.privacyCenter.deletion.cancel": { en: "Cancel deletion" },
|
|
167
173
|
"userDataRights.privacyCenter.deletion.dialogTitle": { en: "Delete your account?" },
|
|
@@ -215,7 +221,7 @@ export function createUserDataRightsFeature(opts = {}) {
|
|
|
215
221
|
// when `showDeletion` is false, and status covers restriction
|
|
216
222
|
// AND deletion state, so it can't hang off the optional section.
|
|
217
223
|
title: i18nKey("userDataRights.privacyCenter.restriction.title"),
|
|
218
|
-
description: "userDataRights.privacyCenter.restriction.explainer",
|
|
224
|
+
description: i18nKey("userDataRights.privacyCenter.restriction.explainer"),
|
|
219
225
|
fields: [
|
|
220
226
|
{
|
|
221
227
|
field: "status",
|
|
@@ -227,7 +233,7 @@ export function createUserDataRightsFeature(opts = {}) {
|
|
|
227
233
|
? [
|
|
228
234
|
{
|
|
229
235
|
title: i18nKey("userDataRights.privacyCenter.deletion.title"),
|
|
230
|
-
description: "userDataRights.privacyCenter.deletion.explainer",
|
|
236
|
+
description: i18nKey("userDataRights.privacyCenter.deletion.explainer"),
|
|
231
237
|
// gracePeriodEnd is only meaningful once a deletion is
|
|
232
238
|
// actually pending — hide it instead of showing an empty
|
|
233
239
|
// date when status isn't deletionRequested.
|
|
@@ -269,6 +275,8 @@ export function createUserDataRightsFeature(opts = {}) {
|
|
|
269
275
|
handler: UserDataRightsHandlers.cancelDeletion,
|
|
270
276
|
visible: { field: "status", eq: "deletionRequested" },
|
|
271
277
|
style: "secondary",
|
|
278
|
+
// Declared: the id's last segment ("deletion") would otherwise resolve to the destructive trash icon.
|
|
279
|
+
icon: "x",
|
|
272
280
|
},
|
|
273
281
|
]
|
|
274
282
|
: []),
|
|
@@ -372,15 +380,20 @@ export function createUserDataRightsFeature(opts = {}) {
|
|
|
372
380
|
// Surfaces a stalled export-cleanup cron: if the daily
|
|
373
381
|
// pass stops running, done-status export bundles keep sitting in
|
|
374
382
|
// storage past their TTL+grace with downloadStorageKey still set
|
|
375
|
-
// (unencrypted PII at rest).
|
|
376
|
-
//
|
|
377
|
-
//
|
|
378
|
-
// Scope: Done-status bundles
|
|
379
|
-
//
|
|
383
|
+
// (unencrypted PII at rest). Gauges keep their last value in-process, so
|
|
384
|
+
// a dead cron does NOT make the backlog gauge absent; the last-run
|
|
385
|
+
// timestamp gauge below is the liveness signal.
|
|
386
|
+
// Scope: Done-status bundles past expiresAt+grace plus Failed-status
|
|
387
|
+
// orphans (no TTL, due from completedAt ?? startedAt; see storageCleanupPass).
|
|
380
388
|
r.metric(EXPORT_CLEANUP_BACKLOG_AGE_METRIC, {
|
|
381
389
|
type: "gauge",
|
|
382
390
|
unit: "seconds",
|
|
383
|
-
description: "Age in seconds of the oldest
|
|
391
|
+
description: "Age in seconds of the oldest export bundle (done: expiresAt+grace passed; failed: orphaned ZIP since completedAt or startedAt) whose downloadStorageKey is still set. 0 when the storage-cleanup pass has no backlog.",
|
|
392
|
+
});
|
|
393
|
+
r.metric(EXPORT_CLEANUP_LAST_RUN_TIMESTAMP_METRIC, {
|
|
394
|
+
type: "gauge",
|
|
395
|
+
unit: "seconds",
|
|
396
|
+
description: "Unix timestamp of the last export storage-cleanup pass that completed. Alert on time() minus this value exceeding the cron interval plus a buffer; the backlog-age gauge alone stays at its last value when the cron stops.",
|
|
384
397
|
});
|
|
385
398
|
// S2.U3 Atom 3b — Worker fuer Async Export-Pipeline. Cron-getriggert.
|
|
386
399
|
const RUN_EXPORT_JOBS_REASON = "processes pending export jobs across every tenant";
|