@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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { UnprocessableError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
2
2
|
import * as z from "zod";
|
|
3
|
+
import { hostRowIsGone } from "../../shared/index.js";
|
|
3
4
|
import { DEFAULT_FOLDER_ACCESS } from "../constants.js";
|
|
4
5
|
import { folderAssignmentExecutor, folderExecutor } from "../executor.js";
|
|
5
6
|
const deleteFolderPayloadSchema = z.object({ id: z.uuid() });
|
|
@@ -10,18 +11,44 @@ const deleteFolderPayloadSchema = z.object({ id: z.uuid() });
|
|
|
10
11
|
// see 658/1). Block server-side instead: this is the same "referential
|
|
11
12
|
// integrity has no FK here" reasoning set-folder.write.ts already applies to
|
|
12
13
|
// folderId on assignment-creation.
|
|
14
|
+
//
|
|
15
|
+
// Assignments whose host is gone (hard-deleted, soft-deleted or unregistered)
|
|
16
|
+
// are cleaned up here instead of blocking forever: clear-folder can't reach
|
|
17
|
+
// them (it requires a visible host). A host-delete hook would break restore,
|
|
18
|
+
// so the cleanup runs lazily at folder:delete.
|
|
19
|
+
const ASSIGNMENT_PAGE_SIZE = 200;
|
|
13
20
|
export function createDeleteFolderHandler(access = DEFAULT_FOLDER_ACCESS) {
|
|
14
21
|
return {
|
|
15
22
|
name: "folder:delete",
|
|
16
23
|
schema: deleteFolderPayloadSchema,
|
|
17
24
|
access,
|
|
18
|
-
description: "Deletes a folder from the tenant catalog and refuses while any entity is still filed in it; use it once that folder's contents have been unfiled or moved elsewhere.",
|
|
25
|
+
description: "Deletes a folder from the tenant catalog and refuses while any live entity is still filed in it (assignments of deleted hosts are cleaned up); use it once that folder's contents have been unfiled or moved elsewhere.",
|
|
19
26
|
agent: { risk: "high" },
|
|
20
27
|
handler: async (event, ctx) => {
|
|
21
28
|
const payload = event.payload; // @cast-boundary engine-payload
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
const orphanAssignmentIds = [];
|
|
30
|
+
let cursor;
|
|
31
|
+
do {
|
|
32
|
+
const page = await folderAssignmentExecutor.list({
|
|
33
|
+
filter: { field: "folderId", op: "eq", value: payload.id },
|
|
34
|
+
limit: ASSIGNMENT_PAGE_SIZE,
|
|
35
|
+
cursor,
|
|
36
|
+
}, event.user, ctx.db);
|
|
37
|
+
for (const row of page.rows) {
|
|
38
|
+
const gone = await hostRowIsGone(ctx.registry, String(row["entityType"]), String(row["entityId"]), ctx.db);
|
|
39
|
+
if (!gone) {
|
|
40
|
+
return writeFailure(new UnprocessableError("folder_has_assignments", {
|
|
41
|
+
details: { folderId: payload.id },
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
orphanAssignmentIds.push(String(row["id"]));
|
|
45
|
+
}
|
|
46
|
+
cursor = page.nextCursor ?? undefined;
|
|
47
|
+
} while (cursor !== undefined);
|
|
48
|
+
for (const id of orphanAssignmentIds) {
|
|
49
|
+
const removed = await folderAssignmentExecutor.delete({ id }, event.user, ctx.db);
|
|
50
|
+
if (!removed.isSuccess)
|
|
51
|
+
return removed;
|
|
25
52
|
}
|
|
26
53
|
return folderExecutor.delete({ id: payload.id }, event.user, ctx.db);
|
|
27
54
|
},
|
|
@@ -12,4 +12,4 @@ export type StaleDraftRow = {
|
|
|
12
12
|
};
|
|
13
13
|
export declare function selectStaleDraftsBatch(db: DbConnection, olderThanDays: number, batchSize: number): Promise<readonly StaleDraftRow[]>;
|
|
14
14
|
/** Re-check staleness after the select→release window so a freshly saved draft is not deleted. */
|
|
15
|
-
export declare function isDraftStillStale(db: DbConnection, id: string, olderThanDays: number): Promise<boolean>;
|
|
15
|
+
export declare function isDraftStillStale(db: DbConnection, tenantId: TenantId, id: string, olderThanDays: number): Promise<boolean>;
|
|
@@ -26,10 +26,11 @@ export async function selectStaleDraftsBatch(db, olderThanDays, batchSize) {
|
|
|
26
26
|
}));
|
|
27
27
|
}
|
|
28
28
|
/** Re-check staleness after the select→release window so a freshly saved draft is not deleted. */
|
|
29
|
-
export async function isDraftStillStale(db, id, olderThanDays) {
|
|
29
|
+
export async function isDraftStillStale(db, tenantId, id, olderThanDays) {
|
|
30
30
|
const rows = (await unsafeReadRetrying(db, `SELECT 1 FROM "read_form_drafts"
|
|
31
31
|
WHERE "id" = $1
|
|
32
|
-
AND
|
|
33
|
-
|
|
32
|
+
AND "tenant_id" = $2
|
|
33
|
+
AND COALESCE("modified_at", "inserted_at") < now() - ($3::int * interval '1 day')
|
|
34
|
+
LIMIT 1`, [id, tenantId, olderThanDays]));
|
|
34
35
|
return rows.length > 0;
|
|
35
36
|
}
|
|
@@ -3,6 +3,7 @@ export declare const formDraftEntity: import("@cosmicdrift/kumiko-types/fields")
|
|
|
3
3
|
readonly type: "text";
|
|
4
4
|
readonly description?: string;
|
|
5
5
|
readonly maxLength?: number;
|
|
6
|
+
readonly minLength?: number;
|
|
6
7
|
readonly required?: boolean;
|
|
7
8
|
readonly searchable?: boolean;
|
|
8
9
|
readonly sortable?: boolean;
|
|
@@ -24,6 +25,7 @@ export declare const formDraftEntity: import("@cosmicdrift/kumiko-types/fields")
|
|
|
24
25
|
readonly type: "text";
|
|
25
26
|
readonly description?: string;
|
|
26
27
|
readonly maxLength?: number;
|
|
28
|
+
readonly minLength?: number;
|
|
27
29
|
readonly required?: boolean;
|
|
28
30
|
readonly searchable?: boolean;
|
|
29
31
|
readonly sortable?: boolean;
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { type DbConnection } from "@cosmicdrift/kumiko-framework/db";
|
|
2
|
-
import { type ConfigKeyDefinition, type JobContext, type TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
import { type AppContext, type ConfigKeyDefinition, type JobContext, type TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
3
|
import { type StaleDraftRow } from "../db/queries/cleanup.js";
|
|
4
4
|
export declare const FORM_DRAFT_RETENTION_DAYS_CONFIG_KEY = "form-draft:config:retention-days";
|
|
5
5
|
export declare const FORM_DRAFT_DEFAULT_RETENTION_DAYS = 30;
|
|
6
6
|
export declare const formDraftRetentionDaysConfig: ConfigKeyDefinition<"number">;
|
|
7
7
|
export declare function groupStaleDraftIdsByTenant(batch: readonly StaleDraftRow[]): ReadonlyMap<TenantId, readonly string[]>;
|
|
8
|
+
export declare function deleteStaleDraftsBatch(batch: readonly StaleDraftRow[], db: DbConnection, retentionDays: number, log: AppContext["log"]): Promise<{
|
|
9
|
+
readonly deletedRows: readonly StaleDraftRow[];
|
|
10
|
+
readonly skippedCount: number;
|
|
11
|
+
}>;
|
|
8
12
|
export declare function cleanupDraftsJob(_payload: Record<string, unknown>, ctx: JobContext, db: DbConnection): Promise<void>;
|
|
@@ -72,16 +72,19 @@ export function groupStaleDraftIdsByTenant(batch) {
|
|
|
72
72
|
// resurrects, PII included. formDraftExecutor.delete() is the event-sourced
|
|
73
73
|
// path; it needs a tenant-scoped db + a SessionUser, so rows are grouped by
|
|
74
74
|
// tenant first (see groupStaleDraftIdsByTenant above).
|
|
75
|
-
async function deleteStaleDraftsBatch(batch, db, retentionDays, log) {
|
|
75
|
+
export async function deleteStaleDraftsBatch(batch, db, retentionDays, log) {
|
|
76
76
|
const deletedRows = [];
|
|
77
|
+
let skippedCount = 0;
|
|
77
78
|
const byId = new Map(batch.map((row) => [row.id, row]));
|
|
78
79
|
for (const [tenantId, ids] of groupStaleDraftIdsByTenant(batch)) {
|
|
79
80
|
const systemUser = createSystemUser(tenantId);
|
|
80
81
|
const tenantDb = createTenantDb(db, tenantId, "system");
|
|
81
82
|
for (const id of ids) {
|
|
82
83
|
// Race: user may have saved between select and here — skip if no longer stale.
|
|
83
|
-
if (!(await isDraftStillStale(db, id, retentionDays)))
|
|
84
|
+
if (!(await isDraftStillStale(db, tenantId, id, retentionDays))) {
|
|
85
|
+
skippedCount++;
|
|
84
86
|
continue;
|
|
87
|
+
}
|
|
85
88
|
const result = await formDraftExecutor.delete({ id }, systemUser, tenantDb);
|
|
86
89
|
if (result.isSuccess) {
|
|
87
90
|
const row = byId.get(id);
|
|
@@ -93,7 +96,7 @@ async function deleteStaleDraftsBatch(batch, db, retentionDays, log) {
|
|
|
93
96
|
}
|
|
94
97
|
}
|
|
95
98
|
}
|
|
96
|
-
return deletedRows;
|
|
99
|
+
return { deletedRows, skippedCount };
|
|
97
100
|
}
|
|
98
101
|
export async function cleanupDraftsJob(_payload, ctx, db) {
|
|
99
102
|
const resolved = ctx.configResolver
|
|
@@ -122,12 +125,15 @@ export async function cleanupDraftsJob(_payload, ctx, db) {
|
|
|
122
125
|
// entirely for rows with no FileRefs in the blob, the common case.
|
|
123
126
|
// Delete first (with staleness re-check), then release binaries from rows
|
|
124
127
|
// that actually disappeared — never release a draft the user just saved.
|
|
125
|
-
const deletedRows = await deleteStaleDraftsBatch(batch, db, retentionDays, ctx.log);
|
|
128
|
+
const { deletedRows, skippedCount } = await deleteStaleDraftsBatch(batch, db, retentionDays, ctx.log);
|
|
126
129
|
for (const row of deletedRows) {
|
|
127
130
|
await releaseRowFileRefs(row, db, fileProviderResolver, ctx.log);
|
|
128
131
|
}
|
|
129
132
|
deleted += deletedRows.length;
|
|
130
|
-
|
|
133
|
+
// A skipped row is no longer stale, so the next select drops it — a fully
|
|
134
|
+
// skipped batch still makes progress. Only a batch with neither deletes nor
|
|
135
|
+
// skips (all failed) would reselect the same rows forever.
|
|
136
|
+
if (deletedRows.length + skippedCount === 0 || batch.length < DEFAULT_BATCH_SIZE)
|
|
131
137
|
break;
|
|
132
138
|
}
|
|
133
139
|
ctx.log?.info?.(`[form-draft:cleanup] deleted=${deleted} retentionDays=${retentionDays}`);
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { requireDefined, requireNonEmpty, requireSecretSet } from "./config-helpers.js";
|
|
2
2
|
export { isHostAllowlisted, readHostAllowlistFromEnv } from "./host-allowlist.js";
|
|
3
|
-
export { BlockedHostError, HostResolutionError, MAIL_ALLOWED_PRIVATE_HOSTS_ENV_VAR, type MailConnectTarget, type MailHostGuardOptions, readAllowedPrivateMailHostsFromEnv, resolveMailConnectTarget, } from "./mail-host-policy.js";
|
|
3
|
+
export { BlockedHostError, HostResolutionError, MAIL_ALLOWED_PRIVATE_HOSTS_ENV_VAR, type MailConnectTarget, type MailHostGuardOptions, mailAllowedPrivateHostsEnvSchema, readAllowedPrivateMailHostsFromEnv, resolveMailConnectTarget, } from "./mail-host-policy.js";
|
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
// Foundation packages (ai-foundation, mail-foundation, file-foundation).
|
|
3
3
|
export { requireDefined, requireNonEmpty, requireSecretSet } from "./config-helpers.js";
|
|
4
4
|
export { isHostAllowlisted, readHostAllowlistFromEnv } from "./host-allowlist.js";
|
|
5
|
-
export { BlockedHostError, HostResolutionError, MAIL_ALLOWED_PRIVATE_HOSTS_ENV_VAR, readAllowedPrivateMailHostsFromEnv, resolveMailConnectTarget, } from "./mail-host-policy.js";
|
|
5
|
+
export { BlockedHostError, HostResolutionError, MAIL_ALLOWED_PRIVATE_HOSTS_ENV_VAR, mailAllowedPrivateHostsEnvSchema, readAllowedPrivateMailHostsFromEnv, resolveMailConnectTarget, } from "./mail-host-policy.js";
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { lookup } from "node:dns/promises";
|
|
2
|
+
import * as z from "zod";
|
|
2
3
|
export declare const MAIL_ALLOWED_PRIVATE_HOSTS_ENV_VAR = "KUMIKO_MAIL_ALLOWED_PRIVATE_HOSTS";
|
|
4
|
+
export declare const mailAllowedPrivateHostsEnvSchema: z.ZodObject<{
|
|
5
|
+
KUMIKO_MAIL_ALLOWED_PRIVATE_HOSTS: z.ZodOptional<z.ZodString>;
|
|
6
|
+
}, z.core.$strip>;
|
|
3
7
|
export declare function readAllowedPrivateMailHostsFromEnv(env?: Readonly<Record<string, string | undefined>>): readonly string[];
|
|
4
8
|
export type MailHostGuardOptions = {
|
|
5
9
|
readonly allowedPrivateMailHosts?: readonly string[];
|
|
@@ -10,14 +10,23 @@
|
|
|
10
10
|
// internal relay or a dev/test server (mailpit, greenmail) — deliberately
|
|
11
11
|
// an operator env var (KUMIKO_MAIL_ALLOWED_PRIVATE_HOSTS, read via
|
|
12
12
|
// readAllowedPrivateMailHostsFromEnv), never a tenant-config key, so a
|
|
13
|
-
// tenant can never grant themselves the bypass.
|
|
14
|
-
//
|
|
15
|
-
// smtp's envSchema (see its feature.ts) to avoid the framework's env-var-
|
|
16
|
-
// conflict check tripping when both features are mounted together.
|
|
13
|
+
// tenant can never grant themselves the bypass. Both features register the
|
|
14
|
+
// shared mailAllowedPrivateHostsEnvSchema below.
|
|
17
15
|
import { isIP } from "node:net";
|
|
18
16
|
import { resolvePublicHostname } from "@cosmicdrift/kumiko-framework/http";
|
|
17
|
+
import * as z from "zod";
|
|
19
18
|
import { isHostAllowlisted, readHostAllowlistFromEnv } from "./host-allowlist.js";
|
|
20
19
|
export const MAIL_ALLOWED_PRIVATE_HOSTS_ENV_VAR = "KUMIKO_MAIL_ALLOWED_PRIVATE_HOSTS";
|
|
20
|
+
// One schema instance, registered by every feature that reads the variable:
|
|
21
|
+
// composeEnvSchema accepts the same field instance from several features, so
|
|
22
|
+
// an app mounting only one of them still gets the variable validated and
|
|
23
|
+
// documented.
|
|
24
|
+
export const mailAllowedPrivateHostsEnvSchema = z.object({
|
|
25
|
+
[MAIL_ALLOWED_PRIVATE_HOSTS_ENV_VAR]: z
|
|
26
|
+
.string()
|
|
27
|
+
.optional()
|
|
28
|
+
.describe("Comma-separated operator allowlist of private/internal hosts (e.g. a local mailpit/greenmail dev server) that bypass the public-address check for SMTP and IMAP host config. Shared by mail-transport-smtp and inbound-provider-imap — never a tenant-config value."),
|
|
29
|
+
});
|
|
21
30
|
export function readAllowedPrivateMailHostsFromEnv(env = process.env) {
|
|
22
31
|
return readHostAllowlistFromEnv(MAIL_ALLOWED_PRIVATE_HOSTS_ENV_VAR, env);
|
|
23
32
|
}
|
|
@@ -5,6 +5,7 @@ export declare const mailAccountEntity: import("@cosmicdrift/kumiko-types/fields
|
|
|
5
5
|
readonly type: "text";
|
|
6
6
|
readonly description?: string;
|
|
7
7
|
readonly maxLength?: number;
|
|
8
|
+
readonly minLength?: number;
|
|
8
9
|
readonly required?: boolean;
|
|
9
10
|
readonly searchable?: boolean;
|
|
10
11
|
readonly sortable?: boolean;
|
|
@@ -26,6 +27,7 @@ export declare const mailAccountEntity: import("@cosmicdrift/kumiko-types/fields
|
|
|
26
27
|
readonly type: "text";
|
|
27
28
|
readonly description?: string;
|
|
28
29
|
readonly maxLength?: number;
|
|
30
|
+
readonly minLength?: number;
|
|
29
31
|
readonly required?: boolean;
|
|
30
32
|
readonly searchable?: boolean;
|
|
31
33
|
readonly sortable?: boolean;
|
|
@@ -47,6 +49,7 @@ export declare const mailAccountEntity: import("@cosmicdrift/kumiko-types/fields
|
|
|
47
49
|
readonly type: "text";
|
|
48
50
|
readonly description?: string;
|
|
49
51
|
readonly maxLength?: number;
|
|
52
|
+
readonly minLength?: number;
|
|
50
53
|
readonly required?: boolean;
|
|
51
54
|
readonly searchable?: boolean;
|
|
52
55
|
readonly sortable?: boolean;
|
|
@@ -68,6 +71,7 @@ export declare const mailAccountEntity: import("@cosmicdrift/kumiko-types/fields
|
|
|
68
71
|
readonly type: "text";
|
|
69
72
|
readonly description?: string;
|
|
70
73
|
readonly maxLength?: number;
|
|
74
|
+
readonly minLength?: number;
|
|
71
75
|
readonly required?: boolean;
|
|
72
76
|
readonly searchable?: boolean;
|
|
73
77
|
readonly sortable?: boolean;
|
|
@@ -89,6 +93,7 @@ export declare const mailAccountEntity: import("@cosmicdrift/kumiko-types/fields
|
|
|
89
93
|
readonly type: "text";
|
|
90
94
|
readonly description?: string;
|
|
91
95
|
readonly maxLength?: number;
|
|
96
|
+
readonly minLength?: number;
|
|
92
97
|
readonly required?: boolean;
|
|
93
98
|
readonly searchable?: boolean;
|
|
94
99
|
readonly sortable?: boolean;
|
|
@@ -110,6 +115,7 @@ export declare const mailAccountEntity: import("@cosmicdrift/kumiko-types/fields
|
|
|
110
115
|
readonly type: "text";
|
|
111
116
|
readonly description?: string;
|
|
112
117
|
readonly maxLength?: number;
|
|
118
|
+
readonly minLength?: number;
|
|
113
119
|
readonly required?: boolean;
|
|
114
120
|
readonly searchable?: boolean;
|
|
115
121
|
readonly sortable?: boolean;
|
|
@@ -131,6 +137,7 @@ export declare const mailAccountEntity: import("@cosmicdrift/kumiko-types/fields
|
|
|
131
137
|
readonly type: "text";
|
|
132
138
|
readonly description?: string;
|
|
133
139
|
readonly maxLength?: number;
|
|
140
|
+
readonly minLength?: number;
|
|
134
141
|
readonly required?: boolean;
|
|
135
142
|
readonly searchable?: boolean;
|
|
136
143
|
readonly sortable?: boolean;
|
|
@@ -172,6 +179,7 @@ export declare const inboundMessageEntity: import("@cosmicdrift/kumiko-types/fie
|
|
|
172
179
|
readonly type: "text";
|
|
173
180
|
readonly description?: string;
|
|
174
181
|
readonly maxLength?: number;
|
|
182
|
+
readonly minLength?: number;
|
|
175
183
|
readonly required?: boolean;
|
|
176
184
|
readonly searchable?: boolean;
|
|
177
185
|
readonly sortable?: boolean;
|
|
@@ -193,6 +201,7 @@ export declare const inboundMessageEntity: import("@cosmicdrift/kumiko-types/fie
|
|
|
193
201
|
readonly type: "text";
|
|
194
202
|
readonly description?: string;
|
|
195
203
|
readonly maxLength?: number;
|
|
204
|
+
readonly minLength?: number;
|
|
196
205
|
readonly required?: boolean;
|
|
197
206
|
readonly searchable?: boolean;
|
|
198
207
|
readonly sortable?: boolean;
|
|
@@ -214,6 +223,7 @@ export declare const inboundMessageEntity: import("@cosmicdrift/kumiko-types/fie
|
|
|
214
223
|
readonly type: "text";
|
|
215
224
|
readonly description?: string;
|
|
216
225
|
readonly maxLength?: number;
|
|
226
|
+
readonly minLength?: number;
|
|
217
227
|
readonly required?: boolean;
|
|
218
228
|
readonly searchable?: boolean;
|
|
219
229
|
readonly sortable?: boolean;
|
|
@@ -235,6 +245,7 @@ export declare const inboundMessageEntity: import("@cosmicdrift/kumiko-types/fie
|
|
|
235
245
|
readonly type: "text";
|
|
236
246
|
readonly description?: string;
|
|
237
247
|
readonly maxLength?: number;
|
|
248
|
+
readonly minLength?: number;
|
|
238
249
|
readonly required?: boolean;
|
|
239
250
|
readonly searchable?: boolean;
|
|
240
251
|
readonly sortable?: boolean;
|
|
@@ -256,6 +267,7 @@ export declare const inboundMessageEntity: import("@cosmicdrift/kumiko-types/fie
|
|
|
256
267
|
readonly type: "text";
|
|
257
268
|
readonly description?: string;
|
|
258
269
|
readonly maxLength?: number;
|
|
270
|
+
readonly minLength?: number;
|
|
259
271
|
readonly required?: boolean;
|
|
260
272
|
readonly searchable?: boolean;
|
|
261
273
|
readonly sortable?: boolean;
|
|
@@ -277,6 +289,7 @@ export declare const inboundMessageEntity: import("@cosmicdrift/kumiko-types/fie
|
|
|
277
289
|
readonly type: "text";
|
|
278
290
|
readonly description?: string;
|
|
279
291
|
readonly maxLength?: number;
|
|
292
|
+
readonly minLength?: number;
|
|
280
293
|
readonly required?: boolean;
|
|
281
294
|
readonly searchable?: boolean;
|
|
282
295
|
readonly sortable?: boolean;
|
|
@@ -298,6 +311,7 @@ export declare const inboundMessageEntity: import("@cosmicdrift/kumiko-types/fie
|
|
|
298
311
|
readonly type: "text";
|
|
299
312
|
readonly description?: string;
|
|
300
313
|
readonly maxLength?: number;
|
|
314
|
+
readonly minLength?: number;
|
|
301
315
|
readonly required?: boolean;
|
|
302
316
|
readonly searchable?: boolean;
|
|
303
317
|
readonly sortable?: boolean;
|
|
@@ -319,6 +333,7 @@ export declare const inboundMessageEntity: import("@cosmicdrift/kumiko-types/fie
|
|
|
319
333
|
readonly type: "text";
|
|
320
334
|
readonly description?: string;
|
|
321
335
|
readonly maxLength?: number;
|
|
336
|
+
readonly minLength?: number;
|
|
322
337
|
readonly required?: boolean;
|
|
323
338
|
readonly searchable?: boolean;
|
|
324
339
|
readonly sortable?: boolean;
|
|
@@ -340,6 +355,7 @@ export declare const inboundMessageEntity: import("@cosmicdrift/kumiko-types/fie
|
|
|
340
355
|
readonly type: "text";
|
|
341
356
|
readonly description?: string;
|
|
342
357
|
readonly maxLength?: number;
|
|
358
|
+
readonly minLength?: number;
|
|
343
359
|
readonly required?: boolean;
|
|
344
360
|
readonly searchable?: boolean;
|
|
345
361
|
readonly sortable?: boolean;
|
|
@@ -376,6 +392,7 @@ export declare const inboundMessageEntity: import("@cosmicdrift/kumiko-types/fie
|
|
|
376
392
|
readonly type: "text";
|
|
377
393
|
readonly description?: string;
|
|
378
394
|
readonly maxLength?: number;
|
|
395
|
+
readonly minLength?: number;
|
|
379
396
|
readonly required?: boolean;
|
|
380
397
|
readonly searchable?: boolean;
|
|
381
398
|
readonly sortable?: boolean;
|
|
@@ -397,6 +414,7 @@ export declare const inboundMessageEntity: import("@cosmicdrift/kumiko-types/fie
|
|
|
397
414
|
readonly type: "text";
|
|
398
415
|
readonly description?: string;
|
|
399
416
|
readonly maxLength?: number;
|
|
417
|
+
readonly minLength?: number;
|
|
400
418
|
readonly required?: boolean;
|
|
401
419
|
readonly searchable?: boolean;
|
|
402
420
|
readonly sortable?: boolean;
|
|
@@ -422,6 +440,7 @@ export declare const mailThreadEntity: import("@cosmicdrift/kumiko-types/fields"
|
|
|
422
440
|
readonly type: "text";
|
|
423
441
|
readonly description?: string;
|
|
424
442
|
readonly maxLength?: number;
|
|
443
|
+
readonly minLength?: number;
|
|
425
444
|
readonly required?: boolean;
|
|
426
445
|
readonly searchable?: boolean;
|
|
427
446
|
readonly sortable?: boolean;
|
|
@@ -443,6 +462,7 @@ export declare const mailThreadEntity: import("@cosmicdrift/kumiko-types/fields"
|
|
|
443
462
|
readonly type: "text";
|
|
444
463
|
readonly description?: string;
|
|
445
464
|
readonly maxLength?: number;
|
|
465
|
+
readonly minLength?: number;
|
|
446
466
|
readonly required?: boolean;
|
|
447
467
|
readonly searchable?: boolean;
|
|
448
468
|
readonly sortable?: boolean;
|
|
@@ -507,6 +527,7 @@ export declare const syncCursorEntity: import("@cosmicdrift/kumiko-types/fields"
|
|
|
507
527
|
readonly type: "text";
|
|
508
528
|
readonly description?: string;
|
|
509
529
|
readonly maxLength?: number;
|
|
530
|
+
readonly minLength?: number;
|
|
510
531
|
readonly required?: boolean;
|
|
511
532
|
readonly searchable?: boolean;
|
|
512
533
|
readonly sortable?: boolean;
|
|
@@ -528,6 +549,7 @@ export declare const syncCursorEntity: import("@cosmicdrift/kumiko-types/fields"
|
|
|
528
549
|
readonly type: "text";
|
|
529
550
|
readonly description?: string;
|
|
530
551
|
readonly maxLength?: number;
|
|
552
|
+
readonly minLength?: number;
|
|
531
553
|
readonly required?: boolean;
|
|
532
554
|
readonly searchable?: boolean;
|
|
533
555
|
readonly sortable?: boolean;
|
|
@@ -549,6 +571,7 @@ export declare const syncCursorEntity: import("@cosmicdrift/kumiko-types/fields"
|
|
|
549
571
|
readonly type: "text";
|
|
550
572
|
readonly description?: string;
|
|
551
573
|
readonly maxLength?: number;
|
|
574
|
+
readonly minLength?: number;
|
|
552
575
|
readonly required?: boolean;
|
|
553
576
|
readonly searchable?: boolean;
|
|
554
577
|
readonly sortable?: boolean;
|
|
@@ -591,6 +614,7 @@ export declare const seenMessageEntity: import("@cosmicdrift/kumiko-types/fields
|
|
|
591
614
|
readonly type: "text";
|
|
592
615
|
readonly description?: string;
|
|
593
616
|
readonly maxLength?: number;
|
|
617
|
+
readonly minLength?: number;
|
|
594
618
|
readonly required?: boolean;
|
|
595
619
|
readonly searchable?: boolean;
|
|
596
620
|
readonly sortable?: boolean;
|
|
@@ -612,6 +636,7 @@ export declare const seenMessageEntity: import("@cosmicdrift/kumiko-types/fields
|
|
|
612
636
|
readonly type: "text";
|
|
613
637
|
readonly description?: string;
|
|
614
638
|
readonly maxLength?: number;
|
|
639
|
+
readonly minLength?: number;
|
|
615
640
|
readonly required?: boolean;
|
|
616
641
|
readonly searchable?: boolean;
|
|
617
642
|
readonly sortable?: boolean;
|
|
@@ -3,6 +3,7 @@ export declare const mailAccountsProjectionTable: import("@cosmicdrift/kumiko-fr
|
|
|
3
3
|
readonly type: "text";
|
|
4
4
|
readonly description?: string;
|
|
5
5
|
readonly maxLength?: number;
|
|
6
|
+
readonly minLength?: number;
|
|
6
7
|
readonly required?: boolean;
|
|
7
8
|
readonly searchable?: boolean;
|
|
8
9
|
readonly sortable?: boolean;
|
|
@@ -24,6 +25,7 @@ export declare const mailAccountsProjectionTable: import("@cosmicdrift/kumiko-fr
|
|
|
24
25
|
readonly type: "text";
|
|
25
26
|
readonly description?: string;
|
|
26
27
|
readonly maxLength?: number;
|
|
28
|
+
readonly minLength?: number;
|
|
27
29
|
readonly required?: boolean;
|
|
28
30
|
readonly searchable?: boolean;
|
|
29
31
|
readonly sortable?: boolean;
|
|
@@ -45,6 +47,7 @@ export declare const mailAccountsProjectionTable: import("@cosmicdrift/kumiko-fr
|
|
|
45
47
|
readonly type: "text";
|
|
46
48
|
readonly description?: string;
|
|
47
49
|
readonly maxLength?: number;
|
|
50
|
+
readonly minLength?: number;
|
|
48
51
|
readonly required?: boolean;
|
|
49
52
|
readonly searchable?: boolean;
|
|
50
53
|
readonly sortable?: boolean;
|
|
@@ -66,6 +69,7 @@ export declare const mailAccountsProjectionTable: import("@cosmicdrift/kumiko-fr
|
|
|
66
69
|
readonly type: "text";
|
|
67
70
|
readonly description?: string;
|
|
68
71
|
readonly maxLength?: number;
|
|
72
|
+
readonly minLength?: number;
|
|
69
73
|
readonly required?: boolean;
|
|
70
74
|
readonly searchable?: boolean;
|
|
71
75
|
readonly sortable?: boolean;
|
|
@@ -87,6 +91,7 @@ export declare const mailAccountsProjectionTable: import("@cosmicdrift/kumiko-fr
|
|
|
87
91
|
readonly type: "text";
|
|
88
92
|
readonly description?: string;
|
|
89
93
|
readonly maxLength?: number;
|
|
94
|
+
readonly minLength?: number;
|
|
90
95
|
readonly required?: boolean;
|
|
91
96
|
readonly searchable?: boolean;
|
|
92
97
|
readonly sortable?: boolean;
|
|
@@ -108,6 +113,7 @@ export declare const mailAccountsProjectionTable: import("@cosmicdrift/kumiko-fr
|
|
|
108
113
|
readonly type: "text";
|
|
109
114
|
readonly description?: string;
|
|
110
115
|
readonly maxLength?: number;
|
|
116
|
+
readonly minLength?: number;
|
|
111
117
|
readonly required?: boolean;
|
|
112
118
|
readonly searchable?: boolean;
|
|
113
119
|
readonly sortable?: boolean;
|
|
@@ -129,6 +135,7 @@ export declare const mailAccountsProjectionTable: import("@cosmicdrift/kumiko-fr
|
|
|
129
135
|
readonly type: "text";
|
|
130
136
|
readonly description?: string;
|
|
131
137
|
readonly maxLength?: number;
|
|
138
|
+
readonly minLength?: number;
|
|
132
139
|
readonly required?: boolean;
|
|
133
140
|
readonly searchable?: boolean;
|
|
134
141
|
readonly sortable?: boolean;
|
|
@@ -167,6 +174,7 @@ export declare const inboundMessagesProjectionTable: import("@cosmicdrift/kumiko
|
|
|
167
174
|
readonly type: "text";
|
|
168
175
|
readonly description?: string;
|
|
169
176
|
readonly maxLength?: number;
|
|
177
|
+
readonly minLength?: number;
|
|
170
178
|
readonly required?: boolean;
|
|
171
179
|
readonly searchable?: boolean;
|
|
172
180
|
readonly sortable?: boolean;
|
|
@@ -188,6 +196,7 @@ export declare const inboundMessagesProjectionTable: import("@cosmicdrift/kumiko
|
|
|
188
196
|
readonly type: "text";
|
|
189
197
|
readonly description?: string;
|
|
190
198
|
readonly maxLength?: number;
|
|
199
|
+
readonly minLength?: number;
|
|
191
200
|
readonly required?: boolean;
|
|
192
201
|
readonly searchable?: boolean;
|
|
193
202
|
readonly sortable?: boolean;
|
|
@@ -209,6 +218,7 @@ export declare const inboundMessagesProjectionTable: import("@cosmicdrift/kumiko
|
|
|
209
218
|
readonly type: "text";
|
|
210
219
|
readonly description?: string;
|
|
211
220
|
readonly maxLength?: number;
|
|
221
|
+
readonly minLength?: number;
|
|
212
222
|
readonly required?: boolean;
|
|
213
223
|
readonly searchable?: boolean;
|
|
214
224
|
readonly sortable?: boolean;
|
|
@@ -230,6 +240,7 @@ export declare const inboundMessagesProjectionTable: import("@cosmicdrift/kumiko
|
|
|
230
240
|
readonly type: "text";
|
|
231
241
|
readonly description?: string;
|
|
232
242
|
readonly maxLength?: number;
|
|
243
|
+
readonly minLength?: number;
|
|
233
244
|
readonly required?: boolean;
|
|
234
245
|
readonly searchable?: boolean;
|
|
235
246
|
readonly sortable?: boolean;
|
|
@@ -251,6 +262,7 @@ export declare const inboundMessagesProjectionTable: import("@cosmicdrift/kumiko
|
|
|
251
262
|
readonly type: "text";
|
|
252
263
|
readonly description?: string;
|
|
253
264
|
readonly maxLength?: number;
|
|
265
|
+
readonly minLength?: number;
|
|
254
266
|
readonly required?: boolean;
|
|
255
267
|
readonly searchable?: boolean;
|
|
256
268
|
readonly sortable?: boolean;
|
|
@@ -272,6 +284,7 @@ export declare const inboundMessagesProjectionTable: import("@cosmicdrift/kumiko
|
|
|
272
284
|
readonly type: "text";
|
|
273
285
|
readonly description?: string;
|
|
274
286
|
readonly maxLength?: number;
|
|
287
|
+
readonly minLength?: number;
|
|
275
288
|
readonly required?: boolean;
|
|
276
289
|
readonly searchable?: boolean;
|
|
277
290
|
readonly sortable?: boolean;
|
|
@@ -293,6 +306,7 @@ export declare const inboundMessagesProjectionTable: import("@cosmicdrift/kumiko
|
|
|
293
306
|
readonly type: "text";
|
|
294
307
|
readonly description?: string;
|
|
295
308
|
readonly maxLength?: number;
|
|
309
|
+
readonly minLength?: number;
|
|
296
310
|
readonly required?: boolean;
|
|
297
311
|
readonly searchable?: boolean;
|
|
298
312
|
readonly sortable?: boolean;
|
|
@@ -314,6 +328,7 @@ export declare const inboundMessagesProjectionTable: import("@cosmicdrift/kumiko
|
|
|
314
328
|
readonly type: "text";
|
|
315
329
|
readonly description?: string;
|
|
316
330
|
readonly maxLength?: number;
|
|
331
|
+
readonly minLength?: number;
|
|
317
332
|
readonly required?: boolean;
|
|
318
333
|
readonly searchable?: boolean;
|
|
319
334
|
readonly sortable?: boolean;
|
|
@@ -335,6 +350,7 @@ export declare const inboundMessagesProjectionTable: import("@cosmicdrift/kumiko
|
|
|
335
350
|
readonly type: "text";
|
|
336
351
|
readonly description?: string;
|
|
337
352
|
readonly maxLength?: number;
|
|
353
|
+
readonly minLength?: number;
|
|
338
354
|
readonly required?: boolean;
|
|
339
355
|
readonly searchable?: boolean;
|
|
340
356
|
readonly sortable?: boolean;
|
|
@@ -371,6 +387,7 @@ export declare const inboundMessagesProjectionTable: import("@cosmicdrift/kumiko
|
|
|
371
387
|
readonly type: "text";
|
|
372
388
|
readonly description?: string;
|
|
373
389
|
readonly maxLength?: number;
|
|
390
|
+
readonly minLength?: number;
|
|
374
391
|
readonly required?: boolean;
|
|
375
392
|
readonly searchable?: boolean;
|
|
376
393
|
readonly sortable?: boolean;
|
|
@@ -392,6 +409,7 @@ export declare const inboundMessagesProjectionTable: import("@cosmicdrift/kumiko
|
|
|
392
409
|
readonly type: "text";
|
|
393
410
|
readonly description?: string;
|
|
394
411
|
readonly maxLength?: number;
|
|
412
|
+
readonly minLength?: number;
|
|
395
413
|
readonly required?: boolean;
|
|
396
414
|
readonly searchable?: boolean;
|
|
397
415
|
readonly sortable?: boolean;
|
|
@@ -415,6 +433,7 @@ export declare const mailThreadsProjectionTable: import("@cosmicdrift/kumiko-fra
|
|
|
415
433
|
readonly type: "text";
|
|
416
434
|
readonly description?: string;
|
|
417
435
|
readonly maxLength?: number;
|
|
436
|
+
readonly minLength?: number;
|
|
418
437
|
readonly required?: boolean;
|
|
419
438
|
readonly searchable?: boolean;
|
|
420
439
|
readonly sortable?: boolean;
|
|
@@ -436,6 +455,7 @@ export declare const mailThreadsProjectionTable: import("@cosmicdrift/kumiko-fra
|
|
|
436
455
|
readonly type: "text";
|
|
437
456
|
readonly description?: string;
|
|
438
457
|
readonly maxLength?: number;
|
|
458
|
+
readonly minLength?: number;
|
|
439
459
|
readonly required?: boolean;
|
|
440
460
|
readonly searchable?: boolean;
|
|
441
461
|
readonly sortable?: boolean;
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
// Credentials: JSON-Dokument im per-Account-Secret-Slot
|
|
23
23
|
// (credential-document.ts) — kein Tenant-Config, weil mehrere Accounts
|
|
24
24
|
// pro Tenant verschiedene Hosts haben.
|
|
25
|
+
import { mailAllowedPrivateHostsEnvSchema } from "@cosmicdrift/kumiko-bundled-features/foundation-shared";
|
|
25
26
|
import { INBOUND_MAIL_PROVIDER_EXTENSION, InboundAuthError, inboundCredentialSecretKey, } from "@cosmicdrift/kumiko-bundled-features/inbound-mail-foundation";
|
|
26
27
|
import { requireSecretsContext } from "@cosmicdrift/kumiko-bundled-features/secrets";
|
|
27
28
|
import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
@@ -225,5 +226,6 @@ export const inboundProviderImapFeature = defineFeature(FEATURE_NAME, (r) => {
|
|
|
225
226
|
});
|
|
226
227
|
r.requires("inbound-mail-foundation");
|
|
227
228
|
r.requires("secrets");
|
|
229
|
+
r.envSchema(mailAllowedPrivateHostsEnvSchema);
|
|
228
230
|
r.useExtension(INBOUND_MAIL_PROVIDER_EXTENSION, IMAP_PROVIDER_KEY, imapInboundMailPlugin);
|
|
229
231
|
});
|
|
@@ -21,10 +21,9 @@ const IMAP_HOST_UNREACHABLE_MESSAGE = "IMAP host is not reachable or not allowed
|
|
|
21
21
|
// =============================================================================
|
|
22
22
|
// Operator escape hatch for an internal relay or a dev/test IMAP server
|
|
23
23
|
// (greenmail): KUMIKO_MAIL_ALLOWED_PRIVATE_HOSTS, the same operator env
|
|
24
|
-
// var mail-transport-smtp
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
// never grant themselves the private-host bypass.
|
|
24
|
+
// var mail-transport-smtp registers (both share one schema instance, see
|
|
25
|
+
// foundation-shared), never a tenant-config value, so a tenant can never
|
|
26
|
+
// grant themselves the private-host bypass.
|
|
28
27
|
//
|
|
29
28
|
// mailHostLookup is a test-only DNS seam — production leaves it undefined,
|
|
30
29
|
// so resolveMailConnectTarget uses the real resolver. Module-global state:
|
package/dist/jobs/constants.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export declare const JobQueries: {
|
|
|
8
8
|
readonly details: "jobs:query:details";
|
|
9
9
|
readonly catalog: "jobs:query:catalog";
|
|
10
10
|
readonly failures: "jobs:query:failures";
|
|
11
|
+
readonly tenantRuns: "jobs:query:tenant-runs";
|
|
11
12
|
};
|
|
12
13
|
export declare const JobErrors: {
|
|
13
14
|
readonly unknownJob: "unknown_job";
|
package/dist/jobs/constants.js
CHANGED
|
@@ -3,5 +3,6 @@ export type JobRunRetentionResult = {
|
|
|
3
3
|
readonly runsDeleted: number;
|
|
4
4
|
readonly logsDeleted: number;
|
|
5
5
|
readonly tenantFailuresDeleted: number;
|
|
6
|
+
readonly tenantRunsDeleted: number;
|
|
6
7
|
};
|
|
7
8
|
export declare function deleteStaleJobRuns(db: DbConnection, retentionDays: number): Promise<JobRunRetentionResult>;
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import { deleteManyBatched } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
13
13
|
import { jobRunLogsTable, jobRunsTable } from "../../job-run-table.js";
|
|
14
14
|
import { tenantJobFailuresTable } from "../../tenant-job-failure-table.js";
|
|
15
|
+
import { tenantJobRunsTable } from "../../tenant-job-run-table.js";
|
|
15
16
|
const RETENTION_DELETE_BATCH_SIZE = 500;
|
|
16
17
|
export async function deleteStaleJobRuns(db, retentionDays) {
|
|
17
18
|
const cutoff = Temporal.Now.instant().subtract({ hours: retentionDays * 24 });
|
|
@@ -21,9 +22,13 @@ export async function deleteStaleJobRuns(db, retentionDays) {
|
|
|
21
22
|
// they point at is gone by now, and a job whose subject never ran again
|
|
22
23
|
// would otherwise keep its record forever.
|
|
23
24
|
const tenantFailuresResult = await deleteManyBatched(db, tenantJobFailuresTable, { failedAt: { lt: cutoff } }, { limit: RETENTION_DELETE_BATCH_SIZE });
|
|
25
|
+
// Only finished rows: active ones are the stale sweep's business, and a
|
|
26
|
+
// still-running job older than the window must not vanish from the tenant.
|
|
27
|
+
const tenantRunsResult = await deleteManyBatched(db, tenantJobRunsTable, { status: { in: ["completed", "failed"] }, updatedAt: { lt: cutoff } }, { limit: RETENTION_DELETE_BATCH_SIZE });
|
|
24
28
|
return {
|
|
25
29
|
runsDeleted: runsResult.deleted,
|
|
26
30
|
logsDeleted: logsResult.deleted,
|
|
27
31
|
tenantFailuresDeleted: tenantFailuresResult.deleted,
|
|
32
|
+
tenantRunsDeleted: tenantRunsResult.deleted,
|
|
28
33
|
};
|
|
29
34
|
}
|
|
@@ -2,5 +2,7 @@ import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
|
|
|
2
2
|
export declare const STALE_JOB_RUN_ERROR = "job run exceeded the stale-run timeout without a completion signal (likely a crashed worker process)";
|
|
3
3
|
export type StaleRunSweepResult = {
|
|
4
4
|
readonly runsMarkedFailed: number;
|
|
5
|
+
readonly tenantRunsMarkedFailed: number;
|
|
6
|
+
readonly tenantQueuedDropped: number;
|
|
5
7
|
};
|
|
6
8
|
export declare function markStaleJobRunsFailed(db: DbConnection, timeoutHours: number): Promise<StaleRunSweepResult>;
|