@cosmicdrift/kumiko-bundled-features 0.334.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/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/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/handlers/switch-plan.write.js +5 -8
- package/dist/cap-counter/book-cap-usage.js +15 -5
- package/dist/cap-counter/enforce-cap.js +9 -3
- 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/config/db/queries/resolver.js +3 -1
- package/dist/config/handlers/set.write.js +8 -4
- package/dist/config/web/i18n.js +1 -0
- 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/data-retention/resolve-for-tenant.d.ts +2 -3
- package/dist/data-retention/run-retention-cleanup.js +100 -44
- 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/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/files-tenant-data/index.js +3 -2
- package/dist/folders/handlers/clear-folder.write.js +4 -11
- package/dist/folders/handlers/set-folder.write.js +4 -10
- 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/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/tenant-job-failure-table.js +2 -6
- 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/metrics/compute.js +7 -1
- package/dist/metrics/window.js +5 -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.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/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.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/plugin-methods.js +25 -6
- package/dist/subscription-stripe/verify-webhook.js +13 -6
- 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 +8 -5
- package/dist/template-resolver/testing.js +2 -2
- package/dist/template-resolver/user-content-table.d.ts +2 -5
- 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/last-tenant-admin.d.ts +1 -1
- package/dist/tenant/last-tenant-admin.js +5 -3
- 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/tier-engine/feature.js +3 -2
- 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-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 +2 -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
|
@@ -62,6 +62,10 @@ import { generateDownloadToken } from "./token-helpers.js";
|
|
|
62
62
|
// No "_seconds"/"_total" suffix: validateMetricName rejects both on a gauge.
|
|
63
63
|
// Unit lives in the r.metric() `unit: "seconds"` declaration instead.
|
|
64
64
|
export const EXPORT_CLEANUP_BACKLOG_AGE_METRIC = "export_cleanup_backlog_age";
|
|
65
|
+
// Unix seconds of the last storage-cleanup pass that ran to completion. A
|
|
66
|
+
// gauge keeps its last value in-process (no TTL), so a dead cron does not make
|
|
67
|
+
// the backlog gauge disappear — alert on `time() - this > interval + buffer`.
|
|
68
|
+
export const EXPORT_CLEANUP_LAST_RUN_TIMESTAMP_METRIC = "export_cleanup_last_run_timestamp";
|
|
65
69
|
const crud = createEventStoreExecutor(exportJobsTable, exportJobEntity, {
|
|
66
70
|
entityName: "export-job",
|
|
67
71
|
});
|
|
@@ -429,54 +433,71 @@ async function storageCleanupPass(args) {
|
|
|
429
433
|
// via selectMany (db/queries/export-jobs.ts).
|
|
430
434
|
const candidates = await selectExportJobsForStorageCleanup(db, EXPORT_JOB_STATUS.Done, EXPORT_JOB_STATUS.Failed);
|
|
431
435
|
const cleaned = [];
|
|
432
|
-
//
|
|
433
|
-
//
|
|
434
|
-
//
|
|
435
|
-
//
|
|
436
|
-
//
|
|
437
|
-
// see EXPORT_CLEANUP_BACKLOG_AGE_METRIC's r.metric() description.
|
|
436
|
+
// Candidates past their due-instant, keyed by that instant — used below to
|
|
437
|
+
// report how far cleanup has fallen behind if a candidate is still standing
|
|
438
|
+
// (delete threw, or the version-checked null-out lost a race) once the pass
|
|
439
|
+
// is done. Done jobs are due at expiresAt+grace; Failed jobs (orphaned ZIPs,
|
|
440
|
+
// no TTL) are due at completedAt ?? startedAt, i.e. from the first pass.
|
|
438
441
|
const overdueDoneCleanupAfterMs = new Map();
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
// durch (kein User-Pfad → sofort cleanup).
|
|
444
|
-
if (c.status === EXPORT_JOB_STATUS.Done) {
|
|
445
|
-
if (!c.expiresAt)
|
|
442
|
+
let passCompleted = false;
|
|
443
|
+
try {
|
|
444
|
+
for (const c of candidates) {
|
|
445
|
+
if (!c.downloadStorageKey)
|
|
446
446
|
continue;
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
447
|
+
// Done jobs need the expiresAt+grace check. Failed jobs pass straight
|
|
448
|
+
// through (no user path, so clean up immediately).
|
|
449
|
+
if (c.status === EXPORT_JOB_STATUS.Done) {
|
|
450
|
+
if (!c.expiresAt)
|
|
451
|
+
continue;
|
|
452
|
+
// Count the candidate as overdue (lower bound, grace unknown) before the
|
|
453
|
+
// throwing profile lookup, so a failing pass still shows a backlog.
|
|
454
|
+
overdueDoneCleanupAfterMs.set(c.id, c.expiresAt.epochMilliseconds);
|
|
455
|
+
const profile = await resolveProfileForTenant({
|
|
456
|
+
db,
|
|
457
|
+
tenantId: c.requestedFromTenantId,
|
|
458
|
+
});
|
|
459
|
+
const cleanupAfter = c.expiresAt.epochMilliseconds +
|
|
460
|
+
profile.profile.userRights.exportStorageCleanupGraceHours * 60 * 60 * 1000;
|
|
461
|
+
if (now.epochMilliseconds < cleanupAfter) {
|
|
462
|
+
overdueDoneCleanupAfterMs.delete(c.id);
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
overdueDoneCleanupAfterMs.set(c.id, cleanupAfter);
|
|
466
|
+
}
|
|
467
|
+
// Failed-job branch: no TTL check, clean up immediately.
|
|
468
|
+
else {
|
|
469
|
+
const failedAt = c.completedAt ?? c.startedAt;
|
|
470
|
+
if (failedAt)
|
|
471
|
+
overdueDoneCleanupAfterMs.set(c.id, failedAt.epochMilliseconds);
|
|
472
|
+
}
|
|
473
|
+
// Delete the storage file and null the DB column.
|
|
474
|
+
try {
|
|
475
|
+
const provider = await buildStorageProvider(c.requestedFromTenantId);
|
|
476
|
+
await provider.delete(c.downloadStorageKey);
|
|
477
|
+
}
|
|
478
|
+
catch {
|
|
479
|
+
// best-effort; if the storage delete fails, retry on the next pass
|
|
454
480
|
continue;
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
catch {
|
|
464
|
-
// best-effort; wenn Storage-Delete failed, retry beim naechsten Pass
|
|
465
|
-
continue;
|
|
481
|
+
}
|
|
482
|
+
const result = await crud.update({
|
|
483
|
+
id: c.id,
|
|
484
|
+
version: c.version,
|
|
485
|
+
changes: { downloadStorageKey: null },
|
|
486
|
+
}, createSystemUser(c.requestedFromTenantId), systemTenantDb(db, c.requestedFromTenantId));
|
|
487
|
+
if (result.isSuccess)
|
|
488
|
+
cleaned.push(c.id);
|
|
466
489
|
}
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
cleaned.push(c.id);
|
|
490
|
+
passCompleted = true;
|
|
491
|
+
}
|
|
492
|
+
finally {
|
|
493
|
+
reportOldestOverdueAge(metrics, overdueDoneCleanupAfterMs, cleaned, now);
|
|
494
|
+
if (passCompleted)
|
|
495
|
+
reportCleanupLastRun(metrics, now);
|
|
474
496
|
}
|
|
475
|
-
reportOldestOverdueAge(metrics, overdueDoneCleanupAfterMs, cleaned, now);
|
|
476
497
|
return cleaned;
|
|
477
498
|
}
|
|
478
|
-
// Seconds since the longest-overdue
|
|
479
|
-
//
|
|
499
|
+
// Seconds since the longest-overdue bundle (Done: expiresAt+grace passed;
|
|
500
|
+
// Failed: completedAt ?? startedAt) should have been deleted but still isn't — 0 when the pass
|
|
480
501
|
// cleaned everything that was due, or nothing was due.
|
|
481
502
|
function oldestOverdueAgeSeconds(overdueDoneCleanupAfterMs, cleanedIds, now) {
|
|
482
503
|
const cleaned = new Set(cleanedIds);
|
|
@@ -505,6 +526,18 @@ function reportOldestOverdueAge(metrics, overdueDoneCleanupAfterMs, cleanedIds,
|
|
|
505
526
|
console.warn(`[user-data-rights:run-export-jobs] metrics.set(${EXPORT_CLEANUP_BACKLOG_AGE_METRIC}) failed err=${err instanceof Error ? err.message : String(err)}`);
|
|
506
527
|
}
|
|
507
528
|
}
|
|
529
|
+
function reportCleanupLastRun(metrics, now) {
|
|
530
|
+
// skip: no meter configured means there is nothing to report.
|
|
531
|
+
if (!metrics)
|
|
532
|
+
return;
|
|
533
|
+
try {
|
|
534
|
+
metrics.set(EXPORT_CLEANUP_LAST_RUN_TIMESTAMP_METRIC, now.epochMilliseconds / 1000);
|
|
535
|
+
}
|
|
536
|
+
catch (err) {
|
|
537
|
+
// biome-ignore lint/suspicious/noConsole: operator-visibility for observability-emit-failure
|
|
538
|
+
console.warn(`[user-data-rights:run-export-jobs] metrics.set(${EXPORT_CLEANUP_LAST_RUN_TIMESTAMP_METRIC}) failed err=${err instanceof Error ? err.message : String(err)}`);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
508
541
|
// Wrapper: crud.update braucht TenantDb. ExportJob ist tenant-agnostisch
|
|
509
542
|
// (1 Job pro userId), aber der event-store-Stream-Lookup erwartet einen
|
|
510
543
|
// Tenant-Context. system-mode TenantDb bypassed den auto-tenant-filter
|
|
@@ -35,7 +35,7 @@ import { fetchOne, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
|
35
35
|
import { configuredPiiSubjectKms, subjectIdToKey, } from "@cosmicdrift/kumiko-framework/crypto";
|
|
36
36
|
import { createTenantDb, entityEventName, executeRawQuery, nullBlindIndexesForSubject, } from "@cosmicdrift/kumiko-framework/db";
|
|
37
37
|
import { EXT_USER_DATA, EXT_USER_DATA_ORDER, extensionUsageEscapeHatchReason, } from "@cosmicdrift/kumiko-framework/engine";
|
|
38
|
-
import { createEscapeHatchReporter } from "@cosmicdrift/kumiko-framework/pipeline";
|
|
38
|
+
import { createEscapeHatchReporter, UNATTRIBUTED_ACTOR, } from "@cosmicdrift/kumiko-framework/pipeline";
|
|
39
39
|
import { purgeSearchDocumentsForSubject, } from "@cosmicdrift/kumiko-framework/search";
|
|
40
40
|
import { resolveRetentionPolicyForTenant, resolveTenantRetentionPreset, } from "../data-retention/index.js";
|
|
41
41
|
import { decryptStoredPii, runInSubTransaction } from "../shared/index.js";
|
|
@@ -47,7 +47,7 @@ function buildHookDb(db, tenantId, entry, args) {
|
|
|
47
47
|
const report = createEscapeHatchReporter({
|
|
48
48
|
handler: `${EXT_USER_DATA}:${entry.entityName}`,
|
|
49
49
|
tenantId,
|
|
50
|
-
actor: args.actor ??
|
|
50
|
+
actor: args.actor ?? UNATTRIBUTED_ACTOR,
|
|
51
51
|
sink: args.escapeHatchAuditSink,
|
|
52
52
|
});
|
|
53
53
|
return createTenantDb(db, tenantId, "tenant", undefined, undefined, undefined, {
|
|
@@ -23,14 +23,14 @@ import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
|
23
23
|
import { configuredPiiSubjectKms, decryptPiiFieldValues, isPiiCiphertext, } from "@cosmicdrift/kumiko-framework/crypto";
|
|
24
24
|
import { collectEncryptedFieldNames, configuredEntityFieldEncryption, createTenantDb, decryptEntityFieldValues, } from "@cosmicdrift/kumiko-framework/db";
|
|
25
25
|
import { EXT_USER_DATA, extensionUsageEscapeHatchReason, maskWriteOnlyFields, } from "@cosmicdrift/kumiko-framework/engine";
|
|
26
|
-
import { createEscapeHatchReporter } from "@cosmicdrift/kumiko-framework/pipeline";
|
|
26
|
+
import { createEscapeHatchReporter, UNATTRIBUTED_ACTOR, } from "@cosmicdrift/kumiko-framework/pipeline";
|
|
27
27
|
import { tenantMembershipsTable } from "../tenant/index.js";
|
|
28
28
|
import { buildFileRefZipPath } from "./zip-path.js";
|
|
29
29
|
function buildHookDb(db, tenantId, entry, args) {
|
|
30
30
|
const report = createEscapeHatchReporter({
|
|
31
31
|
handler: `${EXT_USER_DATA}:${entry.entityName}`,
|
|
32
32
|
tenantId,
|
|
33
|
-
actor: args.actor ??
|
|
33
|
+
actor: args.actor ?? UNATTRIBUTED_ACTOR,
|
|
34
34
|
sink: args.escapeHatchAuditSink,
|
|
35
35
|
});
|
|
36
36
|
return createTenantDb(db, tenantId, "tenant", undefined, undefined, undefined, {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createEventStoreExecutor } from "@cosmicdrift/kumiko-framework/db";
|
|
2
2
|
import { deleteStoredFileAndDerivatives } from "@cosmicdrift/kumiko-framework/derivatives";
|
|
3
3
|
import { createSystemUser, } from "@cosmicdrift/kumiko-framework/engine";
|
|
4
|
+
import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
|
|
4
5
|
import { fileRefEntity, fileRefsTable } from "@cosmicdrift/kumiko-framework/files";
|
|
5
6
|
import { assertErased } from "../../shared/index.js";
|
|
6
7
|
const ERROR_CONTEXT = "user-data-rights-defaults:fileRef";
|
|
@@ -222,7 +223,9 @@ export const fileRefDeleteHook = async (ctx, strategy) => {
|
|
|
222
223
|
if (storageProvider) {
|
|
223
224
|
const failedKeys = await deleteBinaries(personalRows, storageProvider);
|
|
224
225
|
if (failedKeys.length > 0) {
|
|
225
|
-
throw new
|
|
226
|
+
throw new InternalError({
|
|
227
|
+
message: `[user-data-rights-defaults:fileRef] ${failedKeys.length} binary delete(s) failed — aborting forget so the rows are retried next run (keys: ${failedKeys.join(", ")})`,
|
|
228
|
+
});
|
|
226
229
|
}
|
|
227
230
|
}
|
|
228
231
|
else {
|
|
@@ -42,6 +42,9 @@ export function createUserProfileFeature() {
|
|
|
42
42
|
"profile.email.title": { en: "Email address" },
|
|
43
43
|
"profile.password.title": { en: "Password" },
|
|
44
44
|
"profile.danger.title": { en: "Delete account" },
|
|
45
|
+
"profile.danger.explainer": {
|
|
46
|
+
en: "Your account will be permanently deleted after a grace period. Until then you can cancel the deletion at any time.",
|
|
47
|
+
},
|
|
45
48
|
"profile.danger.gracePeriodEnd": { en: "Deletion date" },
|
|
46
49
|
"profile.danger.delete": { en: "Delete account" },
|
|
47
50
|
"profile.danger.dialogDescription": {
|
|
@@ -94,7 +97,7 @@ export function createUserProfileFeature() {
|
|
|
94
97
|
},
|
|
95
98
|
{
|
|
96
99
|
title: i18nKey("profile.danger.title"),
|
|
97
|
-
description: "profile.danger.explainer",
|
|
100
|
+
description: i18nKey("profile.danger.explainer"),
|
|
98
101
|
// gracePeriodEnd is only meaningful once a deletion is actually
|
|
99
102
|
// pending — hide it instead of showing an empty date when status
|
|
100
103
|
// isn't deletionRequested (same rule as privacy-center).
|
|
@@ -123,6 +126,8 @@ export function createUserProfileFeature() {
|
|
|
123
126
|
handler: UserDataRightsHandlers.cancelDeletion,
|
|
124
127
|
visible: { field: "status", eq: "deletionRequested" },
|
|
125
128
|
style: "secondary",
|
|
129
|
+
// Declared: the id's last segment ("deletion") would otherwise resolve to the destructive trash icon.
|
|
130
|
+
icon: "x",
|
|
126
131
|
},
|
|
127
132
|
],
|
|
128
133
|
});
|
|
@@ -33,6 +33,8 @@ export const changeEmailWrite = defineWriteHandler({
|
|
|
33
33
|
"email uniqueness the same way, and writes the new email via ctx.writeAs(SYSTEM, " +
|
|
34
34
|
"user:update) — field-access on passwordHash/email is privileged-only.",
|
|
35
35
|
},
|
|
36
|
+
// Payload carries the current password.
|
|
37
|
+
agent: { expose: false },
|
|
36
38
|
description: "Replaces the signed-in caller's own email address after re-checking their current password, refusing an unchanged or already-taken address and clearing the verified flag so the app can re-run email verification.",
|
|
37
39
|
handler: async (event, ctx) => {
|
|
38
40
|
const systemUser = createSystemUser(event.user.tenantId);
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// VersionConflictError-checked claim (ctx.tryAppendEvent on
|
|
7
|
-
// WORKFLOW_RESUMED) — a second dispatch for the same row just loses
|
|
8
|
-
// that race and no-ops, same as the sample this was adapted from.
|
|
2
|
+
// Dedup happens solely via resume-run's VersionConflictError-checked claim
|
|
3
|
+
// (ctx.tryAppendEvent on WORKFLOW_RESUMED) — a second dispatch for the same
|
|
4
|
+
// row loses that race and no-ops. No row locks: in an autocommit statement
|
|
5
|
+
// they would be released right after the SELECT and guard nothing.
|
|
9
6
|
export async function selectDueWorkflowRunPending(db, tenantId) {
|
|
10
|
-
// kumiko-lint-ignore raw-sql tenant-scoped due-row pickup
|
|
11
|
-
return (await asRawClient(db).unsafe(`SELECT run_id, step_index FROM workflow_run_pending WHERE tenant_id = $1 AND wake_at < now()
|
|
7
|
+
// kumiko-lint-ignore raw-sql tenant-scoped due-row pickup
|
|
8
|
+
return (await asRawClient(db).unsafe(`SELECT run_id, step_index FROM workflow_run_pending WHERE tenant_id = $1 AND wake_at < now()`, [tenantId]));
|
|
12
9
|
}
|
|
@@ -25,21 +25,41 @@
|
|
|
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";
|
|
27
27
|
import { evaluateEventMatch } from "@cosmicdrift/kumiko-framework/engine";
|
|
28
|
+
import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
|
|
29
|
+
import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
|
|
28
30
|
import { workflowRunPendingTable } from "./tables.js";
|
|
31
|
+
const log = createFallbackLogger("workflow-runner");
|
|
32
|
+
// A row whose persisted matchExpr cannot be evaluated (unknown version from a
|
|
33
|
+
// rolling deploy, corrupt AST) must not abort the whole projection: that would
|
|
34
|
+
// block every other waiting run of this event type on the same event forever.
|
|
35
|
+
function rowMatchesEvent(row, eventType, payload) {
|
|
36
|
+
if (!row.matchExpr)
|
|
37
|
+
return true;
|
|
38
|
+
try {
|
|
39
|
+
return evaluateEventMatch(
|
|
40
|
+
// @cast-boundary engine-payload — matchExpr round-trips through
|
|
41
|
+
// jsonb as the EventMatch AST waitForEvent's `match` resolver
|
|
42
|
+
// persisted (see steps/wait-for-event.ts); evaluateEventMatch
|
|
43
|
+
// rejects a malformed shape itself.
|
|
44
|
+
row.matchExpr, payload);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
log.error("workflow wakeup skipped a pending row with an unevaluable matchExpr", {
|
|
48
|
+
runId: row.runId,
|
|
49
|
+
stepIndex: row.stepIndex,
|
|
50
|
+
eventType,
|
|
51
|
+
error: error instanceof Error ? error.message : String(error),
|
|
52
|
+
});
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
29
56
|
const wakeupApply = async (event, tx) => {
|
|
30
57
|
const rows = await selectMany(tx, workflowRunPendingTable, {
|
|
31
58
|
tenantId: event.tenantId,
|
|
32
59
|
waitEventType: event.type,
|
|
33
60
|
});
|
|
34
61
|
for (const row of rows) {
|
|
35
|
-
const matches = row.
|
|
36
|
-
? evaluateEventMatch(
|
|
37
|
-
// @cast-boundary engine-payload — matchExpr round-trips through
|
|
38
|
-
// jsonb as the EventMatch AST waitForEvent's `match` resolver
|
|
39
|
-
// persisted (see steps/wait-for-event.ts); never an unchecked
|
|
40
|
-
// external value.
|
|
41
|
-
row.matchExpr, event.payload)
|
|
42
|
-
: true;
|
|
62
|
+
const matches = rowMatchesEvent(row, event.type, event.payload);
|
|
43
63
|
// skip: this row's matchExpr rejected the event — not this row's
|
|
44
64
|
// resume, leave wakeAt/triggerEventType untouched.
|
|
45
65
|
if (!matches)
|
|
@@ -47,7 +67,7 @@ const wakeupApply = async (event, tx) => {
|
|
|
47
67
|
await updateMany(tx, workflowRunPendingTable, {
|
|
48
68
|
triggerEventType: event.type,
|
|
49
69
|
triggerPayload: event.payload,
|
|
50
|
-
wakeAt:
|
|
70
|
+
wakeAt: getTemporal().Now.instant().toString(),
|
|
51
71
|
}, { tenantId: row.tenantId, runId: row.runId, stepIndex: row.stepIndex });
|
|
52
72
|
}
|
|
53
73
|
};
|
|
@@ -36,7 +36,7 @@ import { workflowRunPendingTable } from "../tables.js";
|
|
|
36
36
|
import { getWorkflow } from "../workflow-registry.js";
|
|
37
37
|
const log = createFallbackLogger("workflow-runner");
|
|
38
38
|
const resumeRunSchema = z.object({
|
|
39
|
-
runId: z.string().
|
|
39
|
+
runId: z.string().uuid(),
|
|
40
40
|
stepIndex: z.number().int().nonnegative(),
|
|
41
41
|
});
|
|
42
42
|
async function appendRunFailed(ctx, runId, failedPayload) {
|
|
@@ -170,16 +170,19 @@ export const resumeRunHandler = {
|
|
|
170
170
|
return { isSuccess: true, data: { outcome: "already-resumed" } };
|
|
171
171
|
}
|
|
172
172
|
const { workflowName } = pending;
|
|
173
|
+
const runEvents = await ctx.loadAggregate(runId);
|
|
174
|
+
// Before the workflow resolution: a racing tick on an already-settled run
|
|
175
|
+
// must not append run-failed after run-completed when the definition has
|
|
176
|
+
// since changed or been removed.
|
|
177
|
+
if (isRunAlreadySettled(runEvents, stepIndex, pending.retryAttempt ?? undefined)) {
|
|
178
|
+
return { isSuccess: true, data: { outcome: "already-resumed" } };
|
|
179
|
+
}
|
|
173
180
|
const resolved = resolveRunnableWorkflow(workflowName, runId, stepIndex, pending.definitionFingerprint);
|
|
174
181
|
if (resolved.failure !== null) {
|
|
175
182
|
await appendRunFailed(ctx, runId, resolved.failure);
|
|
176
183
|
return { isSuccess: true, data: { outcome: "failed" } };
|
|
177
184
|
}
|
|
178
185
|
const workflow = resolved.workflow;
|
|
179
|
-
const runEvents = await ctx.loadAggregate(runId);
|
|
180
|
-
if (isRunAlreadySettled(runEvents, stepIndex, pending.retryAttempt ?? undefined)) {
|
|
181
|
-
return { isSuccess: true, data: { outcome: "already-resumed" } };
|
|
182
|
-
}
|
|
183
186
|
const claim = await ctx.tryAppendEvent({
|
|
184
187
|
aggregateId: runId,
|
|
185
188
|
aggregateType: WORKFLOW_AGGREGATE_TYPE,
|
|
@@ -11,10 +11,17 @@
|
|
|
11
11
|
// No resume logic here — framework#2513 Phase 2 owns the job that reads
|
|
12
12
|
// this table and dispatches the actual resume. This projection only writes
|
|
13
13
|
// the rows.
|
|
14
|
-
import { deleteMany, upsertOnConflict } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
14
|
+
import { deleteMany, fetchOne, upsertOnConflict } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
15
15
|
import { WORKFLOW_RESUMED_TYPE, WORKFLOW_RETRY_SCHEDULED_TYPE, WORKFLOW_RUN_COMPLETED_TYPE, WORKFLOW_RUN_FAILED_TYPE, WORKFLOW_WAITING_FOR_EVENT_TYPE, WORKFLOW_WAITING_TYPE, } from "@cosmicdrift/kumiko-framework/engine";
|
|
16
16
|
import { workflowRunPendingTable } from "./tables.js";
|
|
17
17
|
async function upsertPending(tx, row) {
|
|
18
|
+
// At-least-once redelivery of a suspension event after the event-subscriber
|
|
19
|
+
// already matched a trigger onto this row must not reset wakeAt/trigger —
|
|
20
|
+
// the run would sleep until timeout and lose the matched payload.
|
|
21
|
+
const existing = await fetchOne(tx, workflowRunPendingTable, { tenantId: row.tenantId, runId: row.runId, stepIndex: row.stepIndex });
|
|
22
|
+
// skip: row already carries a trigger event type, keep it on redelivery
|
|
23
|
+
if (existing?.triggerEventType != null)
|
|
24
|
+
return;
|
|
18
25
|
await upsertOnConflict(tx, workflowRunPendingTable, {
|
|
19
26
|
...row,
|
|
20
27
|
// Phase 3's event-subscriber writes these when the awaited event
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-bundled-features",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.335.0",
|
|
4
4
|
"description": "Built-in features — tenant, user, auth, delivery. The stuff you'd rewrite anyway, already typed.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -483,18 +483,20 @@
|
|
|
483
483
|
}
|
|
484
484
|
},
|
|
485
485
|
"dependencies": {
|
|
486
|
-
"@cosmicdrift/kumiko-dispatcher-live": "0.
|
|
487
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
488
|
-
"@cosmicdrift/kumiko-headless": "0.
|
|
489
|
-
"@cosmicdrift/kumiko-renderer": "0.
|
|
490
|
-
"@cosmicdrift/kumiko-renderer-web": "0.
|
|
491
|
-
"@cosmicdrift/kumiko-types": "0.
|
|
486
|
+
"@cosmicdrift/kumiko-dispatcher-live": "0.335.0",
|
|
487
|
+
"@cosmicdrift/kumiko-framework": "0.335.0",
|
|
488
|
+
"@cosmicdrift/kumiko-headless": "0.335.0",
|
|
489
|
+
"@cosmicdrift/kumiko-renderer": "0.335.0",
|
|
490
|
+
"@cosmicdrift/kumiko-renderer-web": "0.335.0",
|
|
491
|
+
"@cosmicdrift/kumiko-types": "0.335.0",
|
|
492
492
|
"@mollie/api-client": "^4.5.0",
|
|
493
493
|
"@node-rs/argon2": "^2.0.2",
|
|
494
494
|
"@types/mailparser": "^3.4.6",
|
|
495
495
|
"@types/qrcode": "^1.5.5",
|
|
496
496
|
"clsx": "^2.1.1",
|
|
497
|
+
"hono": "^4.13.1",
|
|
497
498
|
"imapflow": "^1.7.8",
|
|
499
|
+
"ioredis": "^5.10.1",
|
|
498
500
|
"jose": "^6.2.3",
|
|
499
501
|
"lucide-react": "^1.14.0",
|
|
500
502
|
"mailparser": "^3.9.32",
|
|
@@ -983,8 +985,8 @@
|
|
|
983
985
|
],
|
|
984
986
|
"devDependencies": {
|
|
985
987
|
"@testing-library/user-event": "^14.6.1",
|
|
986
|
-
"@cosmicdrift/kumiko-locale-de": "0.
|
|
987
|
-
"@cosmicdrift/kumiko-locale-es": "0.
|
|
988
|
+
"@cosmicdrift/kumiko-locale-de": "0.335.0",
|
|
989
|
+
"@cosmicdrift/kumiko-locale-es": "0.335.0",
|
|
988
990
|
"jsqr": "^1.4.0"
|
|
989
991
|
}
|
|
990
992
|
}
|
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.335.0",
|
|
4
|
+
"type": "breaking",
|
|
5
|
+
"title": "Manifest rejects unknown denyQns, drops navs under hidden parents; doc lint flags blank and exposed-undescribed handlers",
|
|
6
|
+
"detail": "Agent tools: `buildAgentManifest` now throws when `denyQns` names a handler that is not registered, so a typo can no longer leave a handler exposed. Navs whose parent is hidden from the role are dropped from the manifest, and screens reachable only through them are no longer listed. The agent doc lint reports handlers that are exposed without a description and treats a blank description as a gap.",
|
|
7
|
+
"migration": "`buildAgentManifest` now throws when `denyQns` names a handler that is not registered; remove stale or misspelled entries from your `denyQns` lists."
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"version": "0.335.0",
|
|
11
|
+
"type": "fix",
|
|
12
|
+
"title": "Agent doc lint reports exposed handlers with a non-convertible input schema"
|
|
13
|
+
},
|
|
2
14
|
{
|
|
3
15
|
"version": "0.313.0",
|
|
4
16
|
"type": "fix",
|
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.335.0",
|
|
4
|
+
"type": "fix",
|
|
5
|
+
"title": "Credential-carrying auth handlers are hidden from the agent catalog; additionalAssignableRoles rejects framework-ranked roles",
|
|
6
|
+
"detail": "The credential-carrying handlers `auth-email-password:login`, `change-password`, `user-profile:change-email`, `auth-mfa:verify` and `auth-mfa:enable-confirm-preauth` are now opted out of the agent tool catalog with `agent: { expose: false }`, so passwords, TOTP codes and setup tokens no longer reach an LLM transcript. `invite.additionalAssignableRoles` now throws at construction when it lists a framework-ranked role such as `TenantAdmin`, instead of silently disabling the elevation guard for it."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"version": "0.335.0",
|
|
10
|
+
"type": "fix",
|
|
11
|
+
"title": "Invite accept handles MFA responses, bootstrap error screen offers Sign out",
|
|
12
|
+
"detail": "`InviteAcceptScreen` no longer redirects to a tenant URL when the accept route answers with an MFA challenge or setup requirement (no session cookie was minted); it sends the user to `loginHref` instead. `SessionBootstrapErrorScreen` accepts an optional `onSignOut` and the auth gate wires it, so a permanent bootstrap failure no longer traps the user behind \"Try again\" (new i18n key `auth.sessionBootstrap.signOut`). Cap bookings back off with a small random delay between version-conflict retries."
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"version": "0.335.0",
|
|
16
|
+
"type": "fix",
|
|
17
|
+
"title": "Ended session leads back to the login screen",
|
|
18
|
+
"detail": "When the server ends a session (revoked, expired), the app now shows the login screen with a short hint instead of a raw error banner. After signing in again the user lands on the same screen as before."
|
|
19
|
+
},
|
|
2
20
|
{
|
|
3
21
|
"version": "0.328.1",
|
|
4
22
|
"type": "fix",
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.335.0",
|
|
4
|
+
"type": "fix",
|
|
5
|
+
"title": "MFA web client shares the login response mapper",
|
|
6
|
+
"detail": "The MFA web client reuses the shared `toLoginResponse` helper (now exported from the auth-email-password web barrel) for `landingPath` handling, so login, MFA verify and MFA setup confirm map the response identically."
|
|
7
|
+
},
|
|
2
8
|
{
|
|
3
9
|
"version": "0.329.0",
|
|
4
10
|
"type": "improvement",
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.335.0",
|
|
4
|
+
"type": "breaking",
|
|
5
|
+
"title": "Unregistered catalog.providerName fails unconfigured; switch-plan checks provider mismatch before lookup",
|
|
6
|
+
"detail": "`billing-plans` now fails with an `unconfigured` error when `catalog.providerName` names a provider that is not registered, instead of rendering billing as disabled. Billing renders as disabled only when no `providerName` is set and no provider is mounted. `switch-plan` answers a subscription on an unmounted provider with the `providerMismatch` conflict instead of a 500.",
|
|
7
|
+
"migration": "If `catalog.providerName` names a provider that is not mounted, billing-plans now fails with `unconfigured` instead of rendering billing as disabled; mount the provider or remove `providerName`."
|
|
8
|
+
},
|
|
2
9
|
{
|
|
3
10
|
"version": "0.330.0",
|
|
4
11
|
"type": "fix",
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.335.0",
|
|
4
|
+
"type": "breaking",
|
|
5
|
+
"title": "Cap helpers validate periodStartIso and amount, surface failed soft-warn writes; German column labels",
|
|
6
|
+
"detail": "`bookCapUsage` and `markCapSoftWarned` now reject a `periodStartIso` that is not an ISO instant with a validation error instead of silently forking a counter row or throwing a `RangeError`. `enforceCap` rejects a non-integer or non-positive `amount` instead of letting it bypass the cap. `enforceCapAndMaybeNotify` surfaces a failed soft-warn write instead of re-sending the notification on the next call. The cap-counter list now has German column labels.",
|
|
7
|
+
"migration": "`bookCapUsage` and `markCapSoftWarned` now reject a `periodStartIso` that is not a full ISO instant, including date-only strings like `2026-10-01`; pass e.g. `2026-10-01T00:00:00.000Z`."
|
|
8
|
+
},
|
|
2
9
|
{
|
|
3
10
|
"version": "0.317.0",
|
|
4
11
|
"type": "fix",
|
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.335.0",
|
|
4
|
+
"type": "fix",
|
|
5
|
+
"title": "tenant-caps:list validates cursor and filters, parallel usage reads; cap-counter list not searchable",
|
|
6
|
+
"detail": "`tenant-caps:list` now rejects a malformed pagination cursor and unsupported or repeated filters with a validation error instead of returning a wrong page or silently ignoring the filter, and loads per-tenant usage in parallel. The `cap-counter` operator list is no longer searchable, because search resolved against the caller's own tenant instead of all tenants."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"version": "0.335.0",
|
|
10
|
+
"type": "fix",
|
|
11
|
+
"title": "Unlimited caps show a labelled usage count",
|
|
12
|
+
"detail": "The cap usage bar labels an unlimited cap as \"<used> · unlimited\" through the new `cap-overview.unlimited` key instead of a bare number. The German and Spanish catalogs ship the translation."
|
|
13
|
+
},
|
|
2
14
|
{
|
|
3
15
|
"version": "0.332.0",
|
|
4
16
|
"type": "fix",
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.335.0",
|
|
4
|
+
"type": "fix",
|
|
5
|
+
"title": "Explicit List-Unsubscribe headers replace the automatic pair case-insensitively",
|
|
6
|
+
"detail": "The email channel now matches explicit `data.headers` case-insensitively against the automatic unsubscribe headers. Overriding `List-Unsubscribe` or `List-Unsubscribe-Post` in any spelling drops the whole automatic pair, so a mail client no longer sends a one-click POST to a URL that cannot handle it."
|
|
7
|
+
},
|
|
2
8
|
{
|
|
3
9
|
"version": "0.321.0",
|
|
4
10
|
"type": "improvement",
|
package/src/config/changes.json
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.335.0",
|
|
4
|
+
"type": "breaking",
|
|
5
|
+
"title": "config:write:set masks the echoed value for encrypted-at-rest keys; validation errors no longer include the value",
|
|
6
|
+
"migration": "A caller that reads `data.value` from a `config:write:set` result for a key with `encrypted: true` or `backing: \"secrets\"` now gets the mask. Use the value it sent instead. Tests that match the `value` param of a validation error on such a key need to drop that expectation."
|
|
7
|
+
},
|
|
2
8
|
{
|
|
3
9
|
"version": "0.326.0",
|
|
4
10
|
"type": "breaking",
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.335.0",
|
|
4
|
+
"type": "fix",
|
|
5
|
+
"title": "forget-denied audit events record the specific tenant-gate denial reason",
|
|
6
|
+
"detail": "The `forget-denied` audit event now records the specific tenant-gate reason (for example `target_tenant_not_admin_tenant`) instead of the generic `access_denied`. The framework exports `requireEntityTableMeta` from `bun-db`."
|
|
7
|
+
},
|
|
2
8
|
{
|
|
3
9
|
"version": "0.332.0",
|
|
4
10
|
"type": "fix",
|
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.335.0",
|
|
4
|
+
"type": "fix",
|
|
5
|
+
"title": "Retention hardDelete keeps cross-entity shared files and isolates storage errors per row",
|
|
6
|
+
"detail": "Retention hardDelete no longer deletes a fileRef that a live row of another entity type still references in a file/image field (relevant for unbound uploads). A storage error while deleting a row's files (for example a missing list permission) now skips only that row with `file_delete_failed` instead of aborting the whole tenant's cleanup run."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"version": "0.335.0",
|
|
10
|
+
"type": "fix",
|
|
11
|
+
"title": "Retention hardDelete pages past stuck file rows and blockDelete reports personal fields without anonymize",
|
|
12
|
+
"detail": "Retention hardDelete on entities with file fields now pages by id, so rows stuck on a storage failure or a missing file storage no longer starve the rows behind them. A `blockDelete` entity with a personal field but no `anonymize` function is reported as `missing_anonymize_fields` again after the hold expires; only entities without any anonymizable subject field stay silent. The framework exports `entityHasAnonymizableSubjectField` for this check."
|
|
13
|
+
},
|
|
2
14
|
{
|
|
3
15
|
"version": "0.330.1",
|
|
4
16
|
"type": "fix",
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.335.0",
|
|
4
|
+
"type": "breaking",
|
|
5
|
+
"title": "Channel plugin guard validates mode and render",
|
|
6
|
+
"detail": "Stricter input guards. A delivery channel plugin must declare `mode` as `inline` or `queued` and a function `render`, otherwise boot fails instead of silently dispatching inline. The derivatives-sharp overlay rejects a non-finite `marginPct` before it reaches sharp. A document-ingest provider can no longer register under the reserved name `unknown`, which marks upcast legacy ingest events. The unsubscribe POST route also accepts a JSON body.",
|
|
7
|
+
"migration": "A delivery channel plugin must now declare `mode` as `\"inline\"` or `\"queued\"` and a function `render`; boot fails otherwise instead of dispatching inline."
|
|
8
|
+
},
|
|
2
9
|
{
|
|
3
10
|
"version": "0.330.0",
|
|
4
11
|
"type": "improvement",
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.335.0",
|
|
4
|
+
"type": "fix",
|
|
5
|
+
"title": "Overlay and blur intermediates are lossless and QR overlays render crisp at their final size",
|
|
6
|
+
"detail": "The sharp renderer buffers intermediates losslessly before overlays and blur regions, so a JPEG source no longer gains an extra compression generation. QR overlays are generated at their final size and checked against the smaller of output width and height before generation, so a flat output can no longer cache an unscannable QR."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"version": "0.335.0",
|
|
10
|
+
"type": "fix",
|
|
11
|
+
"title": "Overlay marginPct must be finite"
|
|
12
|
+
},
|
|
2
13
|
{
|
|
3
14
|
"version": "0.287.0",
|
|
4
15
|
"type": "improvement",
|
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.335.0",
|
|
4
|
+
"type": "fix",
|
|
5
|
+
"title": "Writing a document extract is idempotent per fileRef",
|
|
6
|
+
"detail": "`writeDocumentExtractForLiveFileRef` now returns `{ kind: \"skipped\", reason: \"already_extracted\" }` when the fileRef already has a documentExtract, so a redelivered `documentIngest.requested` or a delete-restore race no longer creates a second extract. Providers need no existence check of their own; code that switches exhaustively on `reason` must handle the new value."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"version": "0.335.0",
|
|
10
|
+
"type": "breaking",
|
|
11
|
+
"title": "Provider name \"unknown\" is reserved for upcast legacy events",
|
|
12
|
+
"migration": "Registering a document-ingest provider under the name `unknown` now throws; rename any provider that uses it."
|
|
13
|
+
},
|
|
2
14
|
{
|
|
3
15
|
"version": "0.312.0",
|
|
4
16
|
"type": "breaking",
|