@cosmicdrift/kumiko-samples 0.389.0 → 0.390.1
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/package.json +1 -1
- package/packages/bundled-features/package.json +27 -15
- package/packages/bundled-features/src/audit/feature.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/auth-user-row.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/bootstrap.ts +13 -1
- package/packages/bundled-features/src/auth-email-password/changes.json +6 -0
- package/packages/bundled-features/src/auth-email-password/feature.ts +2 -2
- package/packages/bundled-features/src/auth-email-password/handlers/change-password.write.ts +7 -7
- package/packages/bundled-features/src/auth-email-password/handlers/confirm-account-unlock.write.ts +3 -3
- package/packages/bundled-features/src/auth-email-password/handlers/confirm-token-flow.ts +5 -5
- package/packages/bundled-features/src/auth-email-password/handlers/invite-accept-with-login.write.ts +9 -7
- package/packages/bundled-features/src/auth-email-password/handlers/invite-accept.write.ts +11 -7
- package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +4 -4
- package/packages/bundled-features/src/auth-email-password/handlers/invite-info.query.ts +2 -2
- package/packages/bundled-features/src/auth-email-password/handlers/invite-signup-complete.write.ts +9 -7
- package/packages/bundled-features/src/auth-email-password/handlers/login.write.ts +23 -16
- package/packages/bundled-features/src/auth-email-password/handlers/reset-password.write.ts +3 -2
- package/packages/bundled-features/src/auth-email-password/handlers/signup-confirm.write.ts +12 -12
- package/packages/bundled-features/src/auth-email-password/handlers/signup-request.write.ts +12 -8
- package/packages/bundled-features/src/auth-email-password/handlers/switch-tenant-mfa-gate.write.ts +4 -2
- package/packages/bundled-features/src/auth-email-password/handlers/token-request-handler.ts +2 -2
- package/packages/bundled-features/src/auth-email-password/handlers/verify-email.write.ts +3 -2
- package/packages/bundled-features/src/auth-email-password/seeding.ts +14 -9
- package/packages/bundled-features/src/auth-foundation/feature.ts +13 -4
- package/packages/bundled-features/src/auth-mfa/handlers/disable.write.ts +5 -5
- package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +24 -14
- package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm.write.ts +9 -3
- package/packages/bundled-features/src/auth-mfa/handlers/enable-start-preauth.write.ts +6 -1
- package/packages/bundled-features/src/auth-mfa/handlers/enable-start.write.ts +3 -1
- package/packages/bundled-features/src/auth-mfa/handlers/reencrypt.job.ts +14 -9
- package/packages/bundled-features/src/auth-mfa/handlers/regenerate-recovery.write.ts +5 -4
- package/packages/bundled-features/src/auth-mfa/handlers/verify.write.ts +25 -14
- package/packages/bundled-features/src/auth-mfa/mfa-code-verifier.ts +10 -4
- package/packages/bundled-features/src/auth-mfa/mfa-status-checker.ts +9 -3
- package/packages/bundled-features/src/auth-mfa/verify-factor.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/billing-info-query.ts +2 -2
- package/packages/bundled-features/src/billing-foundation/changes.json +6 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/consent-gate.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/consumer-protection/issue-confirmation.ts +5 -5
- package/packages/bundled-features/src/billing-foundation/consumer-protection/register-consent.ts +10 -7
- package/packages/bundled-features/src/billing-foundation/consumer-protection/register-termination.ts +15 -12
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-open-cancels.ts +22 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-provider-lock.ts +4 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-receipt-limit.ts +4 -4
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-record.ts +17 -58
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-request.ts +10 -8
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-revoke.ts +18 -15
- package/packages/bundled-features/src/billing-foundation/events.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/feature.ts +8 -3
- package/packages/bundled-features/src/billing-foundation/get-subscription-for-tenant.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/handlers/process-event.write.ts +5 -5
- package/packages/bundled-features/src/billing-foundation/handlers/process-payment-event.write.ts +3 -3
- package/packages/bundled-features/src/billing-foundation/plan-catalog.ts +2 -2
- package/packages/bundled-features/src/billing-foundation/projection.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/types.ts +1 -1
- package/packages/bundled-features/src/cap-counter/aggregate-id.ts +1 -1
- package/packages/bundled-features/src/cap-counter/book-cap-usage.ts +5 -4
- package/packages/bundled-features/src/cap-counter/cap-reservation.ts +1 -1
- package/packages/bundled-features/src/cap-counter/constants.ts +1 -1
- package/packages/bundled-features/src/cap-counter/feature.ts +2 -2
- package/packages/bundled-features/src/cap-counter/handlers/increment-rolling.write.ts +1 -1
- package/packages/bundled-features/src/cap-counter/reservation-chain.ts +1 -1
- package/packages/bundled-features/src/cap-counter/with-cap-enforcement.ts +4 -4
- package/packages/bundled-features/src/cap-overview/handlers/caps-usage.query.ts +1 -1
- package/packages/bundled-features/src/cap-overview/handlers/tenant-caps-list.query.ts +1 -1
- package/packages/bundled-features/src/compliance-profiles/seeding.ts +12 -7
- package/packages/bundled-features/src/config/feature.ts +6 -6
- package/packages/bundled-features/src/config/handlers/cascade.query.ts +1 -1
- package/packages/bundled-features/src/config/handlers/readiness.query.ts +1 -1
- package/packages/bundled-features/src/config/handlers/reencrypt.job.ts +23 -13
- package/packages/bundled-features/src/config/handlers/reset.write.ts +3 -3
- package/packages/bundled-features/src/config/handlers/selected-extensions.query.ts +1 -1
- package/packages/bundled-features/src/config/handlers/set.write.ts +4 -4
- package/packages/bundled-features/src/config/handlers/values.query.ts +1 -1
- package/packages/bundled-features/src/crypto-shredding/handlers/forget-subject.write.ts +8 -8
- package/packages/bundled-features/src/crypto-shredding/resolve-file-bytes-deleter.ts +3 -3
- package/packages/bundled-features/src/custom-fields/changes.json +6 -0
- package/packages/bundled-features/src/custom-fields/handlers/clear-custom-field.write.ts +1 -1
- package/packages/bundled-features/src/custom-fields/handlers/delete-system-field.write.ts +1 -1
- package/packages/bundled-features/src/custom-fields/handlers/delete-tenant-field.write.ts +1 -1
- package/packages/bundled-features/src/custom-fields/handlers/set-custom-field.write.ts +1 -1
- package/packages/bundled-features/src/custom-fields/index.ts +4 -0
- package/packages/bundled-features/src/data-retention/feature.ts +1 -1
- package/packages/bundled-features/src/data-retention/run-retention-cleanup.ts +12 -2
- package/packages/bundled-features/src/delivery/attempt-log.ts +1 -1
- package/packages/bundled-features/src/delivery/delivery-service.ts +6 -5
- package/packages/bundled-features/src/delivery/feature.ts +2 -2
- package/packages/bundled-features/src/delivery/jobs.ts +11 -11
- package/packages/bundled-features/src/delivery/testing.ts +1 -1
- package/packages/bundled-features/src/document-ingest-foundation/events.ts +1 -1
- package/packages/bundled-features/src/feature-toggles/handlers/registered.query.ts +1 -1
- package/packages/bundled-features/src/feature-toggles/handlers/set.write.ts +1 -1
- package/packages/bundled-features/src/feature-toggles/toggle-sync-signal.ts +1 -1
- package/packages/bundled-features/src/file-derivatives/feature.ts +2 -2
- package/packages/bundled-features/src/file-derivatives/handlers/public-variant-by-file-ref.query.ts +5 -1
- package/packages/bundled-features/src/file-derivatives/handlers/public-variant.query.ts +7 -5
- package/packages/bundled-features/src/file-foundation/feature.ts +1 -1
- package/packages/bundled-features/src/file-foundation/read-file-ref-content.ts +7 -5
- package/packages/bundled-features/src/file-provider-s3/feature.ts +4 -1
- package/packages/bundled-features/src/files-tenant-data/handlers/sweep-orphaned-derivatives.job.ts +8 -8
- package/packages/bundled-features/src/form-draft/feature.ts +1 -1
- package/packages/bundled-features/src/form-draft/handlers/cleanup.job.ts +22 -12
- package/packages/bundled-features/src/form-draft/handlers/discard.write.ts +3 -3
- package/packages/bundled-features/src/form-draft/handlers/get.query.ts +1 -1
- package/packages/bundled-features/src/foundation-shared/config-helpers.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/events.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/feature.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/account-state.ts +2 -2
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/connect-account.write.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/disconnect-account.write.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message.write.ts +10 -10
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/update-account.write.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/projection.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +1 -1
- package/packages/bundled-features/src/inbound-provider-imap/changes.json +6 -0
- package/packages/bundled-features/src/inbound-provider-imap/feature.ts +4 -1
- package/packages/bundled-features/src/jobs/changes.json +6 -0
- package/packages/bundled-features/src/jobs/events.ts +1 -1
- package/packages/bundled-features/src/jobs/handlers/consumer.write.ts +7 -4
- package/packages/bundled-features/src/jobs/handlers/projection-rebuild.job.ts +1 -1
- package/packages/bundled-features/src/jobs/handlers/reindex-entity.job.ts +11 -5
- package/packages/bundled-features/src/jobs/handlers/retention-cleanup.job.ts +1 -1
- package/packages/bundled-features/src/jobs/handlers/retry.write.ts +1 -1
- package/packages/bundled-features/src/jobs/handlers/stale-run-sweep.job.ts +1 -1
- package/packages/bundled-features/src/jobs/handlers/trigger.write.ts +2 -2
- package/packages/bundled-features/src/jobs/job-run-logger.ts +139 -90
- package/packages/bundled-features/src/legal-pages/README.md +1 -1
- package/packages/bundled-features/src/legal-pages/lib/boot-check.ts +12 -8
- package/packages/bundled-features/src/mail-foundation/feature.ts +2 -2
- package/packages/bundled-features/src/mail-transport-smtp/changes.json +6 -0
- package/packages/bundled-features/src/mail-transport-smtp/feature.ts +4 -1
- package/packages/bundled-features/src/managed-pages/seeding.ts +13 -8
- package/packages/bundled-features/src/notes-history/handlers/add-note.write.ts +1 -1
- package/packages/bundled-features/src/personal-access-tokens/feature.ts +1 -1
- package/packages/bundled-features/src/personal-access-tokens/handlers/create.write.ts +2 -2
- package/packages/bundled-features/src/personal-access-tokens/handlers/revoke.write.ts +1 -1
- package/packages/bundled-features/src/rate-limiting/handlers/status.query.ts +2 -2
- package/packages/bundled-features/src/readiness/handlers/status.query.ts +4 -1
- package/packages/bundled-features/src/renderer-foundation/api.ts +3 -3
- package/packages/bundled-features/src/secrets/announce-secret-change.ts +4 -4
- package/packages/bundled-features/src/secrets/feature.ts +9 -9
- package/packages/bundled-features/src/secrets/handlers/delete.write.ts +4 -3
- package/packages/bundled-features/src/secrets/handlers/rotate.job.ts +13 -8
- package/packages/bundled-features/src/secrets/handlers/set.write.ts +4 -3
- package/packages/bundled-features/src/secrets/secrets-context.ts +16 -6
- package/packages/bundled-features/src/secrets/write-gate.ts +1 -1
- package/packages/bundled-features/src/seo/feature.ts +1 -1
- package/packages/bundled-features/src/sessions/changes.json +12 -0
- package/packages/bundled-features/src/sessions/feature.ts +1 -1
- package/packages/bundled-features/src/sessions/handlers/cleanup.job.ts +2 -2
- package/packages/bundled-features/src/sessions/handlers/detail.query.ts +5 -2
- package/packages/bundled-features/src/sessions/handlers/list.query.ts +5 -2
- package/packages/bundled-features/src/sessions/handlers/mine.query.ts +5 -2
- package/packages/bundled-features/src/sessions/handlers/revoke-all-for-user.write.ts +3 -3
- package/packages/bundled-features/src/sessions/handlers/revoke-all-others.write.ts +1 -1
- package/packages/bundled-features/src/sessions/handlers/revoke.write.ts +1 -1
- package/packages/bundled-features/src/sessions/screens.ts +8 -4
- package/packages/bundled-features/src/sessions/session-revoked-event.ts +1 -1
- package/packages/bundled-features/src/sessions/web/session-device-cell.tsx +10 -12
- package/packages/bundled-features/src/sessions/web/user-agent.ts +12 -0
- package/packages/bundled-features/src/shared/index.ts +15 -0
- package/packages/bundled-features/src/shared/purge-files-reaching-subject.ts +12 -2
- package/packages/bundled-features/src/shared/require-for-tenant.ts +7 -5
- package/packages/bundled-features/src/shared/stream-tenant-backfill.ts +201 -0
- package/packages/bundled-features/src/shared/stream-tenant-derived-state.ts +107 -0
- package/packages/bundled-features/src/step-dispatcher/feature.ts +1 -1
- package/packages/bundled-features/src/subscription-dodo/plugin-methods.ts +1 -1
- package/packages/bundled-features/src/subscription-dodo/runtime.ts +7 -7
- package/packages/bundled-features/src/subscription-stripe/plugin-methods.ts +5 -3
- package/packages/bundled-features/src/subscription-stripe/runtime.ts +7 -7
- package/packages/bundled-features/src/template-resolver/api.ts +3 -3
- package/packages/bundled-features/src/template-resolver/changes.json +18 -0
- package/packages/bundled-features/src/template-resolver/handlers/bundle-import.ts +5 -4
- package/packages/bundled-features/src/template-resolver/handlers/copy-locale.write.ts +2 -2
- package/packages/bundled-features/src/template-resolver/handlers/export-bundle.query.ts +1 -1
- package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +2 -2
- package/packages/bundled-features/src/template-resolver/handlers/send-test-shared.ts +1 -1
- package/packages/bundled-features/src/template-resolver/handlers/send-test.write.ts +2 -2
- package/packages/bundled-features/src/template-resolver/handlers/tenant-override-selection.ts +6 -2
- package/packages/bundled-features/src/template-resolver/handlers/tenant-overrides.query.ts +4 -2
- package/packages/bundled-features/src/template-resolver/handlers/usage.query.ts +1 -1
- package/packages/bundled-features/src/template-resolver/seeding.ts +12 -2
- package/packages/bundled-features/src/template-resolver/store-template-resource.ts +18 -13
- package/packages/bundled-features/src/template-resolver/template-resources.ts +4 -4
- package/packages/bundled-features/src/template-resolver/testing.ts +2 -2
- package/packages/bundled-features/src/template-resolver/web/document-insert-fields.tsx +222 -0
- package/packages/bundled-features/src/template-resolver/web/document-insert-model.ts +164 -0
- package/packages/bundled-features/src/template-resolver/web/document-insert-panel.tsx +115 -0
- package/packages/bundled-features/src/template-resolver/web/document-tiptap-bubble.tsx +177 -0
- package/packages/bundled-features/src/template-resolver/web/document-tiptap-content.tsx +38 -0
- package/packages/bundled-features/src/template-resolver/web/document-tiptap-context.ts +119 -0
- package/packages/bundled-features/src/template-resolver/web/document-tiptap-editor.tsx +51 -1098
- package/packages/bundled-features/src/template-resolver/web/document-tiptap-helpers.ts +161 -0
- package/packages/bundled-features/src/template-resolver/web/document-tiptap-layout.tsx +83 -0
- package/packages/bundled-features/src/template-resolver/web/document-tiptap-toolbar-groups.tsx +153 -0
- package/packages/bundled-features/src/template-resolver/web/document-tiptap-toolbar.tsx +182 -0
- package/packages/bundled-features/src/template-resolver/web/editor-handle.ts +18 -0
- package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor.tsx +19 -6
- package/packages/bundled-features/src/template-resolver/web/use-document-tiptap-model.ts +138 -0
- package/packages/bundled-features/src/tenant/changes.json +19 -0
- package/packages/bundled-features/src/tenant/handlers/cancel-invitation.write.ts +1 -1
- package/packages/bundled-features/src/tenant/handlers/memberships.query.ts +1 -1
- package/packages/bundled-features/src/tenant/handlers/remove-member.write.ts +2 -2
- package/packages/bundled-features/src/tenant/handlers/update-member-roles.write.ts +1 -1
- package/packages/bundled-features/src/tenant/index.ts +5 -0
- package/packages/bundled-features/src/tenant/invited-membership.ts +6 -1
- package/packages/bundled-features/src/tenant/schema/tenant.ts +4 -1
- package/packages/bundled-features/src/tenant/seeding.ts +65 -62
- package/packages/bundled-features/src/tenant/stream-tenant-backfill.ts +115 -0
- package/packages/bundled-features/src/tenant/testing.ts +77 -0
- package/packages/bundled-features/src/tenant-handover/events.ts +1 -1
- package/packages/bundled-features/src/tenant-handover/handlers/claim.write.ts +1 -1
- package/packages/bundled-features/src/tenant-lifecycle/feature.ts +5 -5
- package/packages/bundled-features/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +22 -10
- package/packages/bundled-features/src/tenant-lifecycle/handlers/request-destruction.write.ts +22 -11
- package/packages/bundled-features/src/tenant-lifecycle/run-tenant-destroy.ts +23 -12
- package/packages/bundled-features/src/tenant-lifecycle/stages.ts +7 -3
- package/packages/bundled-features/src/tenant-lifecycle/testing.ts +9 -8
- package/packages/bundled-features/src/tier-engine/entity.ts +1 -1
- package/packages/bundled-features/src/tier-engine/feature.ts +8 -3
- package/packages/bundled-features/src/tier-engine/handlers/active-tier.query.ts +3 -3
- package/packages/bundled-features/src/tier-engine/handlers/get-tenant-tier.query.ts +7 -1
- package/packages/bundled-features/src/tier-engine/handlers/set-tenant-tier.write.ts +3 -3
- package/packages/bundled-features/src/user/db/queries/stream-tenant-backfill.ts +71 -97
- package/packages/bundled-features/src/user/handlers/find-for-auth.query.ts +2 -2
- package/packages/bundled-features/src/user/handlers/update-roles.ts +2 -2
- package/packages/bundled-features/src/user/index.ts +2 -0
- package/packages/bundled-features/src/user/invited-global-roles.ts +6 -1
- package/packages/bundled-features/src/user/principal-status.ts +1 -1
- package/packages/bundled-features/src/user/seeding.ts +24 -6
- package/packages/bundled-features/src/user/testing.ts +22 -0
- package/packages/bundled-features/src/user-data-rights/COMPLIANCE.md +1 -1
- package/packages/bundled-features/src/user-data-rights/README.md +1 -1
- package/packages/bundled-features/src/user-data-rights/audit-download.ts +12 -2
- package/packages/bundled-features/src/user-data-rights/changes.json +24 -0
- package/packages/bundled-features/src/user-data-rights/feature.ts +29 -24
- package/packages/bundled-features/src/user-data-rights/handlers/confirm-deletion-by-token.write.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/handlers/deletion-grace-period.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/handlers/download-by-job.query.ts +2 -2
- package/packages/bundled-features/src/user-data-rights/handlers/download-by-token.query.ts +2 -2
- package/packages/bundled-features/src/user-data-rights/handlers/request-deletion.write.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/handlers/restrict-account.write.ts +4 -4
- package/packages/bundled-features/src/user-data-rights/handlers/run-forget-cleanup.write.ts +12 -6
- package/packages/bundled-features/src/user-data-rights/index.ts +2 -0
- package/packages/bundled-features/src/user-data-rights/lib/mail-transport-resolver.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/lib/storage-provider-resolver.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/lib/update-user-lifecycle.ts +6 -1
- package/packages/bundled-features/src/user-data-rights/run-export-jobs.ts +8 -2
- package/packages/bundled-features/src/user-data-rights/run-forget-cleanup.ts +14 -3
- package/packages/bundled-features/src/user-data-rights/run-user-export.ts +11 -1
- package/packages/bundled-features/src/user-data-rights/web/constants.ts +10 -0
- package/packages/bundled-features/src/user-data-rights/web/index.ts +6 -1
- package/packages/bundled-features/src/user-profile/handlers/change-email.write.ts +8 -6
- package/packages/bundled-features/src/waitlist/handlers/export-purge.write.ts +1 -1
- package/packages/bundled-features/src/waitlist/handlers/invite.write.ts +2 -2
- package/packages/bundled-features/src/waitlist/handlers/reject.write.ts +1 -1
- package/packages/bundled-features/src/waitlist/handlers/submit.write.ts +9 -4
- package/packages/bundled-features/src/waitlist/legacy-migrate.ts +6 -1
- package/packages/bundled-features/src/waitlist/link-on-signup.ts +7 -1
- package/packages/bundled-features/src/workflow-runner/changes.json +6 -0
- package/packages/bundled-features/src/workflow-runner/db/queries/due-runs.ts +1 -1
- package/packages/bundled-features/src/workflow-runner/event-subscriber.ts +4 -3
- package/packages/bundled-features/src/workflow-runner/event-trigger.ts +2 -1
- package/packages/bundled-features/src/workflow-runner/feature.ts +2 -2
- package/packages/bundled-features/src/workflow-runner/handlers/resume-run.write.ts +8 -8
- package/packages/bundled-features/src/workflow-runner/runner.ts +6 -4
- package/samples/apps/cap-billing-demo/package.json +1 -1
- package/samples/apps/cap-billing-demo/src/feature.ts +19 -5
- package/samples/apps/config-demo/src/app/server.ts +1 -1
- package/samples/apps/styleguide/src/app/server.ts +2 -2
- package/samples/apps/ui-walkthrough/package.json +1 -1
- package/samples/apps/use-all-bundled/feature-manifest.json +1 -1
- package/samples/apps/use-all-bundled/package.json +1 -1
- package/samples/apps/use-all-bundled/src/app/seed.ts +12 -2
- package/samples/apps/user-data-rights-demo/README.md +1 -1
- package/samples/apps/user-data-rights-demo/package.json +1 -1
- package/samples/apps/user-data-rights-demo/src/feature.ts +3 -2
- package/samples/recipes/api-worker-split/README.md +2 -1
- package/samples/recipes/api-worker-split/src/feature.ts +2 -1
- package/samples/recipes/cross-feature-events/README.md +5 -5
- package/samples/recipes/cross-feature-events/package.json +1 -1
- package/samples/recipes/cross-feature-events/src/feature.ts +4 -4
- package/samples/recipes/event-sourcing/README.md +5 -5
- package/samples/recipes/event-sourcing/package.json +1 -1
- package/samples/recipes/event-sourcing/src/feature.ts +15 -15
- package/samples/recipes/files-post-processing/README.md +1 -1
- package/samples/recipes/files-post-processing/package.json +1 -1
- package/samples/recipes/files-post-processing/src/feature.ts +6 -4
- package/samples/recipes/lane-routing/package.json +1 -1
- package/samples/recipes/legal-pages/README.md +2 -2
- package/samples/recipes/lifecycle-hooks/README.md +2 -2
- package/samples/recipes/lifecycle-hooks/src/feature.ts +1 -1
- package/samples/recipes/managed-config/README.md +3 -3
- package/samples/recipes/search/README.md +1 -1
- package/samples/recipes/search/src/feature.ts +3 -3
- package/samples/recipes/secrets-demo/README.md +1 -1
- package/samples/recipes/secrets-demo/src/feature.ts +5 -2
- package/samples/recipes/state-machine/src/events.ts +1 -1
- package/samples/recipes/state-machine/src/reducer.ts +1 -1
- package/samples/recipes/workflow-engine/src/resume-loop.ts +4 -4
- package/samples/recipes/workflow-engine/src/workflow-runner.ts +2 -2
|
@@ -18,7 +18,17 @@ import {
|
|
|
18
18
|
updateMany,
|
|
19
19
|
upsertOnConflict,
|
|
20
20
|
} from "@cosmicdrift/kumiko-framework/db";
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
parseTenantId,
|
|
23
|
+
type Registry,
|
|
24
|
+
SYSTEM_TENANT_ID,
|
|
25
|
+
} from "@cosmicdrift/kumiko-framework/engine";
|
|
26
|
+
import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
|
|
27
|
+
import {
|
|
28
|
+
isRowLevelSecurityViolation,
|
|
29
|
+
runInRowLevelSecurityScope,
|
|
30
|
+
tenantRowLevelSecurityScope,
|
|
31
|
+
} from "@cosmicdrift/kumiko-framework/internal/row-level-security";
|
|
22
32
|
import type {
|
|
23
33
|
JobDroppedMeta,
|
|
24
34
|
JobLogEntry,
|
|
@@ -77,6 +87,16 @@ const DEFAULT_CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
|
|
|
77
87
|
// Run rows hang on SYSTEM_TENANT_ID and a null trigger is a cron/system run;
|
|
78
88
|
// job-run-table.ts declares whenAbsent: "tenant" for exactly that case, so the
|
|
79
89
|
// logger must resolve the same subject the entity chain would.
|
|
90
|
+
// Job callbacks run outside any request scope; the tenant tables enforce their rows per tenant, so each write
|
|
91
|
+
// declares the tenant it belongs to.
|
|
92
|
+
function inTenantScope<T>(tenantId: string, work: () => Promise<T>): Promise<T> {
|
|
93
|
+
const parsed = parseTenantId(tenantId);
|
|
94
|
+
if (parsed === null) {
|
|
95
|
+
throw new InternalError({ message: `job run logger got a malformed tenant id "${tenantId}"` });
|
|
96
|
+
}
|
|
97
|
+
return runInRowLevelSecurityScope(tenantRowLevelSecurityScope(parsed), work);
|
|
98
|
+
}
|
|
99
|
+
|
|
80
100
|
function jobRunSubject(triggeredById: string | null): SubjectId {
|
|
81
101
|
return triggeredById === null
|
|
82
102
|
? { kind: "tenant", tenantId: SYSTEM_TENANT_ID }
|
|
@@ -182,7 +202,7 @@ const TENANT_RUN_CONFLICT_KEYS = ["tenantId", "jobName", "bullJobId"] as const;
|
|
|
182
202
|
function tenantJobFailureTarget(
|
|
183
203
|
jobName: string,
|
|
184
204
|
outcome: JobOutcomeMeta | undefined,
|
|
185
|
-
):
|
|
205
|
+
): { tenantId: string; jobName: string; subject: string | null } | null {
|
|
186
206
|
if (!outcome?.tenantVisible || outcome.tenantId === SYSTEM_TENANT_ID) return null;
|
|
187
207
|
return { tenantId: outcome.tenantId, jobName, subject: outcome.tenantVisible.subject };
|
|
188
208
|
}
|
|
@@ -202,19 +222,21 @@ async function recordTenantJobFailure(
|
|
|
202
222
|
// partial unique indexes (subject NULL vs. set) are no ON CONFLICT target.
|
|
203
223
|
// The lock makes the later of two racing final failures replace the earlier
|
|
204
224
|
// one; the indexes are the backstop against any writer that skips the lock.
|
|
205
|
-
await
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
225
|
+
await inTenantScope(where.tenantId, () =>
|
|
226
|
+
transaction(db, async (tx) => {
|
|
227
|
+
await acquireNamespacedAdvisoryLock(
|
|
228
|
+
tx,
|
|
229
|
+
TENANT_JOB_FAILURE_LOCK_NAMESPACE,
|
|
230
|
+
JSON.stringify([where["tenantId"], where["jobName"], where["subject"]]),
|
|
231
|
+
);
|
|
232
|
+
await deleteMany(tx, tenantJobFailuresTable, where);
|
|
233
|
+
await insertOne(tx, tenantJobFailuresTable, {
|
|
234
|
+
...where,
|
|
235
|
+
messageKey,
|
|
236
|
+
failedAt: Temporal.Now.instant(),
|
|
237
|
+
});
|
|
238
|
+
}),
|
|
239
|
+
);
|
|
218
240
|
}
|
|
219
241
|
|
|
220
242
|
async function clearTenantJobFailure(
|
|
@@ -225,7 +247,7 @@ async function clearTenantJobFailure(
|
|
|
225
247
|
const where = tenantJobFailureTarget(jobName, outcome);
|
|
226
248
|
// skip: no tenant-visible target — nothing was ever recorded
|
|
227
249
|
if (!where) return;
|
|
228
|
-
await deleteMany(db, tenantJobFailuresTable, where);
|
|
250
|
+
await inTenantScope(where.tenantId, () => deleteMany(db, tenantJobFailuresTable, where));
|
|
229
251
|
}
|
|
230
252
|
|
|
231
253
|
// Tenant-visible run state. One row per BullMQ job in
|
|
@@ -260,28 +282,30 @@ async function recordTenantRunQueued(
|
|
|
260
282
|
// skip: system-tenant run, no tenant-scoped query reads it
|
|
261
283
|
if (!key) return;
|
|
262
284
|
const now = Temporal.Now.instant();
|
|
263
|
-
await
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
+
await inTenantScope(key.tenantId, () =>
|
|
286
|
+
transaction(db, async (tx) => {
|
|
287
|
+
// A finished row under the same id is left over from before a Redis reset
|
|
288
|
+
// (ids restart at 1), not this job. Only rows queued before this BullMQ job
|
|
289
|
+
// was created qualify: a fast worker can finish before the hook runs (no
|
|
290
|
+
// queuedAt), and a deduplicated add of an already finished job re-announces
|
|
291
|
+
// it with a creation time older than its own row. Both rows must stay.
|
|
292
|
+
await deleteMany(tx, tenantJobRunsTable, {
|
|
293
|
+
tenantId: key.tenantId,
|
|
294
|
+
jobName,
|
|
295
|
+
bullJobId,
|
|
296
|
+
status: { in: ["completed", "failed"] },
|
|
297
|
+
queuedAt: { lt: Temporal.Instant.fromEpochMilliseconds(meta.jobCreatedAtMs) },
|
|
298
|
+
});
|
|
299
|
+
// DO NOTHING: a fast worker can report the start before this enqueue hook
|
|
300
|
+
// returns, and a repeated jobId (debounce, fan-out dedup) is the same job.
|
|
301
|
+
await insertOnConflictDoNothing(
|
|
302
|
+
tx,
|
|
303
|
+
tenantJobRunsTable,
|
|
304
|
+
{ ...key, bullJobId, status: "queued", queuedAt: now, updatedAt: now },
|
|
305
|
+
{ conflictKeys: [...TENANT_RUN_CONFLICT_KEYS] },
|
|
306
|
+
);
|
|
307
|
+
}),
|
|
308
|
+
);
|
|
285
309
|
}
|
|
286
310
|
|
|
287
311
|
async function recordTenantRunStarted(
|
|
@@ -296,14 +320,16 @@ async function recordTenantRunStarted(
|
|
|
296
320
|
// skip: system-tenant run, no tenant-scoped query reads it
|
|
297
321
|
if (!key) return;
|
|
298
322
|
const now = Temporal.Now.instant();
|
|
299
|
-
await
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
323
|
+
await inTenantScope(key.tenantId, () =>
|
|
324
|
+
upsertOnConflict(
|
|
325
|
+
db,
|
|
326
|
+
tenantJobRunsTable,
|
|
327
|
+
{ ...key, bullJobId, status: "running", startedAt: now, updatedAt: now },
|
|
328
|
+
{
|
|
329
|
+
conflictKeys: [...TENANT_RUN_CONFLICT_KEYS],
|
|
330
|
+
update: { status: "running", startedAt: now, finishedAt: null, updatedAt: now },
|
|
331
|
+
},
|
|
332
|
+
),
|
|
307
333
|
);
|
|
308
334
|
}
|
|
309
335
|
|
|
@@ -323,11 +349,13 @@ async function recordTenantRunOutcome(
|
|
|
323
349
|
// A failed attempt BullMQ will retry goes back to waiting: the tenant keeps
|
|
324
350
|
// seeing pending work, not a failure the next attempt may still resolve.
|
|
325
351
|
if (result === "failed" && !outcome.finalAttempt) {
|
|
326
|
-
await
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
352
|
+
await inTenantScope(key.tenantId, () =>
|
|
353
|
+
updateMany(
|
|
354
|
+
db,
|
|
355
|
+
tenantJobRunsTable,
|
|
356
|
+
{ status: "queued", startedAt: null, finishedAt: null, updatedAt: now },
|
|
357
|
+
{ tenantId: key.tenantId, jobName, bullJobId },
|
|
358
|
+
),
|
|
331
359
|
);
|
|
332
360
|
// skip: retry pending, the row stays active until the final attempt
|
|
333
361
|
return;
|
|
@@ -336,30 +364,32 @@ async function recordTenantRunOutcome(
|
|
|
336
364
|
// one it replaces. The per-key lock keeps two runs of the same subject that
|
|
337
365
|
// finish together from each missing the other's uncommitted row and leaving
|
|
338
366
|
// both behind.
|
|
339
|
-
await
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
367
|
+
await inTenantScope(key.tenantId, () =>
|
|
368
|
+
transaction(db, async (tx) => {
|
|
369
|
+
await acquireNamespacedAdvisoryLock(
|
|
370
|
+
tx,
|
|
371
|
+
TENANT_JOB_RUN_LOCK_NAMESPACE,
|
|
372
|
+
JSON.stringify([key.tenantId, key.jobName, key.subject]),
|
|
373
|
+
);
|
|
374
|
+
// Read under the lock: the run that finishes last in lock order must also carry the
|
|
375
|
+
// latest finishedAt, otherwise a stale timestamp makes it the row that gets pruned.
|
|
376
|
+
const finishedAt = Temporal.Now.instant();
|
|
377
|
+
await upsertOnConflict(
|
|
378
|
+
tx,
|
|
379
|
+
tenantJobRunsTable,
|
|
380
|
+
{ ...key, bullJobId, status: result, finishedAt, updatedAt: finishedAt },
|
|
381
|
+
{
|
|
382
|
+
conflictKeys: [...TENANT_RUN_CONFLICT_KEYS],
|
|
383
|
+
update: { status: result, finishedAt, updatedAt: finishedAt },
|
|
384
|
+
},
|
|
385
|
+
);
|
|
386
|
+
await deleteMany(tx, tenantJobRunsTable, {
|
|
387
|
+
...key,
|
|
388
|
+
status: { in: ["completed", "failed"] },
|
|
389
|
+
bullJobId: { ne: bullJobId },
|
|
390
|
+
});
|
|
391
|
+
}),
|
|
392
|
+
);
|
|
363
393
|
}
|
|
364
394
|
|
|
365
395
|
async function requeueTenantRun(
|
|
@@ -373,11 +403,13 @@ async function requeueTenantRun(
|
|
|
373
403
|
const key = tenantRunKey(jobName, meta.tenantVisibleRun.tenantId, meta.tenantVisibleRun.subject);
|
|
374
404
|
// skip: system-tenant run, no tenant-scoped query reads it
|
|
375
405
|
if (!key) return;
|
|
376
|
-
await
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
406
|
+
await inTenantScope(key.tenantId, () =>
|
|
407
|
+
updateMany(
|
|
408
|
+
db,
|
|
409
|
+
tenantJobRunsTable,
|
|
410
|
+
{ status: "queued", startedAt: null, finishedAt: null, updatedAt: Temporal.Now.instant() },
|
|
411
|
+
{ tenantId: key.tenantId, jobName, bullJobId },
|
|
412
|
+
),
|
|
381
413
|
);
|
|
382
414
|
}
|
|
383
415
|
|
|
@@ -387,20 +419,22 @@ async function dropTenantRunQueued(
|
|
|
387
419
|
bullJobId: string,
|
|
388
420
|
meta: JobDroppedMeta,
|
|
389
421
|
): Promise<void> {
|
|
390
|
-
await
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
422
|
+
await inTenantScope(meta.tenantId, () =>
|
|
423
|
+
deleteMany(db, tenantJobRunsTable, {
|
|
424
|
+
tenantId: meta.tenantId,
|
|
425
|
+
jobName,
|
|
426
|
+
bullJobId,
|
|
427
|
+
status: "queued",
|
|
428
|
+
}),
|
|
429
|
+
);
|
|
396
430
|
}
|
|
397
431
|
|
|
398
432
|
const log = createFallbackLogger("job-run-logger");
|
|
399
433
|
|
|
400
434
|
// The tenant run state is an optional side write: a throw here must not
|
|
401
435
|
// fail the enqueue, skip the run-row write or turn a completed run into a
|
|
402
|
-
// BullMQ retry.
|
|
403
|
-
async function isolateTenantRunWrite(
|
|
436
|
+
// BullMQ retry. A row-level-security rejection is logged louder (a missing or wrong tenant scope), never rethrown.
|
|
437
|
+
export async function isolateTenantRunWrite(
|
|
404
438
|
write: () => Promise<void>,
|
|
405
439
|
jobName: string,
|
|
406
440
|
bullJobId: string,
|
|
@@ -408,6 +442,13 @@ async function isolateTenantRunWrite(
|
|
|
408
442
|
try {
|
|
409
443
|
await write();
|
|
410
444
|
} catch (e) {
|
|
445
|
+
if (isRowLevelSecurityViolation(e)) {
|
|
446
|
+
log.error(
|
|
447
|
+
"tenant job run state write rejected by row level security: missing or wrong tenant scope",
|
|
448
|
+
{ jobName, bullJobId },
|
|
449
|
+
);
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
411
452
|
log.error("tenant job run state write failed", {
|
|
412
453
|
jobName,
|
|
413
454
|
bullJobId,
|
|
@@ -417,7 +458,8 @@ async function isolateTenantRunWrite(
|
|
|
417
458
|
}
|
|
418
459
|
|
|
419
460
|
// The tenant failure record is an optional side write: a throw here must not
|
|
420
|
-
// skip the run-row update or turn a completed run into a BullMQ retry.
|
|
461
|
+
// skip the run-row update or turn a completed run into a BullMQ retry. A row-level-security rejection is
|
|
462
|
+
// logged louder (a missing or wrong tenant scope), never rethrown.
|
|
421
463
|
async function isolateTenantFailureWrite(
|
|
422
464
|
write: () => Promise<void>,
|
|
423
465
|
jobName: string,
|
|
@@ -426,6 +468,13 @@ async function isolateTenantFailureWrite(
|
|
|
426
468
|
try {
|
|
427
469
|
await write();
|
|
428
470
|
} catch (e) {
|
|
471
|
+
if (isRowLevelSecurityViolation(e)) {
|
|
472
|
+
log.error(
|
|
473
|
+
"tenant job failure record write rejected by row level security: missing or wrong tenant scope",
|
|
474
|
+
{ jobName, bullJobId },
|
|
475
|
+
);
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
429
478
|
log.error("tenant job failure record write failed", {
|
|
430
479
|
jobName,
|
|
431
480
|
bullJobId,
|
|
@@ -544,7 +593,7 @@ export function createJobRunLogger(opts: JobRunLoggerOptions): JobRunLoggerCallb
|
|
|
544
593
|
const triggeredById = meta.triggeredById ?? null;
|
|
545
594
|
cachePut(bullJobId, runId, triggeredById);
|
|
546
595
|
// Parse against the registered schema so out-of-dispatcher writes
|
|
547
|
-
// get the same validation guarantee as ctx.
|
|
596
|
+
// get the same validation guarantee as ctx.events.append. A shape
|
|
548
597
|
// drift between feature + logger fails loudly at the source
|
|
549
598
|
// instead of silently landing on the table.
|
|
550
599
|
const payload = runStartedSchema.parse({
|
|
@@ -164,7 +164,7 @@ A throw rejects the boot, so the process never reports ready.
|
|
|
164
164
|
|
|
165
165
|
| Slug + locale | What happens when missing |
|
|
166
166
|
|---|---|
|
|
167
|
-
| `imprint` / `de` | **Production:** `throw new Error(...)` blocks app start. **Dev:** `ctx.log.warn(...)` |
|
|
167
|
+
| `imprint` / `de` | **Production:** `throw new Error(...)` blocks app start. **Dev:** `ctx.obs.log.warn(...)` |
|
|
168
168
|
| `privacy` / `de` | as above |
|
|
169
169
|
|
|
170
170
|
EN versions are **not** boot-fail-relevant by default (`LEGAL_OPTIONAL_BLOCKS`).
|
|
@@ -6,13 +6,17 @@ import {
|
|
|
6
6
|
import { SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
|
|
7
7
|
import { LEGAL_REQUIRED_BLOCKS, type LegalRequiredBlock } from "../web/constants.ts";
|
|
8
8
|
|
|
9
|
-
// Only the fields the check reads, so HandlerContext
|
|
10
|
-
//
|
|
9
|
+
// Only the fields the check reads, so a HandlerContext or a hand-built object in a unit test
|
|
10
|
+
// fits.
|
|
11
11
|
export type LegalPagesBootCheckCtx = {
|
|
12
|
-
readonly
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
readonly services: {
|
|
13
|
+
readonly extensions: { readonly templateResolver?: TemplateResolverApi };
|
|
14
|
+
};
|
|
15
|
+
readonly obs?: {
|
|
16
|
+
readonly log?: {
|
|
17
|
+
readonly info?: (msg: string) => void;
|
|
18
|
+
readonly warn?: (msg: string) => void;
|
|
19
|
+
};
|
|
16
20
|
};
|
|
17
21
|
};
|
|
18
22
|
|
|
@@ -42,7 +46,7 @@ export async function runLegalPagesBootCheck(
|
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
if (missing.length === 0) {
|
|
45
|
-
ctx.log?.info?.("legal-pages boot-check: alle Pflicht-Blocks vorhanden");
|
|
49
|
+
ctx.obs?.log?.info?.("legal-pages boot-check: alle Pflicht-Blocks vorhanden");
|
|
46
50
|
} else {
|
|
47
51
|
const message =
|
|
48
52
|
`legal-pages: missing ${missing.length} required text-block(s) in SYSTEM_TENANT: ` +
|
|
@@ -52,6 +56,6 @@ export async function runLegalPagesBootCheck(
|
|
|
52
56
|
if (process.env["NODE_ENV"] === "production") {
|
|
53
57
|
throw new Error(`Boot-Validation failed: ${message}`);
|
|
54
58
|
}
|
|
55
|
-
ctx.log?.warn?.(message);
|
|
59
|
+
ctx.obs?.log?.warn?.(message);
|
|
56
60
|
}
|
|
57
61
|
}
|
|
@@ -64,7 +64,7 @@ export const MAIL_TRANSPORT_EXTENSION = "mailTransport" as const;
|
|
|
64
64
|
* **Why not the full HandlerContext?** On the worker path (transport builds
|
|
65
65
|
* triggered by r.job, such as the user-data-rights forget/export cron) there
|
|
66
66
|
* is no per-request `config`/`tx`/`actor`; the wrapper builds the per-tenant
|
|
67
|
-
* ConfigAccessor from `ctx.configResolver`. A plugin reading `ctx.tx` or other
|
|
67
|
+
* ConfigAccessor from `ctx.services.configResolver`. A plugin reading `ctx.tx` or other
|
|
68
68
|
* request-only fields would break the worker path at runtime, and that would
|
|
69
69
|
* only show up with real SMTP in production. Casting
|
|
70
70
|
* `ctx as unknown as HandlerContext` satisfies the compiler but fails at
|
|
@@ -82,7 +82,7 @@ export const MAIL_TRANSPORT_EXTENSION = "mailTransport" as const;
|
|
|
82
82
|
export type MailTransportContext = {
|
|
83
83
|
readonly config?: ConfigAccessor;
|
|
84
84
|
readonly registry?: Registry;
|
|
85
|
-
readonly secrets
|
|
85
|
+
readonly secrets: import("@cosmicdrift/kumiko-framework/secrets").SecretsContext | undefined;
|
|
86
86
|
readonly auditUserId?: string | undefined;
|
|
87
87
|
};
|
|
88
88
|
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.390.0",
|
|
4
|
+
"type": "fix",
|
|
5
|
+
"title": "nodemailer 10.2.1 fixes the address-parser advisories",
|
|
6
|
+
"detail": "nodemailer 10.0.13 mis-parsed quoted semicolons, angle brackets and nested comments in addresses (GHSA-m943-m63m-w62w, GHSA-85x2-q946-cp5h, GHSA-8c8m-p3m8-cjg8). The dependency is now 10.2.1, and the overrides for ip-address (10.7.4) and fast-copy (4.2.0) remove the remaining audit findings."
|
|
7
|
+
},
|
|
2
8
|
{
|
|
3
9
|
"version": "0.389.0",
|
|
4
10
|
"type": "improvement",
|
|
@@ -231,7 +231,10 @@ async function buildSmtpTransport(
|
|
|
231
231
|
}
|
|
232
232
|
|
|
233
233
|
async function readPassword(ctx: MailTransportContext, tenantId: string): Promise<string> {
|
|
234
|
-
const secrets = requireSecretsContext(
|
|
234
|
+
const secrets = requireSecretsContext(
|
|
235
|
+
{ secrets: ctx.secrets, auditUserId: ctx.auditUserId },
|
|
236
|
+
FEATURE_NAME,
|
|
237
|
+
);
|
|
235
238
|
const branded = await secrets.get(tenantId, SMTP_PASSWORD);
|
|
236
239
|
return requireSecretSet(branded, FEATURE_NAME, SMTP_PASSWORD.name).reveal();
|
|
237
240
|
}
|
|
@@ -8,12 +8,12 @@ import {
|
|
|
8
8
|
type DbConnection,
|
|
9
9
|
fetchOne,
|
|
10
10
|
} from "@cosmicdrift/kumiko-framework/db";
|
|
11
|
-
import type { SessionUser, TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
12
11
|
import {
|
|
13
|
-
|
|
14
|
-
type
|
|
15
|
-
|
|
16
|
-
} from "@cosmicdrift/kumiko-framework/
|
|
12
|
+
createSystemUser,
|
|
13
|
+
type SessionUser,
|
|
14
|
+
type TenantId,
|
|
15
|
+
} from "@cosmicdrift/kumiko-framework/engine";
|
|
16
|
+
import { runEventStoreSeed, type SeedIfExists } from "@cosmicdrift/kumiko-framework/seeding";
|
|
17
17
|
|
|
18
18
|
import { type PageRow, pageEntity, pagesTable } from "./table.ts";
|
|
19
19
|
|
|
@@ -35,9 +35,14 @@ export type SeedPageOptions = {
|
|
|
35
35
|
export async function seedPage(db: DbConnection, opts: SeedPageOptions): Promise<{ id: string }> {
|
|
36
36
|
// The default user must have user.tenantId === opts.tenantId; otherwise the
|
|
37
37
|
// event-store stream ends up in the wrong tenant bucket → version_conflict
|
|
38
|
-
// on subsequent real writes.
|
|
39
|
-
const by = opts.by ??
|
|
40
|
-
const tdb = createTenantDb({
|
|
38
|
+
// on subsequent real writes.
|
|
39
|
+
const by = opts.by ?? createSystemUser(opts.tenantId);
|
|
40
|
+
const tdb = createTenantDb({
|
|
41
|
+
db,
|
|
42
|
+
tenantId: opts.tenantId,
|
|
43
|
+
mode: "system",
|
|
44
|
+
reason: "seeding writes managed pages into an explicitly named tenant",
|
|
45
|
+
});
|
|
41
46
|
|
|
42
47
|
const existing = await fetchOne<PageRow>(db, pagesTable, {
|
|
43
48
|
tenantId: opts.tenantId,
|
|
@@ -80,7 +80,7 @@ export function createAddNoteHandler(
|
|
|
80
80
|
return decryptStoredPii(userRow.displayName, "displayName", "notes-history:add-note");
|
|
81
81
|
});
|
|
82
82
|
} catch (e) {
|
|
83
|
-
ctx.log?.warn("notes-history: authorName lookup failed", {
|
|
83
|
+
ctx.obs.log?.warn("notes-history: authorName lookup failed", {
|
|
84
84
|
error: e,
|
|
85
85
|
userId: event.user.id,
|
|
86
86
|
});
|
|
@@ -136,7 +136,7 @@ export function createPersonalAccessTokensFeature(
|
|
|
136
136
|
|
|
137
137
|
// Custom domain-event for cross-instance access-invalidation,
|
|
138
138
|
// mirroring sessions' SESSION_REVOKED_EVENT_SHORT. r.defineEvent
|
|
139
|
-
// registers the schema so ctx.
|
|
139
|
+
// registers the schema so ctx.events.unsafeAppend (revoke) enforces it at
|
|
140
140
|
// append time. revoke-for-user.ts appends via the low-level append()
|
|
141
141
|
// instead (needs to anchor on SYSTEM_TENANT_ID) — same guarantee via a
|
|
142
142
|
// different path. No projection: the payload IS the read, consumed
|
|
@@ -68,7 +68,7 @@ export function createPatCreateHandler(opts: CreatePatOptions = {}) {
|
|
|
68
68
|
escapeHatch: {
|
|
69
69
|
grants: ["systemIdentity"],
|
|
70
70
|
reason:
|
|
71
|
-
"Reads the caller's own passwordHash (privileged-only field) via ctx.queryAs(SYSTEM, " +
|
|
71
|
+
"Reads the caller's own passwordHash (privileged-only field) via ctx.dispatch.queryAs(SYSTEM, " +
|
|
72
72
|
"user:findForAuth) to re-verify the current password before minting the token. Also " +
|
|
73
73
|
"reads the MFA enrollment of the user being re-authenticated via the mfaVerifier callback.",
|
|
74
74
|
},
|
|
@@ -79,7 +79,7 @@ export function createPatCreateHandler(opts: CreatePatOptions = {}) {
|
|
|
79
79
|
agent: { expose: false },
|
|
80
80
|
handler: async (event, ctx) => {
|
|
81
81
|
const systemUser = createSystemUser(event.user.tenantId);
|
|
82
|
-
const me = (await ctx.queryAs(systemUser, UserQueries.findForAuth, {
|
|
82
|
+
const me = (await ctx.dispatch.queryAs(systemUser, UserQueries.findForAuth, {
|
|
83
83
|
id: event.user.id,
|
|
84
84
|
})) as { passwordHash: string | null } | null; // @cast-boundary db-runner
|
|
85
85
|
if (!me?.passwordHash) return reauthFailed();
|
|
@@ -39,7 +39,7 @@ export const revokePatWrite = defineWriteHandler({
|
|
|
39
39
|
// see feature.ts). Without this, the access-invalidation consumer
|
|
40
40
|
// never hears about the revoke and an already-open SSE stream
|
|
41
41
|
// authenticated by this exact token survives it.
|
|
42
|
-
await ctx.
|
|
42
|
+
await ctx.events.unsafeAppend({
|
|
43
43
|
aggregateId: generateId(),
|
|
44
44
|
aggregateType: PAT_REVOKED_AGGREGATE_TYPE,
|
|
45
45
|
type: PAT_REVOKED_EVENT_QN,
|
|
@@ -37,12 +37,12 @@ export const rateLimitStatus = defineQueryHandler({
|
|
|
37
37
|
// about the wiring either.
|
|
38
38
|
const denied = bucketAccessDenied(query.payload.bucket, ctx.user);
|
|
39
39
|
if (denied) throw denied;
|
|
40
|
-
if (!ctx.rateLimit) {
|
|
40
|
+
if (!ctx.services.rateLimit) {
|
|
41
41
|
throw new UnprocessableError(RateLimitErrors.resolverUnavailable, {
|
|
42
42
|
i18nKey: "rateLimiting.errors.resolverUnavailable",
|
|
43
43
|
});
|
|
44
44
|
}
|
|
45
|
-
const decision = await ctx.rateLimit.peek(query.payload.bucket, {
|
|
45
|
+
const decision = await ctx.services.rateLimit.peek(query.payload.bucket, {
|
|
46
46
|
limit: query.payload.limit,
|
|
47
47
|
windowSeconds: query.payload.windowSeconds,
|
|
48
48
|
});
|
|
@@ -36,7 +36,10 @@ export const statusQuery = defineQueryHandler({
|
|
|
36
36
|
|
|
37
37
|
// has() is metadata-only: no decryption, no read-audit event — a
|
|
38
38
|
// readiness probe must not pollute the credential-read trail.
|
|
39
|
-
const secrets = requireSecretsContext(
|
|
39
|
+
const secrets = requireSecretsContext(
|
|
40
|
+
{ secrets: ctx.services.secrets, user: ctx.user },
|
|
41
|
+
ReadinessQueries.status,
|
|
42
|
+
);
|
|
40
43
|
const missingSecrets: ReadinessMissingSecret[] = [];
|
|
41
44
|
for (const [qualifiedName, keyDef] of ctx.registry.getAllSecretKeys()) {
|
|
42
45
|
if (keyDef.required !== true) continue;
|
|
@@ -87,12 +87,12 @@ function indexByKind(plugins: ReadonlyArray<RendererPlugin>): Map<RenderKind, Re
|
|
|
87
87
|
// Single source of truth for handlers that require renderer-foundation.
|
|
88
88
|
// The pattern mirrors requireTemplateResolver + requireTextContent.
|
|
89
89
|
export function requireRendererFoundation(
|
|
90
|
-
ctx: { readonly
|
|
90
|
+
ctx: { readonly services: { readonly extensions: Readonly<Record<string, unknown>> } },
|
|
91
91
|
callerName: string,
|
|
92
92
|
): RendererFoundationApi {
|
|
93
93
|
// @cast-boundary engine-bridge — rendererFoundation kommt per extraContext
|
|
94
|
-
// from the app bootstrap.
|
|
95
|
-
const api =
|
|
94
|
+
// from the app bootstrap and lands in services.extensions; the framework does not know its type.
|
|
95
|
+
const api = ctx.services.extensions["rendererFoundation"] as RendererFoundationApi | undefined;
|
|
96
96
|
if (!api) {
|
|
97
97
|
throw new InternalError({
|
|
98
98
|
message:
|
|
@@ -6,8 +6,8 @@ import {
|
|
|
6
6
|
} from "@cosmicdrift/kumiko-framework/redis";
|
|
7
7
|
|
|
8
8
|
type AnnouncingContext = {
|
|
9
|
-
readonly cacheSync?: CacheSyncBus;
|
|
10
|
-
readonly
|
|
9
|
+
readonly services: { readonly cacheSync?: CacheSyncBus };
|
|
10
|
+
readonly lifecycle: { readonly afterCommit?: (hook: () => Promise<void>) => void };
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
// Secret writes bypass config:write:*, yet provider caches (e.g. the S3 secret
|
|
@@ -18,13 +18,13 @@ export function announceSecretChange(
|
|
|
18
18
|
tenantId: TenantId,
|
|
19
19
|
key: string,
|
|
20
20
|
): void {
|
|
21
|
-
const bus = ctx.cacheSync;
|
|
21
|
+
const bus = ctx.services.cacheSync;
|
|
22
22
|
// skip: no bus wired (single-process setup), nothing to announce
|
|
23
23
|
if (!bus) return;
|
|
24
24
|
const message: TenantConfigSyncMessage = { tenantId, key };
|
|
25
25
|
const publish = async (): Promise<void> => {
|
|
26
26
|
bus.publish(CACHE_SYNC_TOPICS.tenantConfig, message);
|
|
27
27
|
};
|
|
28
|
-
if (ctx.
|
|
28
|
+
if (ctx.lifecycle.afterCommit) ctx.lifecycle.afterCommit(publish);
|
|
29
29
|
else bus.publish(CACHE_SYNC_TOPICS.tenantConfig, message);
|
|
30
30
|
}
|
|
@@ -70,19 +70,19 @@ export { type StoredEnvelope, type StoredMetadata, tenantSecretsTable } from "./
|
|
|
70
70
|
export const JOIN_REQUEST_TRANSACTION_REASON =
|
|
71
71
|
"the secret write runs on the request transaction so a failing batch rolls it back together with the config writes";
|
|
72
72
|
|
|
73
|
-
// AppContext carries ctx.secrets via extraContext. requireSecretsContext
|
|
73
|
+
// AppContext carries ctx.services.secrets via extraContext. requireSecretsContext
|
|
74
74
|
// wraps that raw context so every `.get(...)` call automatically includes
|
|
75
75
|
// the current user and handler as audit metadata, preventing feature code
|
|
76
76
|
// from silently bypassing read auditing.
|
|
77
77
|
//
|
|
78
|
-
// The
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
// auditUserId, which takes precedence
|
|
78
|
+
// The source shape stays minimal: handler paths pass `{ secrets: ctx.services.secrets,
|
|
79
|
+
// user: ctx.user }`, provider paths (the S3 plugin in the worker) pass `{ secrets, auditUserId }`.
|
|
80
|
+
// `secrets` is a required key on purpose: a whole HandlerContext or JobContext does not carry
|
|
81
|
+
// it flat, so handing one over is a type error instead of a silently undefined secret store.
|
|
82
|
+
// Provider contexts set auditUserId, which takes precedence over the caller in `user`.
|
|
83
83
|
export function requireSecretsContext(
|
|
84
84
|
ctx: {
|
|
85
|
-
readonly secrets
|
|
85
|
+
readonly secrets: SecretsContext | undefined;
|
|
86
86
|
readonly auditUserId?: string | undefined;
|
|
87
87
|
readonly user?: { readonly id: string };
|
|
88
88
|
},
|
|
@@ -91,7 +91,7 @@ export function requireSecretsContext(
|
|
|
91
91
|
if (!ctx.secrets) {
|
|
92
92
|
throw new InternalError({
|
|
93
93
|
message:
|
|
94
|
-
`[${handlerName}] ctx.secrets missing — pass ` +
|
|
94
|
+
`[${handlerName}] ctx.services.secrets missing — pass ` +
|
|
95
95
|
"createSecretsContext({db, masterKeyProvider, registry}) via extraContext.secrets at boot.",
|
|
96
96
|
});
|
|
97
97
|
}
|
|
@@ -187,7 +187,7 @@ export function createSecretsFeature(
|
|
|
187
187
|
// Read-audit domain-event. Registered here so ops tools + MSPs can
|
|
188
188
|
// discover the type; secrets-context.get parses payloads against
|
|
189
189
|
// `secretReadSchema` at write time because the low-level append() path
|
|
190
|
-
// skips ctx.
|
|
190
|
+
// skips ctx.events.append's schema-validation guard.
|
|
191
191
|
r.defineEvent("read", secretReadSchema, { piiFields: "none" });
|
|
192
192
|
|
|
193
193
|
// Per-tenant handlers (set/delete/list) run in the default tenant-scope,
|