@cosmicdrift/kumiko-bundled-features 0.338.0 → 0.340.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/auth-email-password/handlers/invite-accept-with-login.write.js +2 -2
- package/dist/auth-email-password/handlers/invite-accept.write.js +2 -4
- package/dist/auth-email-password/handlers/invite-info.query.js +3 -1
- package/dist/auth-email-password/handlers/invite-signup-complete.write.js +2 -4
- package/dist/auth-email-password/handlers/signup-confirm.write.js +1 -1
- package/dist/auth-email-password/handlers/signup-request.write.js +1 -1
- package/dist/auth-email-password/web/auth-gate.d.ts +12 -2
- package/dist/auth-email-password/web/auth-gate.js +25 -1
- package/dist/auth-email-password/web/auth-redirect.d.ts +5 -0
- package/dist/auth-email-password/web/auth-redirect.js +58 -0
- package/dist/auth-email-password/web/client-plugin.d.ts +8 -1
- package/dist/auth-email-password/web/client-plugin.js +2 -0
- package/dist/auth-email-password/web/index.d.ts +2 -1
- package/dist/auth-email-password/web/index.js +1 -0
- package/dist/auth-email-password/web/session.d.ts +3 -1
- package/dist/auth-email-password/web/session.js +8 -2
- package/dist/auth-mfa/feature.js +1 -1
- package/dist/auth-mfa/handlers/enable-confirm-preauth.write.js +1 -1
- package/dist/auth-mfa/handlers/enable-start-preauth.write.js +1 -1
- package/dist/auth-mfa/handlers/verify.write.js +1 -1
- package/dist/auth-mfa/mfa-code-verifier.js +1 -1
- package/dist/auth-mfa/mfa-status-checker.js +1 -1
- package/dist/billing-foundation/constants.d.ts +7 -0
- package/dist/billing-foundation/constants.js +8 -0
- package/dist/billing-foundation/consumer-protection/confirmation-mail.js +3 -3
- package/dist/billing-foundation/consumer-protection/consent-gate.d.ts +3 -3
- package/dist/billing-foundation/consumer-protection/consent-gate.js +10 -2
- package/dist/billing-foundation/consumer-protection/consent-text.js +2 -2
- package/dist/billing-foundation/consumer-protection/register-termination.d.ts +1 -1
- package/dist/billing-foundation/consumer-protection/register-termination.js +31 -3
- package/dist/billing-foundation/consumer-protection/termination-mail.d.ts +3 -1
- package/dist/billing-foundation/consumer-protection/termination-mail.js +1 -1
- package/dist/billing-foundation/consumer-protection/termination-notify.d.ts +18 -0
- package/dist/billing-foundation/consumer-protection/termination-notify.js +21 -0
- package/dist/billing-foundation/consumer-protection/termination-pages.js +31 -2
- package/dist/billing-foundation/consumer-protection/termination-record.d.ts +24 -2
- package/dist/billing-foundation/consumer-protection/termination-record.js +102 -34
- package/dist/billing-foundation/consumer-protection/termination-request.js +10 -42
- package/dist/billing-foundation/consumer-protection/termination-texts.js +20 -20
- package/dist/billing-foundation/events.d.ts +19 -0
- package/dist/billing-foundation/events.js +12 -0
- package/dist/billing-foundation/handlers/create-checkout-session.write.js +2 -2
- package/dist/billing-foundation/handlers/start-plan-checkout.write.js +4 -2
- package/dist/billing-foundation/i18n.js +3 -0
- package/dist/billing-foundation/projection.js +23 -40
- package/dist/billing-foundation/tenant-destroy-hook.js +4 -4
- package/dist/billing-foundation/types.d.ts +6 -1
- package/dist/billing-foundation/validate-options.js +4 -0
- package/dist/channel-discord/discord-channel.d.ts +6 -0
- package/dist/channel-discord/discord-channel.js +19 -0
- package/dist/channel-discord/feature.d.ts +3 -0
- package/dist/channel-discord/feature.js +30 -0
- package/dist/channel-discord/index.d.ts +2 -0
- package/dist/channel-discord/index.js +2 -0
- package/dist/channel-slack/feature.d.ts +3 -0
- package/dist/channel-slack/feature.js +30 -0
- package/dist/channel-slack/index.d.ts +2 -0
- package/dist/channel-slack/index.js +2 -0
- package/dist/channel-slack/slack-channel.d.ts +5 -0
- package/dist/channel-slack/slack-channel.js +16 -0
- package/dist/channel-teams/feature.d.ts +3 -0
- package/dist/channel-teams/feature.js +34 -0
- package/dist/channel-teams/index.d.ts +2 -0
- package/dist/channel-teams/index.js +2 -0
- package/dist/channel-teams/teams-channel.d.ts +5 -0
- package/dist/channel-teams/teams-channel.js +38 -0
- package/dist/channel-telegram/feature.d.ts +3 -0
- package/dist/channel-telegram/feature.js +28 -0
- package/dist/channel-telegram/index.d.ts +2 -0
- package/dist/channel-telegram/index.js +2 -0
- package/dist/channel-telegram/telegram-channel.d.ts +8 -0
- package/dist/channel-telegram/telegram-channel.js +54 -0
- package/dist/crypto-shredding/handlers/forget-subject.write.js +6 -6
- package/dist/custom-fields/handlers/clear-custom-field.write.js +1 -1
- package/dist/custom-fields/handlers/define-tenant-field.write.js +1 -1
- package/dist/custom-fields/handlers/set-custom-field.write.js +1 -1
- package/dist/custom-fields/wire-user-data-rights.js +1 -1
- package/dist/data-retention/feature.js +1 -1
- package/dist/delivery/channel-context.d.ts +2 -1
- package/dist/delivery/channel-context.js +2 -2
- package/dist/delivery/chat-webhook-channel.d.ts +19 -0
- package/dist/delivery/chat-webhook-channel.js +48 -0
- package/dist/delivery/chat-webhook-sender.d.ts +21 -0
- package/dist/delivery/chat-webhook-sender.js +102 -0
- package/dist/delivery/delivery-service.js +3 -0
- package/dist/delivery/index.d.ts +2 -0
- package/dist/delivery/index.js +2 -0
- package/dist/delivery/jobs.js +1 -1
- package/dist/delivery/testing.d.ts +2 -0
- package/dist/delivery/types.d.ts +3 -1
- package/dist/delivery/types.js +1 -2
- package/dist/file-derivatives/handlers/public-variant-by-file-ref.query.js +1 -1
- package/dist/files-tenant-data/index.js +1 -1
- package/dist/form-draft/feature.js +1 -1
- package/dist/form-draft/handlers/discard.write.js +1 -1
- package/dist/form-draft/handlers/save.write.js +1 -1
- package/dist/inbound-mail-foundation/connect-routes.d.ts +7 -0
- package/dist/inbound-mail-foundation/connect-routes.js +13 -8
- package/dist/inbound-mail-foundation/feature.js +1 -1
- package/dist/inbound-mail-foundation/handlers/ingest-message.write.js +1 -1
- package/dist/inbound-mail-foundation/index.d.ts +1 -0
- package/dist/inbound-mail-foundation/index.js +1 -0
- package/dist/inbound-mail-foundation/oauth-access-token.d.ts +28 -0
- package/dist/inbound-mail-foundation/oauth-access-token.js +131 -0
- package/dist/inbound-mail-foundation/projection.js +37 -45
- package/dist/inbound-mail-foundation/tenant-destroy-hook.js +1 -1
- package/dist/inbound-mail-foundation/types.d.ts +18 -1
- package/dist/inbound-mail-foundation/watch-supervisor.d.ts +5 -0
- package/dist/inbound-mail-foundation/watch-supervisor.js +42 -14
- package/dist/managed-pages/handlers/set.write.js +1 -1
- package/dist/notes-history/handlers/add-note.write.js +1 -1
- package/dist/renderer-simple/feature.d.ts +8 -2
- package/dist/renderer-simple/feature.js +8 -5
- package/dist/renderer-simple/index.d.ts +2 -2
- package/dist/renderer-simple/index.js +1 -1
- package/dist/renderer-simple/simple-renderer.d.ts +15 -0
- package/dist/renderer-simple/simple-renderer.js +74 -27
- package/dist/secrets/feature.js +1 -1
- package/dist/sessions/feature.js +1 -1
- package/dist/sessions/handlers/revoke-all-for-user.write.js +1 -1
- package/dist/tenant-handover/handlers/claim.write.js +1 -2
- package/dist/tenant-lifecycle/feature.js +1 -1
- package/dist/tenant-lifecycle/handlers/cancel-destruction.write.js +3 -1
- package/dist/tenant-lifecycle/handlers/request-destruction.write.js +1 -1
- package/dist/tier-engine/feature.js +1 -1
- package/dist/tier-engine/handlers/get-tenant-tier.query.js +1 -1
- package/dist/tier-engine/handlers/set-tenant-tier.write.js +1 -1
- package/dist/user/handlers/update-roles.js +7 -18
- package/dist/user-data-rights/feature.js +2 -2
- package/dist/user-data-rights/handlers/cancel-deletion.write.js +1 -1
- package/dist/user-data-rights/handlers/confirm-deletion-by-token.write.js +1 -1
- package/dist/user-data-rights/handlers/download-by-job.query.js +1 -1
- package/dist/user-data-rights/handlers/download-by-token.query.js +1 -1
- package/dist/user-data-rights/handlers/export-status.query.js +1 -1
- package/dist/user-data-rights/handlers/lift-restriction.write.js +2 -4
- package/dist/user-data-rights/handlers/my-audit-log.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 +1 -1
- package/dist/user-data-rights/handlers/request-export.write.js +1 -1
- package/dist/user-data-rights/handlers/restrict-account.write.js +2 -2
- package/dist/user-data-rights/handlers/run-forget-cleanup.write.js +1 -1
- package/dist/user-data-rights/lib/update-user-lifecycle.js +1 -1
- package/dist/workflow-runner/db/queries/due-runs.d.ts +4 -5
- package/dist/workflow-runner/db/queries/due-runs.js +5 -4
- package/dist/workflow-runner/event-subscriber.js +6 -2
- package/dist/workflow-runner/event-trigger.js +5 -0
- package/dist/workflow-runner/feature.js +4 -6
- package/dist/workflow-runner/handlers/resume-run.write.js +147 -59
- package/dist/workflow-runner/pending-projection.js +1 -0
- package/dist/workflow-runner/runner.d.ts +7 -1
- package/dist/workflow-runner/runner.js +1 -1
- package/dist/workflow-runner/tables.js +8 -1
- package/package.json +41 -9
- package/src/auth-email-password/changes.json +7 -0
- package/src/billing-foundation/changes.json +41 -0
- package/src/delivery/changes.json +7 -0
- package/src/inbound-mail-foundation/changes.json +14 -0
- package/src/renderer-simple/changes.json +9 -1
- package/src/user/changes.json +13 -6
- package/src/workflow-runner/changes.json +14 -0
- package/dist/billing-foundation/db/queries/payment-projection.d.ts +0 -2
- package/dist/billing-foundation/db/queries/payment-projection.js +0 -13
- package/dist/billing-foundation/db/queries/subscription-projection.d.ts +0 -2
- package/dist/billing-foundation/db/queries/subscription-projection.js +0 -7
- package/dist/inbound-mail-foundation/db/queries/inbound-projections.d.ts +0 -7
- package/dist/inbound-mail-foundation/db/queries/inbound-projections.js +0 -24
|
@@ -20,7 +20,9 @@ export const cancelDestructionWrite = defineWriteHandler({
|
|
|
20
20
|
},
|
|
21
21
|
handler: async (event, ctx) => {
|
|
22
22
|
const tenantId = event.user.tenantId;
|
|
23
|
-
const row = await fetchOne(ctx.db.unsafeRaw(
|
|
23
|
+
const row = await fetchOne(ctx.db.unsafeRaw(), tenantTable, {
|
|
24
|
+
id: tenantId,
|
|
25
|
+
});
|
|
24
26
|
if (!row) {
|
|
25
27
|
return writeFailure(new UnprocessableError("tenant_not_found", { details: { tenantId } }));
|
|
26
28
|
}
|
|
@@ -24,7 +24,7 @@ export const requestDestructionWrite = defineWriteHandler({
|
|
|
24
24
|
},
|
|
25
25
|
handler: async (event, ctx) => {
|
|
26
26
|
const tenantId = event.user.tenantId;
|
|
27
|
-
const runner = ctx.db.unsafeRaw(
|
|
27
|
+
const runner = ctx.db.unsafeRaw();
|
|
28
28
|
const row = await fetchOne(runner, tenantTable, { id: tenantId });
|
|
29
29
|
if (!row) {
|
|
30
30
|
return writeFailure(new UnprocessableError("tenant_not_found", { details: { tenantId } }));
|
|
@@ -303,7 +303,7 @@ export function createTierEngineFeature(opts = {}) {
|
|
|
303
303
|
const rawDb = ctx.systemDb
|
|
304
304
|
? ctx.systemDb.unsafeRaw(autoDefaultTierHookReason)
|
|
305
305
|
: ctx.db && "unsafeRaw" in ctx.db
|
|
306
|
-
? ctx.db.unsafeRaw(
|
|
306
|
+
? ctx.db.unsafeRaw()
|
|
307
307
|
: undefined;
|
|
308
308
|
// skip: defensive — inTransaction phase always sets db, but AppContext's
|
|
309
309
|
// type makes it optional. Throwing would be overreach (lifecycle
|
|
@@ -20,7 +20,7 @@ export const getTenantTierQuery = defineQueryHandler({
|
|
|
20
20
|
},
|
|
21
21
|
handler: async (query, ctx) => {
|
|
22
22
|
const tenantId = query.payload.tenantId; // @cast-boundary engine-bridge
|
|
23
|
-
const tdb = createTenantDb(ctx.db.unsafeRaw(
|
|
23
|
+
const tdb = createTenantDb(ctx.db.unsafeRaw(), tenantId, "system");
|
|
24
24
|
const row = await fetchOne(tdb, tierAssignmentTable, { tenantId });
|
|
25
25
|
return row ?? null;
|
|
26
26
|
},
|
|
@@ -51,7 +51,7 @@ export function createSetTenantTierWrite(opts = {}) {
|
|
|
51
51
|
},
|
|
52
52
|
handler: async (event, ctx) => {
|
|
53
53
|
const tenantId = event.payload.tenantId; // @cast-boundary engine-bridge
|
|
54
|
-
const rawDb = ctx.db.unsafeRaw(
|
|
54
|
+
const rawDb = ctx.db.unsafeRaw();
|
|
55
55
|
const tdb = createTenantDb(rawDb, tenantId, "system");
|
|
56
56
|
const systemUser = { ...event.user, tenantId };
|
|
57
57
|
const tier = event.payload.tier;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { fetchOne, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
2
2
|
import { acquireNamespacedAdvisoryLock, createEventStoreExecutor, } from "@cosmicdrift/kumiko-framework/db";
|
|
3
3
|
import { createSystemUser, findForbiddenRoleAssignment, SYSTEM_TENANT_ID, } from "@cosmicdrift/kumiko-framework/engine";
|
|
4
4
|
import { AccessDeniedError, ConflictError, InternalError, NotFoundError, writeFailure, } from "@cosmicdrift/kumiko-framework/errors";
|
|
@@ -10,9 +10,6 @@ const REVOKE_ALL_SESSIONS_QN = "sessions:write:user-session:revoke-all-for-user"
|
|
|
10
10
|
// Serializes last-SystemAdmin demotion checks globally inside the write TX
|
|
11
11
|
// (dispatcher batch wraps handlers in transaction — xact lock holds through update).
|
|
12
12
|
const LAST_SYSTEM_ADMIN_LOCK_NAMESPACE = 0x7361646d; // 'sadm'
|
|
13
|
-
function quoteIdent(name) {
|
|
14
|
-
return `"${name.replace(/"/g, '""')}"`;
|
|
15
|
-
}
|
|
16
13
|
function isActiveUserRow(row) {
|
|
17
14
|
if (row.isDeleted === true)
|
|
18
15
|
return false;
|
|
@@ -22,19 +19,11 @@ function isActiveSystemAdminRow(row) {
|
|
|
22
19
|
return isActiveUserRow(row) && parseRoles(row.roles).includes("SystemAdmin");
|
|
23
20
|
}
|
|
24
21
|
async function countOtherActiveSystemAdmins(db, excludeUserId) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const rows = (await asRawClient(db).unsafe(`SELECT id, roles, status, is_deleted AS "isDeleted"
|
|
31
|
-
FROM ${quoteIdent(tableName)}
|
|
32
|
-
WHERE is_deleted = false
|
|
33
|
-
AND (status = $1 OR status IS NULL)
|
|
34
|
-
AND roles @> $2::jsonb`,
|
|
35
|
-
// Pass a JS array — JSON.stringify + $n::jsonb double-encodes to a jsonb string
|
|
36
|
-
// (postgres.js), so `@>` never matches a roles array.
|
|
37
|
-
[USER_STATUS.Active, ["SystemAdmin"]]));
|
|
22
|
+
// `roles` is a jsonb multiSelect column: a scalar filter value means `@>` containment.
|
|
23
|
+
const rows = await selectMany(db, userTable, {
|
|
24
|
+
isDeleted: false,
|
|
25
|
+
roles: "SystemAdmin",
|
|
26
|
+
});
|
|
38
27
|
return rows.filter((u) => u.id !== excludeUserId && isActiveSystemAdminRow(u)).length;
|
|
39
28
|
}
|
|
40
29
|
export async function applyUserRolesUpdate(event, ctx, db) {
|
|
@@ -68,7 +57,7 @@ export async function applyUserRolesUpdate(event, ctx, db) {
|
|
|
68
57
|
// Lock before count+update so two concurrent demotions cannot both
|
|
69
58
|
// observe otherActiveSystemAdmins >= 1 and leave zero active SystemAdmins.
|
|
70
59
|
await acquireNamespacedAdvisoryLock(lockRunner, LAST_SYSTEM_ADMIN_LOCK_NAMESPACE, "global");
|
|
71
|
-
const otherActiveSystemAdmins = await countOtherActiveSystemAdmins(ctx.systemDb.unsafeRaw("
|
|
60
|
+
const otherActiveSystemAdmins = await countOtherActiveSystemAdmins(ctx.systemDb.unsafeRaw("count remaining active SystemAdmins across the global users table"), event.payload.id);
|
|
72
61
|
if (otherActiveSystemAdmins === 0) {
|
|
73
62
|
return writeFailure(new ConflictError({
|
|
74
63
|
message: "cannot demote the last active SystemAdmin",
|
|
@@ -411,7 +411,7 @@ export function createUserDataRightsFeature(opts = {}) {
|
|
|
411
411
|
// Discriminator wird via handlerName="user-data-rights:run-export-
|
|
412
412
|
// jobs" im Secret-Read-Audit erfasst.
|
|
413
413
|
const exportUserId = ctx._userId ?? SYSTEM_USER_ID;
|
|
414
|
-
const exportDb = ctx.db.unsafeRaw(
|
|
414
|
+
const exportDb = ctx.db.unsafeRaw(); // @cast-boundary db-operator — jobs never run inside a DbTx
|
|
415
415
|
const exportRegistry = ctx.registry;
|
|
416
416
|
// JobContext carries ctx.meter (raw), not ctx.metrics — that bound
|
|
417
417
|
// handle only exists on HandlerContext (built per write/query call
|
|
@@ -496,7 +496,7 @@ export function createUserDataRightsFeature(opts = {}) {
|
|
|
496
496
|
}
|
|
497
497
|
const T = (await import("@cosmicdrift/kumiko-framework/time")).getTemporal();
|
|
498
498
|
const forgetUserId = ctx._userId ?? SYSTEM_USER_ID;
|
|
499
|
-
const forgetDb = ctx.db.unsafeRaw(
|
|
499
|
+
const forgetDb = ctx.db.unsafeRaw(); // @cast-boundary db-operator — jobs never run inside a DbTx
|
|
500
500
|
const forgetRegistry = ctx.registry;
|
|
501
501
|
const tenantModel = await resolveAppTenantModel({
|
|
502
502
|
registry: forgetRegistry,
|
|
@@ -45,7 +45,7 @@ export const cancelDeletionWrite = defineWriteHandler({
|
|
|
45
45
|
if (!isWithinGracePeriod(row.gracePeriodEnd)) {
|
|
46
46
|
return writeFailure(new UnprocessableError("grace_period_expired"));
|
|
47
47
|
}
|
|
48
|
-
await updateUserLifecycle(ctx.db.unsafeRaw(
|
|
48
|
+
await updateUserLifecycle(ctx.db.unsafeRaw(), event.user.id, {
|
|
49
49
|
status: USER_STATUS.Active,
|
|
50
50
|
gracePeriodEnd: null,
|
|
51
51
|
// #354/1: closes the replay-after-cancel window — a still-TTL-valid email
|
|
@@ -68,7 +68,7 @@ export function createConfirmDeletionByTokenHandler(opts = {}) {
|
|
|
68
68
|
secret: opts.deletionTokenSecret,
|
|
69
69
|
loadPendingRequestId: (userId) => readPendingDeletionRequestId(ctx, userId),
|
|
70
70
|
commitDeletion: async (userId, requestId) => {
|
|
71
|
-
const res = await startDeletionGracePeriod(ctx, userId, () => resolveGracePeriod(ctx, event.user.tenantId), ctx.db.unsafeRaw(
|
|
71
|
+
const res = await startDeletionGracePeriod(ctx, userId, () => resolveGracePeriod(ctx, event.user.tenantId), ctx.db.unsafeRaw(), { pendingDeletionRequestId: requestId });
|
|
72
72
|
if (!res.ok)
|
|
73
73
|
return false;
|
|
74
74
|
gracePeriodEndIso = res.gracePeriodEnd.toString();
|
|
@@ -61,7 +61,7 @@ export const downloadByJobQuery = defineQueryHandler({
|
|
|
61
61
|
// Wert (603/2).
|
|
62
62
|
const auditIp = requestContext.get()?.ip ?? null;
|
|
63
63
|
const auditUa = requestContext.get()?.userAgent ?? null;
|
|
64
|
-
const runner = ctx.db.unsafeRaw(
|
|
64
|
+
const runner = ctx.db.unsafeRaw();
|
|
65
65
|
// Step 1-2: job-lookup + cross-user-isolation
|
|
66
66
|
const jobRow = await fetchOne(runner, exportJobsTable, { id: jobId });
|
|
67
67
|
if (!jobRow || jobRow.userId !== userId) {
|
|
@@ -64,7 +64,7 @@ export const downloadByTokenQuery = defineQueryHandler({
|
|
|
64
64
|
handler: async (query, ctx) => {
|
|
65
65
|
const T = getTemporal();
|
|
66
66
|
const now = T.Now.instant();
|
|
67
|
-
const runner = ctx.db.unsafeRaw(
|
|
67
|
+
const runner = ctx.db.unsafeRaw();
|
|
68
68
|
// Step 1: hash + lookup
|
|
69
69
|
const hash = await hashDownloadToken(query.payload.token);
|
|
70
70
|
const tokenRow = await fetchOne(runner, exportDownloadTokensTable, {
|
|
@@ -30,7 +30,7 @@ export const exportStatusQuery = defineQueryHandler({
|
|
|
30
30
|
reason: EXPORT_STATUS_REASON,
|
|
31
31
|
},
|
|
32
32
|
handler: async (query, ctx) => {
|
|
33
|
-
const rows = await selectMany(ctx.db.unsafeRaw(
|
|
33
|
+
const rows = await selectMany(ctx.db.unsafeRaw(), exportJobsTable, { userId: query.user.id }, { limit: 1, orderBy: { col: "requestedAt", direction: "desc" } });
|
|
34
34
|
const latest = rows[0];
|
|
35
35
|
if (!latest)
|
|
36
36
|
return { hasJob: false };
|
|
@@ -5,8 +5,6 @@ import { USER_STATUS, userTable } from "../../user/index.js";
|
|
|
5
5
|
import { denyIfTargetOutsideAdminTenant } from "../lib/deny-if-target-outside-admin-tenant.js";
|
|
6
6
|
import { updateUserLifecycle } from "../lib/update-user-lifecycle.js";
|
|
7
7
|
const LIFT_RESTRICTION_ESCAPE_HATCH_REASON = "checks the target user's membership in the admin's tenant and appends the user lifecycle status change on the SYSTEM_TENANT_ID user stream, both via DbRunner helpers outside the admin's own tenant scope.";
|
|
8
|
-
const CHECK_TARGET_MEMBERSHIP_REASON = "checks the target user's membership in the admin's tenant via the DbRunner helper";
|
|
9
|
-
const APPEND_LIFECYCLE_EVENT_REASON = "appends the user lifecycle event on the SYSTEM_TENANT_ID user stream";
|
|
10
8
|
// POST /api/user/lift-restriction (S2.U6) — DSGVO Art. 18 Reverse.
|
|
11
9
|
//
|
|
12
10
|
// Operator-only. A Restricted user's own session is unconditionally
|
|
@@ -38,7 +36,7 @@ export const liftRestrictionWrite = defineWriteHandler({
|
|
|
38
36
|
},
|
|
39
37
|
handler: async (event, ctx) => {
|
|
40
38
|
const targetUserId = event.payload.userId;
|
|
41
|
-
const outside = await denyIfTargetOutsideAdminTenant(ctx.db.unsafeRaw(
|
|
39
|
+
const outside = await denyIfTargetOutsideAdminTenant(ctx.db.unsafeRaw(), event.user, targetUserId);
|
|
42
40
|
if (outside)
|
|
43
41
|
return outside;
|
|
44
42
|
const userRow = await ctx.db.global(userTable).fetchOne({
|
|
@@ -55,7 +53,7 @@ export const liftRestrictionWrite = defineWriteHandler({
|
|
|
55
53
|
details: { currentStatus },
|
|
56
54
|
}));
|
|
57
55
|
}
|
|
58
|
-
await updateUserLifecycle(ctx.db.unsafeRaw(
|
|
56
|
+
await updateUserLifecycle(ctx.db.unsafeRaw(), targetUserId, {
|
|
59
57
|
status: USER_STATUS.Active,
|
|
60
58
|
});
|
|
61
59
|
return {
|
|
@@ -53,7 +53,7 @@ export const myAuditLogQuery = defineQueryHandler({
|
|
|
53
53
|
}
|
|
54
54
|
if (p.before)
|
|
55
55
|
where["id"] = { lt: BigInt(p.before) };
|
|
56
|
-
const rows = await selectMany(ctx.db.unsafeRaw(
|
|
56
|
+
const rows = await selectMany(ctx.db.unsafeRaw(), eventsTable, where, {
|
|
57
57
|
orderBy: { col: "id", direction: "desc" },
|
|
58
58
|
limit: p.limit,
|
|
59
59
|
});
|
|
@@ -57,7 +57,7 @@ export function createRequestDeletionByEmailHandler(opts = {}) {
|
|
|
57
57
|
// user-Row landet und in die Token-HMAC-Purpose gefaltet wird. cancel
|
|
58
58
|
// nullt sie → ein nach Cancel nachgespieltes Token verifiziert nicht mehr.
|
|
59
59
|
const requestId = crypto.randomUUID();
|
|
60
|
-
await updateUserLifecycle(ctx.db.unsafeRaw(
|
|
60
|
+
await updateUserLifecycle(ctx.db.unsafeRaw(), userRow["id"], {
|
|
61
61
|
pendingDeletionRequestId: requestId,
|
|
62
62
|
});
|
|
63
63
|
const { token, expiresAt } = signDeletionToken(userRow["id"], requestId, DELETION_VERIFY_TTL_MINUTES, opts.deletionTokenSecret);
|
|
@@ -29,7 +29,7 @@ export function createRequestDeletionHandler(opts = {}) {
|
|
|
29
29
|
description: "Starts the GDPR Art. 17 deletion of the calling user's own account by arming the grace period from the tenant compliance profile and mailing a confirmation, after which only cancel-deletion can stop the erasure.",
|
|
30
30
|
agent: { risk: "high" },
|
|
31
31
|
handler: async (event, ctx) => {
|
|
32
|
-
const res = await startDeletionGracePeriod(ctx, event.user.id, () => resolveGracePeriod(ctx, event.user.tenantId), ctx.db.unsafeRaw(
|
|
32
|
+
const res = await startDeletionGracePeriod(ctx, event.user.id, () => resolveGracePeriod(ctx, event.user.tenantId), ctx.db.unsafeRaw());
|
|
33
33
|
if (!res.ok)
|
|
34
34
|
return writeFailure(res.error);
|
|
35
35
|
const { gracePeriodEnd, userEmail, userLocale } = res;
|
|
@@ -58,7 +58,7 @@ export const requestExportWrite = defineWriteHandler({
|
|
|
58
58
|
const userId = event.user.id;
|
|
59
59
|
const T = getTemporal();
|
|
60
60
|
const now = T.Now.instant();
|
|
61
|
-
const exportJobRunner = ctx.db.unsafeRaw(
|
|
61
|
+
const exportJobRunner = ctx.db.unsafeRaw();
|
|
62
62
|
// Pre-Check: exportJobRunner bypasses the TenantDb-Filter — the
|
|
63
63
|
// TenantDb-Wrapper would hide cross-tenant jobs.
|
|
64
64
|
const existing = await findActiveJob(exportJobRunner, userId);
|
|
@@ -59,7 +59,7 @@ export const restrictAccountWrite = defineWriteHandler({
|
|
|
59
59
|
details: { reason: "admin_required_for_other_user" },
|
|
60
60
|
}));
|
|
61
61
|
}
|
|
62
|
-
const outside = await denyIfTargetOutsideAdminTenant(ctx.db.unsafeRaw(
|
|
62
|
+
const outside = await denyIfTargetOutsideAdminTenant(ctx.db.unsafeRaw(), event.user, targetUserId);
|
|
63
63
|
if (outside)
|
|
64
64
|
return outside;
|
|
65
65
|
}
|
|
@@ -82,7 +82,7 @@ export const restrictAccountWrite = defineWriteHandler({
|
|
|
82
82
|
details: { currentStatus },
|
|
83
83
|
}));
|
|
84
84
|
}
|
|
85
|
-
await updateUserLifecycle(ctx.db.unsafeRaw(
|
|
85
|
+
await updateUserLifecycle(ctx.db.unsafeRaw(), targetUserId, { status: USER_STATUS.Restricted });
|
|
86
86
|
// Cross-Feature: alle live sessions revoken — sonst koennte der User
|
|
87
87
|
// mit existierendem JWT bis zur Token-Expiry weiter schreiben.
|
|
88
88
|
// ctx.writeAs(systemUser, ...) damit der privileged-Handler die
|
|
@@ -36,7 +36,7 @@ export function createRunForgetCleanupHandler(opts = {}) {
|
|
|
36
36
|
}));
|
|
37
37
|
}
|
|
38
38
|
const T = getTemporal();
|
|
39
|
-
const runner = ctx.db.unsafeRaw(
|
|
39
|
+
const runner = ctx.db.unsafeRaw();
|
|
40
40
|
// Operator-triggered forget must also erase binaries, not just rows —
|
|
41
41
|
// it flips users to Deleted, after which the cron never re-processes
|
|
42
42
|
// them, so a row-only delete here would permanently leak the binaries.
|
|
@@ -18,7 +18,7 @@ import { USER_STATUS, userEntity, userTable } from "../../user/index.js";
|
|
|
18
18
|
// NICHT `event.user.tenantId` (das ist der aktive Tenant zur Lifecycle-Zeit und
|
|
19
19
|
// kann abweichen).
|
|
20
20
|
const userExecutor = createEventStoreExecutor(userTable, userEntity, { entityName: "user" });
|
|
21
|
-
// `conn` is ctx.db.unsafeRaw(
|
|
21
|
+
// `conn` is ctx.db.unsafeRaw() (regular handlers) or the open tx
|
|
22
22
|
// (forget-cleanup sub-tx) — keeps the event append atomic with the write.
|
|
23
23
|
//
|
|
24
24
|
// Returns `{ applied: false }` instead of throwing only when `options.expect`
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
1
|
+
import { type TenantDb } from "@cosmicdrift/kumiko-framework/db";
|
|
3
2
|
export type DueRow = {
|
|
4
|
-
readonly
|
|
5
|
-
readonly
|
|
3
|
+
readonly runId: string;
|
|
4
|
+
readonly stepIndex: number;
|
|
6
5
|
};
|
|
7
6
|
export declare const DUE_RUNS_BATCH_SIZE = 100;
|
|
8
|
-
export declare function selectDueWorkflowRunPending(db:
|
|
7
|
+
export declare function selectDueWorkflowRunPending(db: TenantDb): Promise<readonly DueRow[]>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { sql } from "@cosmicdrift/kumiko-framework/db";
|
|
2
|
+
import { workflowRunPendingTable } from "../../tables.js";
|
|
2
3
|
// Oldest first and capped, so a backlog after an outage drains over several
|
|
3
4
|
// ticks instead of holding one tick's `concurrency: "skip"` slot for minutes.
|
|
4
5
|
export const DUE_RUNS_BATCH_SIZE = 100;
|
|
@@ -6,7 +7,7 @@ export const DUE_RUNS_BATCH_SIZE = 100;
|
|
|
6
7
|
// (ctx.tryAppendEvent on WORKFLOW_RESUMED) — a second dispatch for the same
|
|
7
8
|
// row loses that race and no-ops. No row locks: in an autocommit statement
|
|
8
9
|
// they would be released right after the SELECT and guard nothing.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
return
|
|
10
|
+
// `now()` stays the DB clock so job-host clock skew cannot mark rows due early.
|
|
11
|
+
export function selectDueWorkflowRunPending(db) {
|
|
12
|
+
return db.selectMany(workflowRunPendingTable, { wakeAt: { lt: sql `now()` } }, { orderBy: { col: "wakeAt", direction: "asc" }, limit: DUE_RUNS_BATCH_SIZE });
|
|
12
13
|
}
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
// This subscriber never resumes a run itself (D3, workflow-resume-loop.md):
|
|
21
21
|
// the MultiStreamApplyContext it runs in has no callFeature/runStepList
|
|
22
22
|
// access, only unsafeAppendEvent/loadAggregate. It writes triggerEventType
|
|
23
|
-
// +
|
|
23
|
+
// + triggerEventRef + wakeAt=now() onto the matching pending row(s); the
|
|
24
24
|
// existing resume-due-runs job (Phase 2) picks the row up on its next
|
|
25
25
|
// cron tick — the one resume path stays exactly the one Phase 2 built.
|
|
26
26
|
import { selectMany, updateMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
@@ -66,7 +66,11 @@ const wakeupApply = async (event, tx) => {
|
|
|
66
66
|
continue;
|
|
67
67
|
await updateMany(tx, workflowRunPendingTable, {
|
|
68
68
|
triggerEventType: event.type,
|
|
69
|
-
|
|
69
|
+
triggerEventRef: {
|
|
70
|
+
eventId: event.id,
|
|
71
|
+
aggregateId: event.aggregateId,
|
|
72
|
+
version: event.version,
|
|
73
|
+
},
|
|
70
74
|
wakeAt: getTemporal().Now.instant().toString(),
|
|
71
75
|
}, { tenantId: row.tenantId, runId: row.runId, stepIndex: row.stepIndex });
|
|
72
76
|
}
|
|
@@ -69,6 +69,11 @@ export function registerEventTrigger(r, workflow) {
|
|
|
69
69
|
runId,
|
|
70
70
|
workflow,
|
|
71
71
|
triggerEvent,
|
|
72
|
+
triggerEventRef: {
|
|
73
|
+
eventId: event.id,
|
|
74
|
+
aggregateId: event.aggregateId,
|
|
75
|
+
version: event.version,
|
|
76
|
+
},
|
|
72
77
|
...(idempotencyKey && { idempotencyKey }),
|
|
73
78
|
// @cast-boundary msp-to-handler-ctx — MultiStreamApplyContext only
|
|
74
79
|
// exposes unsafeAppendEvent/loadAggregate, a subset of
|
|
@@ -57,21 +57,19 @@ export const workflowRunnerFeature = defineFeature(FEATURE_NAME, (r) => {
|
|
|
57
57
|
// skip: cron fired without a perTenant fan-out tenant — nothing scoped
|
|
58
58
|
return;
|
|
59
59
|
}
|
|
60
|
-
ctx.systemDb.assertTenantMatch(tenantId);
|
|
61
|
-
const dueRows = await selectDueWorkflowRunPending(ctx.systemDb.unsafeRaw("due-row pickup needs FOR UPDATE SKIP LOCKED raw SQL; the query filters by this job's tenantId"), // @cast-boundary db-operator — DbRunner narrows to DbConnection, jobs never run inside a DbTx
|
|
62
|
-
tenantId);
|
|
60
|
+
const dueRows = await selectDueWorkflowRunPending(ctx.systemDb.assertTenantMatch(tenantId));
|
|
63
61
|
for (const row of dueRows) {
|
|
64
62
|
try {
|
|
65
63
|
await ctx.write("workflow-runner:write:resume-run", {
|
|
66
|
-
runId: row.
|
|
67
|
-
stepIndex: row.
|
|
64
|
+
runId: row.runId,
|
|
65
|
+
stepIndex: row.stepIndex,
|
|
68
66
|
});
|
|
69
67
|
}
|
|
70
68
|
catch (error) {
|
|
71
69
|
// skip: one row's dispatch failing (infra hiccup, not a business
|
|
72
70
|
// outcome — resume-run itself never throws for expected cases)
|
|
73
71
|
// must not stop the rest of this tenant's due rows from resuming.
|
|
74
|
-
ctx.log.warn(`[workflow-runner:resume-due-runs] runId=${row.
|
|
72
|
+
ctx.log.warn(`[workflow-runner:resume-due-runs] runId=${row.runId} stepIndex=${row.stepIndex} dispatch failed: ${String(error)}`);
|
|
75
73
|
}
|
|
76
74
|
}
|
|
77
75
|
});
|