@cosmicdrift/kumiko-samples 0.388.0 → 0.390.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/package.json +1 -1
- package/packages/bundled-features/package.json +25 -13
- package/packages/bundled-features/src/agent-tools/changes.json +6 -0
- package/packages/bundled-features/src/agent-tools/index.ts +1 -1
- package/packages/bundled-features/src/agent-tools/tool-dispatch.ts +42 -26
- package/packages/bundled-features/src/audit/feature.ts +2 -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 +18 -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/request-account-unlock.write.ts +1 -0
- package/packages/bundled-features/src/auth-email-password/handlers/request-email-verification.write.ts +1 -0
- package/packages/bundled-features/src/auth-email-password/handlers/request-password-reset.write.ts +1 -0
- 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 +16 -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 +10 -2
- package/packages/bundled-features/src/auth-email-password/handlers/verify-email.write.ts +3 -2
- package/packages/bundled-features/src/auth-email-password/magic-link-mail.ts +16 -3
- package/packages/bundled-features/src/auth-email-password/seeding.ts +14 -9
- package/packages/bundled-features/src/auth-email-password/signup-activation.ts +2 -0
- package/packages/bundled-features/src/auth-email-password/web/auth-client.ts +8 -2
- package/packages/bundled-features/src/auth-email-password/web/auth-redirect.ts +23 -21
- package/packages/bundled-features/src/auth-email-password/web/default-auth-pages-gate.tsx +6 -3
- package/packages/bundled-features/src/auth-email-password/web/forgot-password-screen.tsx +11 -1
- package/packages/bundled-features/src/auth-email-password/web/index.ts +5 -0
- package/packages/bundled-features/src/auth-email-password/web/login-screen.tsx +16 -5
- package/packages/bundled-features/src/auth-email-password/web/reset-password-screen.tsx +7 -1
- package/packages/bundled-features/src/auth-email-password/web/return-path.ts +35 -0
- package/packages/bundled-features/src/auth-email-password/web/session.tsx +6 -1
- package/packages/bundled-features/src/auth-email-password/web/signup-complete-screen.tsx +11 -3
- package/packages/bundled-features/src/auth-email-password/web/signup-screen.tsx +12 -2
- package/packages/bundled-features/src/auth-foundation/feature.ts +13 -4
- package/packages/bundled-features/src/auth-mfa/changes.json +6 -0
- package/packages/bundled-features/src/auth-mfa/config.ts +3 -7
- 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 +17 -8
- 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 +21 -8
- package/packages/bundled-features/src/cap-counter/changes.json +12 -0
- package/packages/bundled-features/src/cap-counter/constants.ts +1 -1
- package/packages/bundled-features/src/cap-counter/feature.ts +3 -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/feature.ts +1 -0
- 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/changes.json +6 -0
- 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 +22 -5
- package/packages/bundled-features/src/config/handlers/values.query.ts +1 -1
- package/packages/bundled-features/src/crypto-shredding/changes.json +12 -0
- package/packages/bundled-features/src/crypto-shredding/handlers/forget-subject.write.ts +9 -9
- 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/custom-fields/lib/parse-serialized-field.ts +1 -1
- 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/changes.json +12 -0
- package/packages/bundled-features/src/delivery/delivery-service.ts +6 -5
- package/packages/bundled-features/src/delivery/feature.ts +3 -2
- package/packages/bundled-features/src/delivery/index.ts +5 -0
- package/packages/bundled-features/src/delivery/jobs.ts +11 -11
- package/packages/bundled-features/src/delivery/system-channel-context.ts +33 -0
- package/packages/bundled-features/src/delivery/testing.ts +1 -1
- package/packages/bundled-features/src/document-ingest-foundation/append-missing-extract-event.ts +84 -0
- package/packages/bundled-features/src/document-ingest-foundation/changes.json +6 -0
- package/packages/bundled-features/src/document-ingest-foundation/events.ts +16 -1
- package/packages/bundled-features/src/document-ingest-foundation/index.ts +11 -0
- package/packages/bundled-features/src/feature-toggles/feature.ts +1 -0
- 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/host-routing/changes.json +6 -0
- package/packages/bundled-features/src/host-routing/validate-options.ts +5 -0
- package/packages/bundled-features/src/inbound-mail-foundation/events.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/feature.ts +2 -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/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/feature.ts +1 -0
- 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/ledger/web/index.ts +1 -1
- 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/legal-pages/seed.ts +5 -2
- package/packages/bundled-features/src/mail-foundation/feature.ts +2 -2
- package/packages/bundled-features/src/mail-transport-smtp/changes.json +12 -0
- package/packages/bundled-features/src/mail-transport-smtp/config-keys.ts +1 -1
- package/packages/bundled-features/src/mail-transport-smtp/feature.ts +13 -12
- package/packages/bundled-features/src/managed-pages/changes.json +6 -0
- package/packages/bundled-features/src/managed-pages/constants.ts +1 -0
- package/packages/bundled-features/src/managed-pages/feature.ts +3 -1
- package/packages/bundled-features/src/managed-pages/index.ts +1 -0
- package/packages/bundled-features/src/managed-pages/seeding.ts +13 -8
- package/packages/bundled-features/src/notes-history/handlers/add-note.write.ts +2 -2
- package/packages/bundled-features/src/personal-access-tokens/feature.ts +2 -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/changes.json +6 -0
- 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 +10 -3
- package/packages/bundled-features/src/seo/feature.ts +1 -1
- package/packages/bundled-features/src/sessions/changes.json +19 -0
- package/packages/bundled-features/src/sessions/feature.ts +2 -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/index.ts +1 -1
- package/packages/bundled-features/src/sessions/screens.ts +8 -4
- package/packages/bundled-features/src/sessions/session-callbacks.ts +1 -9
- 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 +36 -4
- package/packages/bundled-features/src/shared/require-for-tenant.ts +7 -5
- package/packages/bundled-features/src/shared/run-in-sub-transaction.ts +5 -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/changes.json +6 -0
- package/packages/bundled-features/src/step-dispatcher/deliver-dispatch-request.ts +17 -3
- package/packages/bundled-features/src/step-dispatcher/feature.ts +1 -1
- package/packages/bundled-features/src/step-dispatcher/mail-runner.ts +2 -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/tags/feature.ts +1 -0
- package/packages/bundled-features/src/template-resolver/api.ts +3 -3
- package/packages/bundled-features/src/template-resolver/changes.json +54 -0
- package/packages/bundled-features/src/template-resolver/document-ops.ts +20 -1
- package/packages/bundled-features/src/template-resolver/feature.ts +2 -2
- 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/layout-source.query.ts +36 -11
- package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +32 -7
- package/packages/bundled-features/src/template-resolver/handlers/patch-draft.write.ts +1 -1
- package/packages/bundled-features/src/template-resolver/handlers/send-test-shared.ts +1 -1
- package/packages/bundled-features/src/template-resolver/handlers/send-test-status.query.ts +22 -19
- 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 +22 -5
- package/packages/bundled-features/src/template-resolver/store-template-resource.ts +68 -19
- 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/i18n.ts +6 -0
- package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor.tsx +19 -6
- package/packages/bundled-features/src/template-resolver/web/qualified-names.ts +1 -0
- 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/feature.ts +1 -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/team-list.query.ts +3 -3
- 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/changes.json +6 -0
- 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 +42 -4
- 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/feature.ts +1 -0
- 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 +36 -0
- package/packages/bundled-features/src/user-data-rights/feature.ts +30 -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 +8 -1
- 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 +16 -0
- package/packages/bundled-features/src/user-data-rights/web/index.ts +6 -0
- package/packages/bundled-features/src/user-profile/handlers/change-email.write.ts +8 -6
- package/packages/bundled-features/src/waitlist/changes.json +18 -0
- package/packages/bundled-features/src/waitlist/feature.ts +1 -0
- 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 +34 -10
- package/packages/bundled-features/src/waitlist/import.ts +23 -3
- package/packages/bundled-features/src/waitlist/legacy-migrate.ts +8 -2
- package/packages/bundled-features/src/waitlist/link-on-signup.ts +7 -1
- package/packages/bundled-features/src/waitlist/options.ts +2 -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/src/feature.ts +19 -5
- package/samples/apps/config-demo/src/app/server.ts +1 -1
- package/samples/apps/styleguide/src/app/client.tsx +2 -0
- package/samples/apps/styleguide/src/app/seed.ts +6 -4
- package/samples/apps/styleguide/src/app/server.ts +6 -3
- package/samples/apps/styleguide/src/features/widgets/feature.ts +15 -4
- package/samples/apps/styleguide/src/features/workboard/feature.ts +185 -0
- package/samples/apps/styleguide/src/features/workboard/i18n.ts +27 -0
- package/samples/apps/styleguide/src/features/workboard/seed.ts +121 -0
- package/samples/apps/styleguide/src/features/workboard/web.ts +13 -0
- package/samples/apps/ui-walkthrough/src/features/rental/i18n.ts +2 -0
- package/samples/apps/ui-walkthrough/src/features/rental/screens.ts +9 -0
- package/samples/apps/use-all-bundled/bin/main.ts +2 -0
- package/samples/apps/use-all-bundled/feature-manifest.json +17 -1
- package/samples/apps/use-all-bundled/kumiko/schema.ts +2 -0
- package/samples/apps/use-all-bundled/kumiko.config.ts +2 -0
- package/samples/apps/use-all-bundled/package.json +2 -1
- package/samples/apps/use-all-bundled/src/app/dev-app-options.ts +2 -0
- package/samples/apps/use-all-bundled/src/app/notes-feature.ts +2 -0
- package/samples/apps/use-all-bundled/src/app/seed.ts +12 -2
- package/samples/apps/use-all-bundled/src/bundled-lists.ts +15 -0
- package/samples/apps/user-data-rights-demo/README.md +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/golden-path/src/feature.ts +2 -1
- 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 +4 -0
- package/samples/recipes/lifecycle-hooks/src/feature.ts +26 -1
- package/samples/recipes/list-table-options/README.md +50 -0
- package/samples/recipes/list-table-options/package.json +11 -0
- package/samples/recipes/list-table-options/src/feature.ts +221 -0
- package/samples/recipes/managed-config/README.md +3 -3
- package/samples/recipes/pdf-audit-export/src/feature.ts +2 -1
- 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 +4 -3
- package/samples/recipes/state-machine/src/reducer.ts +1 -1
- package/samples/recipes/tenant-isolation/README.md +1 -0
- package/samples/recipes/tenant-isolation/src/feature.ts +36 -0
- 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({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Client-safe ledger surface: QN constants, pure recurring helpers
|
|
2
2
|
// (projection + Soll/Ist merge), and Zod payload schemas for client-side
|
|
3
3
|
// pre-validation before dispatch. The full `../ledger` entry also re-exports
|
|
4
|
-
// the feature/handlers/executor, which pull
|
|
4
|
+
// the feature/handlers/executor, which pull the DB driver; importing it in a
|
|
5
5
|
// browser bundle fails on Node builtins. Client screens import dispatch QNs and
|
|
6
6
|
// pure forecast/merge helpers from this module, then dispatch via the renderer's
|
|
7
7
|
// useDispatcher.
|
|
@@ -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
|
}
|
|
@@ -42,12 +42,15 @@ async function loadBlocksFromJson(path: string): Promise<readonly LegalContentBl
|
|
|
42
42
|
|
|
43
43
|
// Boot seed for runProdApp's `seeds: [...]`. The JSON template is authoritative
|
|
44
44
|
// (ifExists "update"), so legally required text changes reach rows seeded once.
|
|
45
|
-
export function legalContentSeed(
|
|
45
|
+
export function legalContentSeed(
|
|
46
|
+
source: LegalContentSeedSource,
|
|
47
|
+
options?: { readonly folder?: string },
|
|
48
|
+
): LegalContentSeedFn {
|
|
46
49
|
return async ({ db }) => {
|
|
47
50
|
const blocks =
|
|
48
51
|
"blocks" in source
|
|
49
52
|
? legalContentBlocksSchema.parse(source.blocks)
|
|
50
53
|
: await loadBlocksFromJson(source.path);
|
|
51
|
-
await seedLegalContentFromJson(db, blocks);
|
|
54
|
+
await seedLegalContentFromJson(db, blocks, options);
|
|
52
55
|
};
|
|
53
56
|
}
|
|
@@ -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,16 @@
|
|
|
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
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"version": "0.389.0",
|
|
10
|
+
"type": "improvement",
|
|
11
|
+
"title": "SMTP authentication is optional",
|
|
12
|
+
"detail": "authUser is no longer a required key and the SMTP password secret no longer counts as missing in readiness. With an empty user the transport connects without auth, which suits internal relays; with a user set, a missing password still fails the send."
|
|
13
|
+
},
|
|
2
14
|
{
|
|
3
15
|
"version": "0.370.0",
|
|
4
16
|
"type": "breaking",
|
|
@@ -6,6 +6,7 @@ const tenantAdminWrite = access.withSystem(access.roles("TenantAdmin"));
|
|
|
6
6
|
const tenantAdminRead = access.roles("TenantAdmin", "SystemAdmin");
|
|
7
7
|
|
|
8
8
|
// required: true ↔ the requireNonEmpty calls in buildSmtpTransport — keep in sync.
|
|
9
|
+
// authUser stays optional: empty means an unauthenticated relay.
|
|
9
10
|
export const SMTP_CONFIG_KEYS = {
|
|
10
11
|
host: createTenantConfig("text", {
|
|
11
12
|
required: true,
|
|
@@ -41,7 +42,6 @@ export const SMTP_CONFIG_KEYS = {
|
|
|
41
42
|
},
|
|
42
43
|
}),
|
|
43
44
|
authUser: createTenantConfig("text", {
|
|
44
|
-
required: true,
|
|
45
45
|
default: "",
|
|
46
46
|
write: tenantAdminWrite,
|
|
47
47
|
read: tenantAdminRead,
|
|
@@ -105,8 +105,7 @@ export function createMailTransportSmtpFeature() {
|
|
|
105
105
|
return `${plaintext.slice(0, 3)}...${plaintext.slice(-2)}`;
|
|
106
106
|
},
|
|
107
107
|
scope: "tenant",
|
|
108
|
-
//
|
|
109
|
-
required: true,
|
|
108
|
+
// Only needed once authUser is set (readPassword throws then), so readiness must not demand it.
|
|
110
109
|
});
|
|
111
110
|
|
|
112
111
|
const configKeys = r.config({ keys: SMTP_CONFIG_KEYS });
|
|
@@ -191,14 +190,13 @@ async function buildSmtpTransport(
|
|
|
191
190
|
"from",
|
|
192
191
|
SMTP_HINT,
|
|
193
192
|
);
|
|
194
|
-
const authUser =
|
|
195
|
-
await ctxConfig(sharedMailTransportSmtpFeature.exports.configKeys.authUser)
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const password = await readPassword(ctx, tenantId);
|
|
193
|
+
const authUser = (
|
|
194
|
+
await ctxConfig(sharedMailTransportSmtpFeature.exports.configKeys.authUser)
|
|
195
|
+
)?.trim();
|
|
196
|
+
const auth =
|
|
197
|
+
authUser === undefined || authUser === ""
|
|
198
|
+
? undefined
|
|
199
|
+
: { user: authUser, pass: await readPassword(ctx, tenantId) };
|
|
202
200
|
|
|
203
201
|
let target: MailConnectTarget;
|
|
204
202
|
try {
|
|
@@ -227,13 +225,16 @@ async function buildSmtpTransport(
|
|
|
227
225
|
port,
|
|
228
226
|
secure,
|
|
229
227
|
from,
|
|
230
|
-
auth
|
|
228
|
+
...(auth && { auth }),
|
|
231
229
|
...(target.servername && { servername: target.servername }),
|
|
232
230
|
});
|
|
233
231
|
}
|
|
234
232
|
|
|
235
233
|
async function readPassword(ctx: MailTransportContext, tenantId: string): Promise<string> {
|
|
236
|
-
const secrets = requireSecretsContext(
|
|
234
|
+
const secrets = requireSecretsContext(
|
|
235
|
+
{ secrets: ctx.secrets, auditUserId: ctx.auditUserId },
|
|
236
|
+
FEATURE_NAME,
|
|
237
|
+
);
|
|
237
238
|
const branded = await secrets.get(tenantId, SMTP_PASSWORD);
|
|
238
239
|
return requireSecretSet(branded, FEATURE_NAME, SMTP_PASSWORD.name).reveal();
|
|
239
240
|
}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.389.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "MANAGED_PAGES_FEATURE names the feature",
|
|
6
|
+
"detail": "The barrel exports the MANAGED_PAGES_FEATURE constant so other code can refer to the feature name without a string literal."
|
|
7
|
+
},
|
|
2
8
|
{
|
|
3
9
|
"version": "0.363.0",
|
|
4
10
|
"type": "improvement",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const MANAGED_PAGES_FEATURE = "managed-pages" as const;
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from "../page-render/index.ts";
|
|
11
11
|
import { rateLimitedTextResponse, type SystemQueryFn } from "../shared/index.ts";
|
|
12
12
|
import { BRANDING_KEYS, BRANDING_QUERY_QN, CUSTOM_CSS_KEY, coerceBranding } from "./branding.ts";
|
|
13
|
+
import { MANAGED_PAGES_FEATURE } from "./constants.ts";
|
|
13
14
|
import { createBrandingQuery } from "./handlers/branding.query.ts";
|
|
14
15
|
import { bySlugQuery } from "./handlers/by-slug.query.ts";
|
|
15
16
|
import { byTenantPublishedQuery } from "./handlers/by-tenant-published.query.ts";
|
|
@@ -153,7 +154,8 @@ export function createManagedPagesFeature(opts: ManagedPagesOptions): FeatureDef
|
|
|
153
154
|
const defaultLang = opts.defaultLang ?? "en";
|
|
154
155
|
const allowCustomCss = opts.allowCustomCss ?? false;
|
|
155
156
|
|
|
156
|
-
return defineFeature(
|
|
157
|
+
return defineFeature(MANAGED_PAGES_FEATURE, (r) => {
|
|
158
|
+
r.adoptsBundledLists();
|
|
157
159
|
r.describe(
|
|
158
160
|
"Tenant-editable, server-rendered public pages with per-tenant branding. Stores one Markdown `page` per `(tenantId, slug, lang)` in the `read_pages` entity table with a `published` gate plus `description`/`ogImage` SEO meta. Registers an anonymous `GET {basePath}/:slug` route that resolves the tenant from the request Host via the app-supplied `resolveApexTenant`, serves only published pages (drafts → 404), renders Markdown through the hardened `page-render` core, and isolates per-tenant content with `Vary: Host`. Ships TenantAdmin/SystemAdmin admin screens (`entityList` + `entityEdit`) backed by convention CRUD handlers (`managed-pages:write:page:{create,update,delete}`, `managed-pages:query:page:{list,detail}`); the app wires nav/workspace onto `managed-pages:screen:page-list`. Also exposes `managed-pages:query:by-tenant-published` (anonymous, SQL-filtered on `published`) for sitemap/discovery consumers such as the `seo` feature. Branding (via `config`, scope tenant): `branding-{title,description,site-url,accent-color,logo-url,layout-preset,custom-css}` keys with write-time validation (hex color, https URLs), a `configEdit` self-service screen (`managed-pages:screen:branding-settings`), and a `managed-pages:query:branding` read that the render path applies as scoped `:root` CSS vars + a logo/title header. Also exposes `managed-pages:write:set` (idempotent slug-keyed upsert, SystemAdmin cross-tenant via `tenantIdOverride`) as a provisioning API. Requires `config` + `anonymousAccess` wired at app bootstrap.",
|
|
159
161
|
);
|
|
@@ -21,6 +21,7 @@ export {
|
|
|
21
21
|
// (`config:write:set`) — single source for the `managed-pages:config:branding-*`
|
|
22
22
|
// strings, so apps + the per-tenant migration never hardcode them.
|
|
23
23
|
export { BRANDING_QN, MANAGED_PAGES_CSS_FEATURE } from "./branding.ts";
|
|
24
|
+
export { MANAGED_PAGES_FEATURE } from "./constants.ts";
|
|
24
25
|
export { createManagedPagesCssFeature } from "./css-gate.ts";
|
|
25
26
|
export {
|
|
26
27
|
createManagedPagesFeature,
|
|
@@ -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,
|
|
@@ -69,7 +69,7 @@ export function createAddNoteHandler(
|
|
|
69
69
|
let authorName: string | null = null;
|
|
70
70
|
try {
|
|
71
71
|
// read_users is tenant-agnostic → ctx.db.unsafeRaw, not the tenant-scoped ctx.db.
|
|
72
|
-
//
|
|
72
|
+
// Postgres aborts the whole tx after any error inside it, even one that's
|
|
73
73
|
// caught — so the lookup runs in a savepoint where available, otherwise directly
|
|
74
74
|
// (pool statements are their own units).
|
|
75
75
|
authorName = await runInSavepointIfSupported(ctx.db.unsafeRaw(), async (sp) => {
|
|
@@ -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
|
});
|
|
@@ -101,6 +101,7 @@ export function createPersonalAccessTokensFeature(
|
|
|
101
101
|
): FeatureDefinition {
|
|
102
102
|
const { scopes } = options;
|
|
103
103
|
return defineFeature(PAT_FEATURE, (r) => {
|
|
104
|
+
r.adoptsBundledLists();
|
|
104
105
|
r.describe(
|
|
105
106
|
"Long-lived, revocable Personal Access Tokens for headless HTTP-API access. Stores SHA-256 token hashes in the `store_api_tokens` direct-write table; the plaintext is returned once at creation. `create`/`revoke`/`mine` manage a user's own tokens and `available-scopes` lists the app-declared scope catalog. Bearer tokens carrying the PAT prefix are resolved before jwt.verify (roles resolved live, granted scopes enforced fail-closed at the API boundary) — registered as an auth-foundation tokenVerifier provider, resolved generically by the middleware. Pass { toggleable: { default: false } } to tier-gate the whole feature.",
|
|
106
107
|
);
|
|
@@ -135,7 +136,7 @@ export function createPersonalAccessTokensFeature(
|
|
|
135
136
|
|
|
136
137
|
// Custom domain-event for cross-instance access-invalidation,
|
|
137
138
|
// mirroring sessions' SESSION_REVOKED_EVENT_SHORT. r.defineEvent
|
|
138
|
-
// registers the schema so ctx.
|
|
139
|
+
// registers the schema so ctx.events.unsafeAppend (revoke) enforces it at
|
|
139
140
|
// append time. revoke-for-user.ts appends via the low-level append()
|
|
140
141
|
// instead (needs to anchor on SYSTEM_TENANT_ID) — same guarantee via a
|
|
141
142
|
// different path. No projection: the payload IS the read, consumed
|