@flowdular/sdk 0.2.4 → 0.3.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/.ai/agents/sandbox/business-manager.md +5 -1
- package/.ai/blueprints/author-spec/README.md +1 -1
- package/.ai/blueprints/author-spec/spec-requirements.yaml +44 -0
- package/.ai/blueprints/author-spec/steps.yaml +5 -5
- package/.ai/blueprints/author-spec/templates/module.yaml +99 -3
- package/.ai/blueprints/edit-module/gates.yaml +4 -0
- package/.ai/blueprints/edit-module/required-files.yaml +9 -0
- package/.ai/blueprints/new-module/gates.yaml +4 -0
- package/.ai/blueprints/new-module/spec-requirements.yaml +2 -2
- package/.ai/blueprints/release/gates.yaml +4 -0
- package/.ai/platform-capabilities.md +128 -0
- package/.ai/policies/capabilities.yaml +130 -3
- package/.ai/policies/path-ownership.yaml +5 -2
- package/.ai/policies/task-budgets.yaml +5 -3
- package/.ai/references/catalog/module.json +4 -4
- package/.ai/references/catalog/package.json +2 -2
- package/.ai/references/catalog/spec/module.yaml +5 -3
- package/.ai/references/catalog/src/platform.ts +2 -0
- package/.ai/references/catalog/src/services/catalog-service.ts +89 -1
- package/.ai/references/catalog/src/services/data-classes.ts +47 -0
- package/.ai/references/catalog/src/services/database-repository.ts +98 -1
- package/.ai/references/catalog/src/services/repository.ts +22 -1
- package/.ai/references/catalog/tests/data-classes.test.ts +157 -0
- package/.ai/references/catalog.provenance.json +12 -10
- package/.ai/skills/README.md +10 -0
- package/.ai/skills/agent-tool-design/SKILL.md +1 -2
- package/.ai/skills/auth-security-review/SKILL.md +1 -1
- package/.ai/skills/business-agent-design/SKILL.md +0 -1
- package/.ai/skills/cli-extension/SKILL.md +1 -1
- package/.ai/skills/deploy-operate/SKILL.md +119 -0
- package/.ai/skills/module-new/SKILL.md +29 -3
- package/.ai/skills/module-update/SKILL.md +9 -3
- package/.ai/skills/perf-audit/SKILL.md +0 -1
- package/.ai/skills/release-eject-pr/SKILL.md +0 -1
- package/.ai/skills/spec-interview/SKILL.md +120 -0
- package/.ai/skills/test-hardening/SKILL.md +1 -0
- package/.ai/skills/ux-design/SKILL.md +34 -3
- package/.ai/skills/variables/SKILL.md +0 -2
- package/.ai/skills/workflow-development/SKILL.md +0 -1
- package/AGENTS.md +4 -0
- package/docs/agent-contract.md +2 -2
- package/docs/design-system.md +185 -31
- package/modules/access/migrations/0001_access_core.down.sql +1 -0
- package/modules/access/migrations/0001_access_core.up.sql +28 -0
- package/modules/access/migrations/README.md +3 -0
- package/modules/access/module.json +33 -0
- package/modules/access/package.json +43 -0
- package/modules/access/spec/module.yaml +225 -0
- package/modules/access/src/acl/permissions.ts +6 -0
- package/modules/access/src/api/endpoints.ts +244 -0
- package/modules/access/src/client/ActivityView.tsrx +245 -0
- package/modules/access/src/client/AttestationsView.tsrx +176 -0
- package/modules/access/src/client/ReviewView.tsrx +586 -0
- package/modules/access/src/client/api.ts +132 -0
- package/modules/access/src/client/contribution.tsrx +41 -0
- package/modules/access/src/client/index.ts +41 -0
- package/modules/access/src/client/navigation.ts +57 -0
- package/modules/access/src/client/presentation.ts +69 -0
- package/modules/access/src/client/state.ts +72 -0
- package/modules/access/src/domain/types.ts +189 -0
- package/modules/access/src/index.ts +38 -0
- package/modules/access/src/platform.ts +60 -0
- package/modules/access/src/server/index.ts +42 -0
- package/modules/access/src/server/runtime.ts +105 -0
- package/modules/access/src/services/access-service.ts +298 -0
- package/modules/access/src/services/audit-window.ts +78 -0
- package/modules/access/src/services/auth-directory.ts +72 -0
- package/modules/access/src/services/changes.ts +91 -0
- package/modules/access/src/services/data-classes.ts +28 -0
- package/modules/access/src/services/database-repository.ts +185 -0
- package/modules/access/src/services/directory.ts +102 -0
- package/modules/access/src/services/index.ts +33 -0
- package/modules/access/src/services/list-exports.ts +150 -0
- package/modules/access/src/services/migration.ts +54 -0
- package/modules/access/src/services/repository.ts +30 -0
- package/modules/access/src/services/review.ts +178 -0
- package/modules/access/translations/en.json +155 -0
- package/modules/access/translations/pl.json +155 -0
- package/modules/agents/migrations/0022_credential_rotation_inventory.down.sql +2 -0
- package/modules/agents/migrations/0022_credential_rotation_inventory.up.sql +7 -0
- package/modules/agents/migrations/0023_agents_retention_indexes.down.sql +2 -0
- package/modules/agents/migrations/0023_agents_retention_indexes.up.sql +9 -0
- package/modules/agents/migrations/0024_agent_meter_refusals.down.sql +3 -0
- package/modules/agents/migrations/0024_agent_meter_refusals.up.sql +22 -0
- package/modules/agents/migrations/0025_agent_list_indexes.down.sql +3 -0
- package/modules/agents/migrations/0025_agent_list_indexes.up.sql +11 -0
- package/modules/agents/module.json +30 -4
- package/modules/agents/package.json +2 -1
- package/modules/agents/spec/module.yaml +63 -5
- package/modules/agents/src/api/endpoints.ts +291 -33
- package/modules/agents/src/cli/commands.json +11 -0
- package/modules/agents/src/cli/index.ts +45 -0
- package/modules/agents/src/client/AgentDashboardWidget.tsrx +19 -11
- package/modules/agents/src/client/AgentDefinitionList.tsrx +53 -10
- package/modules/agents/src/client/AgentPlaygroundView.tsrx +4 -4
- package/modules/agents/src/client/AgentProceduresView.tsrx +2 -2
- package/modules/agents/src/client/AgentRunsView.tsrx +143 -40
- package/modules/agents/src/client/AgentsView.tsrx +109 -33
- package/modules/agents/src/client/api.ts +99 -21
- package/modules/agents/src/client/state.ts +97 -0
- package/modules/agents/src/domain/types.ts +44 -4
- package/modules/agents/src/platform.ts +42 -0
- package/modules/agents/src/server/action-execution.ts +201 -106
- package/modules/agents/src/server/runtime.ts +32 -0
- package/modules/agents/src/services/agent-service.ts +158 -30
- package/modules/agents/src/services/credential-rotation.ts +185 -0
- package/modules/agents/src/services/credential-vault.ts +71 -46
- package/modules/agents/src/services/data-classes.ts +176 -0
- package/modules/agents/src/services/database-repository.ts +393 -101
- package/modules/agents/src/services/metering.ts +111 -0
- package/modules/agents/src/services/migration.ts +109 -0
- package/modules/agents/src/services/module-agent-preflight.ts +17 -9
- package/modules/agents/src/services/notifications.ts +98 -0
- package/modules/agents/src/services/provider-repository.ts +9 -18
- package/modules/agents/src/services/provider-service.ts +1 -9
- package/modules/agents/src/services/reports.ts +107 -0
- package/modules/agents/src/services/repository.ts +86 -6
- package/modules/agents/src/services/usage-service.ts +2 -0
- package/modules/agents/src/services/worker.ts +114 -4
- package/modules/agents/src/settings.ts +14 -3
- package/modules/agents/translations/en.json +14 -4
- package/modules/agents/translations/pl.json +14 -4
- package/modules/approvals/migrations/0001_approvals_core.down.sql +3 -0
- package/modules/approvals/migrations/0001_approvals_core.up.sql +78 -0
- package/modules/approvals/migrations/0002_approvals_expiry_routing_role.down.sql +2 -0
- package/modules/approvals/migrations/0002_approvals_expiry_routing_role.up.sql +19 -0
- package/modules/approvals/migrations/0003_approvals_retention_indexes.down.sql +6 -0
- package/modules/approvals/migrations/0003_approvals_retention_indexes.up.sql +14 -0
- package/modules/approvals/migrations/0004_approvals_inbox_keyset_indexes.down.sql +5 -0
- package/modules/approvals/migrations/0004_approvals_inbox_keyset_indexes.up.sql +12 -0
- package/modules/approvals/migrations/0005_approvals_grant_audit.down.sql +4 -0
- package/modules/approvals/migrations/0005_approvals_grant_audit.up.sql +20 -0
- package/modules/approvals/migrations/README.md +3 -0
- package/modules/approvals/module.json +34 -0
- package/modules/approvals/package.json +43 -0
- package/modules/approvals/spec/module.yaml +327 -0
- package/modules/approvals/src/acl/permissions.ts +7 -0
- package/modules/approvals/src/api/endpoints.ts +457 -0
- package/modules/approvals/src/client/ApprovalRequestDrawer.tsrx +138 -0
- package/modules/approvals/src/client/ApprovalsView.tsrx +562 -0
- package/modules/approvals/src/client/PendingCountWidget.tsrx +52 -0
- package/modules/approvals/src/client/api.ts +203 -0
- package/modules/approvals/src/client/approvals.css +51 -0
- package/modules/approvals/src/client/contribution.tsrx +44 -0
- package/modules/approvals/src/client/index.ts +26 -0
- package/modules/approvals/src/client/navigation.ts +39 -0
- package/modules/approvals/src/client/pending-count.ts +26 -0
- package/modules/approvals/src/client/presentation.ts +52 -0
- package/modules/approvals/src/client/state.ts +141 -0
- package/modules/approvals/src/domain/capability.ts +107 -0
- package/modules/approvals/src/domain/grant.ts +69 -0
- package/modules/approvals/src/domain/types.ts +168 -0
- package/modules/approvals/src/index.ts +61 -0
- package/modules/approvals/src/platform.ts +136 -0
- package/modules/approvals/src/server/index.ts +42 -0
- package/modules/approvals/src/server/runtime.ts +185 -0
- package/modules/approvals/src/services/approvals-service.ts +637 -0
- package/modules/approvals/src/services/callbacks.ts +75 -0
- package/modules/approvals/src/services/data-classes.ts +167 -0
- package/modules/approvals/src/services/database-repository.ts +783 -0
- package/modules/approvals/src/services/eligibility.ts +121 -0
- package/modules/approvals/src/services/expiry-runner.ts +81 -0
- package/modules/approvals/src/services/index.ts +31 -0
- package/modules/approvals/src/services/migration.ts +256 -0
- package/modules/approvals/src/services/notifications.ts +95 -0
- package/modules/approvals/src/services/repository.ts +169 -0
- package/modules/approvals/src/services/service-error.ts +70 -0
- package/modules/approvals/src/settings.ts +102 -0
- package/modules/approvals/translations/en.json +104 -0
- package/modules/approvals/translations/pl.json +104 -0
- package/modules/audit/migrations/0001_audit_core.down.sql +8 -0
- package/modules/audit/migrations/0001_audit_core.up.sql +107 -0
- package/modules/audit/migrations/0002_audit_sweep_routing_role.down.sql +2 -0
- package/modules/audit/migrations/0002_audit_sweep_routing_role.up.sql +21 -0
- package/modules/audit/migrations/0003_audit_export_request.down.sql +12 -0
- package/modules/audit/migrations/0003_audit_export_request.up.sql +40 -0
- package/modules/audit/migrations/0004_audit_hold_seal_erasure.down.sql +17 -0
- package/modules/audit/migrations/0004_audit_hold_seal_erasure.up.sql +192 -0
- package/modules/audit/migrations/0005_audit_event_format.down.sql +17 -0
- package/modules/audit/migrations/0005_audit_event_format.up.sql +60 -0
- package/modules/audit/migrations/0006_audit_own_class_walks.down.sql +3 -0
- package/modules/audit/migrations/0006_audit_own_class_walks.up.sql +14 -0
- package/modules/audit/migrations/README.md +3 -0
- package/modules/audit/module.json +32 -0
- package/modules/audit/package.json +49 -0
- package/modules/audit/spec/module.yaml +809 -0
- package/modules/audit/src/acl/permissions.ts +7 -0
- package/modules/audit/src/api/endpoints.ts +315 -0
- package/modules/audit/src/cli/commands.json +95 -0
- package/modules/audit/src/cli/index.ts +658 -0
- package/modules/audit/src/client/DataClassesView.tsrx +420 -0
- package/modules/audit/src/client/ExportsView.tsrx +256 -0
- package/modules/audit/src/client/HoldsView.tsrx +604 -0
- package/modules/audit/src/client/SweepsView.tsrx +251 -0
- package/modules/audit/src/client/api.ts +185 -0
- package/modules/audit/src/client/contribution.tsrx +52 -0
- package/modules/audit/src/client/index.ts +23 -0
- package/modules/audit/src/client/navigation.ts +74 -0
- package/modules/audit/src/client/presentation.ts +105 -0
- package/modules/audit/src/client/state.ts +86 -0
- package/modules/audit/src/domain/data-classes.ts +16 -0
- package/modules/audit/src/domain/types.ts +339 -0
- package/modules/audit/src/index.ts +76 -0
- package/modules/audit/src/platform.ts +44 -0
- package/modules/audit/src/server/index.ts +177 -0
- package/modules/audit/src/server/runtime.ts +303 -0
- package/modules/audit/src/services/anchor-key.ts +143 -0
- package/modules/audit/src/services/anchor-rotation.ts +147 -0
- package/modules/audit/src/services/audit-runners.ts +278 -0
- package/modules/audit/src/services/backup-guard.ts +95 -0
- package/modules/audit/src/services/database-repository.ts +1946 -0
- package/modules/audit/src/services/declared-classes.ts +64 -0
- package/modules/audit/src/services/erasure-port.ts +126 -0
- package/modules/audit/src/services/erasure-service.ts +770 -0
- package/modules/audit/src/services/export-directory.ts +106 -0
- package/modules/audit/src/services/export-service.ts +714 -0
- package/modules/audit/src/services/hold-service.ts +295 -0
- package/modules/audit/src/services/index.ts +17 -0
- package/modules/audit/src/services/migration.ts +658 -0
- package/modules/audit/src/services/own-classes.ts +318 -0
- package/modules/audit/src/services/platform-version.ts +23 -0
- package/modules/audit/src/services/repository.ts +495 -0
- package/modules/audit/src/services/retention-service.ts +191 -0
- package/modules/audit/src/services/seal-service.ts +769 -0
- package/modules/audit/src/services/service-error.ts +61 -0
- package/modules/audit/src/services/subject-keys.ts +90 -0
- package/modules/audit/src/services/sweep-service.ts +298 -0
- package/modules/audit/src/services/zip.ts +217 -0
- package/modules/audit/src/settings.ts +69 -0
- package/modules/audit/translations/en.json +210 -0
- package/modules/audit/translations/pl.json +210 -0
- package/modules/auth/README.md +77 -29
- package/modules/auth/migrations/0016_external_identities.up.sql +16 -0
- package/modules/auth/migrations/0017_mfa_key_id.up.sql +8 -0
- package/modules/auth/migrations/0018_retire_bundled_module_scopes.up.sql +29 -0
- package/modules/auth/migrations/0019_notifications_member_scopes.up.sql +44 -0
- package/modules/auth/migrations/0020_identity_providers.up.sql +61 -0
- package/modules/auth/migrations/0021_membership_status.up.sql +19 -0
- package/modules/auth/migrations/0022_auth_provider_scopes.up.sql +45 -0
- package/modules/auth/migrations/0023_enterprise_module_scopes.down.sql +19 -0
- package/modules/auth/migrations/0023_enterprise_module_scopes.up.sql +71 -0
- package/modules/auth/migrations/0024_approvals_member_scopes.down.sql +22 -0
- package/modules/auth/migrations/0024_approvals_member_scopes.up.sql +48 -0
- package/modules/auth/migrations/0025_audit_holds_owner_scope.down.sql +21 -0
- package/modules/auth/migrations/0025_audit_holds_owner_scope.up.sql +47 -0
- package/modules/auth/migrations/0026_auth_export_keyset_indexes.down.sql +6 -0
- package/modules/auth/migrations/0026_auth_export_keyset_indexes.up.sql +15 -0
- package/modules/auth/migrations/0027_workflow_automation_profile_scopes.down.sql +34 -0
- package/modules/auth/migrations/0027_workflow_automation_profile_scopes.up.sql +72 -0
- package/modules/auth/migrations/0028_auth_member_search_prefix_indexes.down.sql +6 -0
- package/modules/auth/migrations/0028_auth_member_search_prefix_indexes.up.sql +21 -0
- package/modules/auth/migrations/0029_reports_exports_access_scopes.down.sql +22 -0
- package/modules/auth/migrations/0029_reports_exports_access_scopes.up.sql +56 -0
- package/modules/auth/migrations/0030_auth_membership_keyset_index.down.sql +4 -0
- package/modules/auth/migrations/0030_auth_membership_keyset_index.up.sql +10 -0
- package/modules/auth/migrations/0031_membership_scope_backfills_under_rls.down.sql +4 -0
- package/modules/auth/migrations/0031_membership_scope_backfills_under_rls.up.sql +31 -0
- package/modules/auth/migrations/0032_audit_actor_service.down.sql +9 -0
- package/modules/auth/migrations/0032_audit_actor_service.up.sql +10 -0
- package/modules/auth/migrations/0033_auth_account_keyset_indexes.down.sql +5 -0
- package/modules/auth/migrations/0033_auth_account_keyset_indexes.up.sql +23 -0
- package/modules/auth/module.json +3 -3
- package/modules/auth/package.json +2 -1
- package/modules/auth/spec/module.yaml +372 -3
- package/modules/auth/src/acl/scopes.ts +50 -6
- package/modules/auth/src/cli/commands.json +11 -0
- package/modules/auth/src/cli/greenfield.ts +2 -2
- package/modules/auth/src/cli/index.ts +14 -0
- package/modules/auth/src/cli/secrets.ts +114 -0
- package/modules/auth/src/client/AuthenticationCore.tsrx +61 -8
- package/modules/auth/src/client/api.ts +72 -6
- package/modules/auth/src/client/contribution.tsrx +23 -0
- package/modules/auth/src/client/index.ts +6 -0
- package/modules/auth/src/client/providers/IdentityProviderForm.tsrx +191 -0
- package/modules/auth/src/client/providers/IdentityProvidersView.tsrx +358 -0
- package/modules/auth/src/client/providers/api.ts +128 -0
- package/modules/auth/src/client/providers/provider-columns.tsrx +60 -0
- package/modules/auth/src/client/providers/state.ts +35 -0
- package/modules/auth/src/client/security/SecurityView.tsrx +9 -1
- package/modules/auth/src/client/state.ts +15 -1
- package/modules/auth/src/domain/types.ts +55 -0
- package/modules/auth/src/index.ts +11 -1
- package/modules/auth/src/middleware/authentication.ts +4 -0
- package/modules/auth/src/server/endpoints.ts +467 -171
- package/modules/auth/src/server/http.ts +3 -2
- package/modules/auth/src/server/index.ts +22 -1
- package/modules/auth/src/server/membership-endpoints.ts +62 -0
- package/modules/auth/src/server/mfa-enforcement.ts +156 -0
- package/modules/auth/src/server/oidc.ts +389 -0
- package/modules/auth/src/server/provider-endpoints.ts +246 -0
- package/modules/auth/src/server/provider-host.ts +77 -0
- package/modules/auth/src/server/runtime.ts +231 -55
- package/modules/auth/src/services/auth-service-error.ts +7 -2
- package/modules/auth/src/services/auth-service.ts +948 -51
- package/modules/auth/src/services/data-classes.ts +236 -0
- package/modules/auth/src/services/database-repository.ts +853 -71
- package/modules/auth/src/services/identity-provider-service.ts +571 -0
- package/modules/auth/src/services/mail-delivery.ts +16 -16
- package/modules/auth/src/services/mail-message.ts +54 -0
- package/modules/auth/src/services/mail-port.ts +24 -0
- package/modules/auth/src/services/mail-smtp.ts +85 -0
- package/modules/auth/src/services/mfa-rotation.ts +144 -0
- package/modules/auth/src/services/migration.ts +810 -0
- package/modules/auth/src/services/password.ts +14 -0
- package/modules/auth/src/services/provider-secret-rotation.ts +169 -0
- package/modules/auth/src/services/provider-secrets.ts +136 -0
- package/modules/auth/src/services/repository.ts +321 -5
- package/modules/auth/src/services/session-sweep-runner.ts +73 -0
- package/modules/auth/src/services/settings-store.ts +10 -83
- package/modules/auth/src/services/totp.ts +88 -62
- package/modules/auth/src/services/validation.ts +194 -7
- package/modules/auth/src/settings.ts +18 -0
- package/modules/auth/translations/en.json +84 -1
- package/modules/auth/translations/pl.json +84 -1
- package/modules/automations/migrations/0005_secret_rotation_inventory.down.sql +1 -0
- package/modules/automations/migrations/0005_secret_rotation_inventory.up.sql +6 -0
- package/modules/automations/migrations/0006_automations_list_sort_indexes.down.sql +4 -0
- package/modules/automations/migrations/0006_automations_list_sort_indexes.up.sql +11 -0
- package/modules/automations/module.json +24 -6
- package/modules/automations/package.json +4 -1
- package/modules/automations/spec/module.yaml +81 -7
- package/modules/automations/src/api/endpoints.ts +202 -28
- package/modules/automations/src/cli/commands.json +17 -0
- package/modules/automations/src/cli/index.ts +114 -0
- package/modules/automations/src/client/AutomationScheduleForm.tsrx +77 -19
- package/modules/automations/src/client/AutomationSchedulesView.tsrx +144 -40
- package/modules/automations/src/client/AutomationTriggersView.tsrx +131 -32
- package/modules/automations/src/client/api.ts +62 -19
- package/modules/automations/src/client/presentation.ts +20 -2
- package/modules/automations/src/client/state.ts +84 -0
- package/modules/automations/src/domain/cadence.ts +99 -3
- package/modules/automations/src/domain/cron.ts +366 -0
- package/modules/automations/src/domain/time-zone.ts +61 -0
- package/modules/automations/src/platform.ts +38 -4
- package/modules/automations/src/server/index.ts +11 -0
- package/modules/automations/src/server/runtime.ts +46 -22
- package/modules/{automations-workflows-integration/src/server/adapter.ts → automations/src/server/workflow-target.ts} +13 -14
- package/modules/{automations-workflows-integration/src/server/tools.ts → automations/src/server/workflow-tools.ts} +0 -0
- package/modules/automations/src/services/data-classes.ts +62 -0
- package/modules/automations/src/services/database-repository.ts +152 -38
- package/modules/automations/src/services/migration.ts +119 -9
- package/modules/automations/src/services/repository.ts +52 -1
- package/modules/automations/src/services/schedule-runner.ts +84 -0
- package/modules/automations/src/services/schedule-service.ts +152 -26
- package/modules/automations/src/services/secret-rotation.ts +176 -0
- package/modules/automations/src/services/secret-vault.ts +68 -41
- package/modules/automations/src/services/trigger-service.ts +13 -10
- package/modules/automations/translations/en.json +15 -2
- package/modules/automations/translations/pl.json +15 -2
- package/modules/connectors/migrations/0001_connectors_core.down.sql +3 -0
- package/modules/connectors/migrations/0001_connectors_core.up.sql +72 -0
- package/modules/connectors/migrations/0002_connectors_call_keys.down.sql +3 -0
- package/modules/connectors/migrations/0002_connectors_call_keys.up.sql +18 -0
- package/modules/connectors/migrations/0003_connectors_rotation_inventory.down.sql +2 -0
- package/modules/connectors/migrations/0003_connectors_rotation_inventory.up.sql +19 -0
- package/modules/connectors/migrations/README.md +3 -0
- package/modules/connectors/module.json +32 -0
- package/modules/{automations-workflows-integration → connectors}/package.json +28 -26
- package/modules/connectors/spec/module.yaml +448 -0
- package/modules/connectors/src/acl/permissions.ts +6 -0
- package/modules/connectors/src/agent/tools.ts +180 -0
- package/modules/connectors/src/api/endpoints.ts +572 -0
- package/modules/connectors/src/cli/commands.json +17 -0
- package/modules/connectors/src/cli/index.ts +126 -0
- package/modules/connectors/src/client/ConnectorCallsView.tsrx +358 -0
- package/modules/connectors/src/client/ConnectorForm.tsrx +424 -0
- package/modules/connectors/src/client/ConnectorsView.tsrx +711 -0
- package/modules/connectors/src/client/api.ts +281 -0
- package/modules/connectors/src/client/consent.ts +48 -0
- package/modules/connectors/src/client/contribution.tsrx +37 -0
- package/modules/connectors/src/client/index.ts +22 -0
- package/modules/connectors/src/client/navigation.ts +42 -0
- package/modules/connectors/src/client/presentation.ts +38 -0
- package/modules/connectors/src/client/state.ts +175 -0
- package/modules/connectors/src/domain/calls.ts +70 -0
- package/modules/connectors/src/domain/definitions.ts +190 -0
- package/modules/connectors/src/domain/http-json.ts +68 -0
- package/modules/connectors/src/domain/types.ts +185 -0
- package/modules/connectors/src/index.ts +64 -0
- package/modules/connectors/src/platform.ts +56 -0
- package/modules/connectors/src/server/index.ts +66 -0
- package/modules/connectors/src/server/runtime.ts +163 -0
- package/modules/connectors/src/services/call-service.ts +903 -0
- package/modules/connectors/src/services/connectors-service.ts +653 -0
- package/modules/connectors/src/services/credential-rotation.ts +221 -0
- package/modules/connectors/src/services/credential-vault.ts +194 -0
- package/modules/connectors/src/services/data-classes.ts +158 -0
- package/modules/connectors/src/services/database-repository.ts +897 -0
- package/modules/connectors/src/services/egress.ts +261 -0
- package/modules/connectors/src/services/index.ts +19 -0
- package/modules/connectors/src/services/migration.ts +202 -0
- package/modules/connectors/src/services/repository.ts +175 -0
- package/modules/connectors/src/services/service-error.ts +46 -0
- package/modules/connectors/src/settings.ts +69 -0
- package/modules/connectors/translations/en.json +191 -0
- package/modules/connectors/translations/pl.json +191 -0
- package/modules/directory/migrations/0001_directory_core.down.sql +5 -0
- package/modules/directory/migrations/0001_directory_core.up.sql +111 -0
- package/modules/directory/migrations/README.md +3 -0
- package/modules/directory/module.json +27 -0
- package/modules/directory/package.json +46 -0
- package/modules/directory/spec/module.yaml +351 -0
- package/modules/directory/src/acl/permissions.ts +7 -0
- package/modules/directory/src/api/endpoints.ts +478 -0
- package/modules/directory/src/api/scim-endpoints.ts +516 -0
- package/modules/directory/src/client/GroupMappingsView.tsrx +430 -0
- package/modules/directory/src/client/ProvisioningLogView.tsrx +248 -0
- package/modules/directory/src/client/ScimTokensView.tsrx +587 -0
- package/modules/directory/src/client/api.ts +222 -0
- package/modules/directory/src/client/contribution.tsrx +46 -0
- package/modules/directory/src/client/index.ts +22 -0
- package/modules/directory/src/client/navigation.ts +57 -0
- package/modules/directory/src/client/presentation.ts +143 -0
- package/modules/directory/src/client/state.ts +173 -0
- package/modules/directory/src/domain/scim.ts +346 -0
- package/modules/directory/src/domain/types.ts +166 -0
- package/modules/directory/src/index.ts +44 -0
- package/modules/directory/src/platform.ts +43 -0
- package/modules/directory/src/server/index.ts +17 -0
- package/modules/directory/src/server/runtime.ts +124 -0
- package/modules/directory/src/services/auth-port.ts +127 -0
- package/modules/directory/src/services/data-classes.ts +41 -0
- package/modules/directory/src/services/database-repository.ts +1067 -0
- package/modules/directory/src/services/directory-service.ts +311 -0
- package/modules/directory/src/services/index.ts +43 -0
- package/modules/directory/src/services/migration.ts +146 -0
- package/modules/directory/src/services/provisioning-service.ts +1420 -0
- package/modules/directory/src/services/rate-limiter.ts +103 -0
- package/modules/directory/src/services/repository.ts +208 -0
- package/modules/directory/src/services/service-error.ts +42 -0
- package/modules/directory/src/services/token-service.ts +215 -0
- package/modules/directory/src/settings.ts +86 -0
- package/modules/directory/translations/en.json +185 -0
- package/modules/directory/translations/pl.json +185 -0
- package/modules/documents/migrations/0001_documents_core.down.sql +1 -0
- package/modules/documents/migrations/0001_documents_core.up.sql +35 -0
- package/modules/documents/migrations/0002_documents_files_page.down.sql +1 -0
- package/modules/documents/migrations/0002_documents_files_page.up.sql +6 -0
- package/modules/documents/migrations/0003_documents_rotation_inventory.down.sql +2 -0
- package/modules/documents/migrations/0003_documents_rotation_inventory.up.sql +18 -0
- package/modules/documents/migrations/README.md +3 -0
- package/modules/documents/module.json +32 -0
- package/modules/documents/package.json +50 -0
- package/modules/documents/spec/module.yaml +248 -0
- package/modules/documents/src/acl/permissions.ts +6 -0
- package/modules/documents/src/api/endpoints.ts +401 -0
- package/modules/documents/src/cli/commands.json +17 -0
- package/modules/documents/src/cli/index.ts +145 -0
- package/modules/documents/src/client/DocumentUploader.tsrx +148 -0
- package/modules/documents/src/client/DocumentsView.tsrx +540 -0
- package/modules/documents/src/client/api.ts +209 -0
- package/modules/documents/src/client/contribution.tsrx +29 -0
- package/modules/documents/src/client/download.ts +70 -0
- package/modules/documents/src/client/index.ts +22 -0
- package/modules/documents/src/client/navigation.ts +25 -0
- package/modules/documents/src/client/presentation.ts +110 -0
- package/modules/documents/src/client/state.ts +117 -0
- package/modules/documents/src/domain/attachments.ts +84 -0
- package/modules/documents/src/domain/types.ts +80 -0
- package/modules/documents/src/index.ts +46 -0
- package/modules/documents/src/platform.ts +61 -0
- package/modules/documents/src/server/index.ts +30 -0
- package/modules/documents/src/server/runtime.ts +107 -0
- package/modules/documents/src/services/attachments.ts +39 -0
- package/modules/documents/src/services/data-classes.ts +25 -0
- package/modules/documents/src/services/database-repository.ts +270 -0
- package/modules/documents/src/services/documents-service.ts +590 -0
- package/modules/documents/src/services/index.ts +18 -0
- package/modules/documents/src/services/migration.ts +117 -0
- package/modules/documents/src/services/repository.ts +58 -0
- package/modules/documents/src/services/storage-rotation.ts +157 -0
- package/modules/documents/src/settings.ts +107 -0
- package/modules/documents/translations/en.json +111 -0
- package/modules/documents/translations/pl.json +111 -0
- package/modules/exports/migrations/0001_exports_core.down.sql +2 -0
- package/modules/exports/migrations/0001_exports_core.up.sql +28 -0
- package/modules/exports/migrations/0002_exports_job_routing_role.down.sql +4 -0
- package/modules/exports/migrations/0002_exports_job_routing_role.up.sql +21 -0
- package/modules/exports/migrations/0003_exports_rotation_inventory.down.sql +1 -0
- package/modules/exports/migrations/0003_exports_rotation_inventory.up.sql +9 -0
- package/modules/exports/migrations/README.md +3 -0
- package/modules/exports/module.json +32 -0
- package/modules/exports/package.json +45 -0
- package/modules/exports/spec/module.yaml +230 -0
- package/modules/exports/src/acl/permissions.ts +6 -0
- package/modules/exports/src/api/endpoints.ts +211 -0
- package/modules/exports/src/cli/commands.json +17 -0
- package/modules/exports/src/cli/index.ts +145 -0
- package/modules/exports/src/client/ExportsView.tsrx +327 -0
- package/modules/exports/src/client/api.ts +133 -0
- package/modules/exports/src/client/contribution.tsrx +31 -0
- package/modules/exports/src/client/index.ts +20 -0
- package/modules/exports/src/client/navigation.ts +25 -0
- package/modules/exports/src/client/presentation.ts +53 -0
- package/modules/exports/src/client/state.ts +56 -0
- package/modules/exports/src/domain/lists.ts +29 -0
- package/modules/exports/src/domain/types.ts +120 -0
- package/modules/exports/src/index.ts +41 -0
- package/modules/exports/src/platform.ts +80 -0
- package/modules/exports/src/server/index.ts +50 -0
- package/modules/exports/src/server/runtime.ts +160 -0
- package/modules/exports/src/services/data-classes.ts +59 -0
- package/modules/exports/src/services/database-repository.ts +403 -0
- package/modules/exports/src/services/export-runner.ts +123 -0
- package/modules/exports/src/services/export-service.ts +356 -0
- package/modules/exports/src/services/index.ts +6 -0
- package/modules/exports/src/services/list-registry.ts +105 -0
- package/modules/exports/src/services/migration.ts +116 -0
- package/modules/exports/src/services/repository.ts +96 -0
- package/modules/exports/src/services/storage-rotation.ts +138 -0
- package/modules/exports/src/settings.ts +70 -0
- package/modules/exports/translations/en.json +69 -0
- package/modules/exports/translations/pl.json +69 -0
- package/modules/import/migrations/0001_import_core.down.sql +6 -0
- package/modules/import/migrations/0001_import_core.up.sql +61 -0
- package/modules/import/migrations/0002_import_job_routing_role.down.sql +4 -0
- package/modules/import/migrations/0002_import_job_routing_role.up.sql +20 -0
- package/modules/import/migrations/0003_import_jobs_export_index.down.sql +1 -0
- package/modules/import/migrations/0003_import_jobs_export_index.up.sql +7 -0
- package/modules/import/migrations/0004_import_jobs_traceparent.down.sql +1 -0
- package/modules/import/migrations/0004_import_jobs_traceparent.up.sql +7 -0
- package/modules/import/migrations/README.md +3 -0
- package/modules/import/module.json +37 -0
- package/modules/import/package.json +50 -0
- package/modules/import/spec/module.yaml +335 -0
- package/modules/import/src/acl/permissions.ts +6 -0
- package/modules/import/src/api/endpoints.ts +400 -0
- package/modules/import/src/client/ImportJobDrawer.tsrx +229 -0
- package/modules/import/src/client/ImportsView.tsrx +455 -0
- package/modules/import/src/client/NewImportForm.tsrx +274 -0
- package/modules/import/src/client/api.ts +243 -0
- package/modules/import/src/client/contribution.tsrx +29 -0
- package/modules/import/src/client/index.ts +23 -0
- package/modules/import/src/client/navigation.ts +25 -0
- package/modules/import/src/client/presentation.ts +59 -0
- package/modules/import/src/client/state.ts +43 -0
- package/modules/import/src/domain/csv.ts +258 -0
- package/modules/import/src/domain/ports.ts +157 -0
- package/modules/import/src/domain/types.ts +179 -0
- package/modules/import/src/index.ts +58 -0
- package/modules/import/src/platform.ts +60 -0
- package/modules/import/src/server/index.ts +49 -0
- package/modules/import/src/server/runtime.ts +158 -0
- package/modules/import/src/services/csv-source.ts +121 -0
- package/modules/import/src/services/data-classes.ts +63 -0
- package/modules/import/src/services/database-repository.ts +780 -0
- package/modules/import/src/services/import-runner.ts +127 -0
- package/modules/import/src/services/import-service.ts +872 -0
- package/modules/import/src/services/index.ts +6 -0
- package/modules/import/src/services/migration.ts +156 -0
- package/modules/import/src/services/port-registry.ts +158 -0
- package/modules/import/src/services/repository.ts +147 -0
- package/modules/import/src/settings.ts +70 -0
- package/modules/import/translations/en.json +175 -0
- package/modules/import/translations/pl.json +175 -0
- package/modules/metering/migrations/0001_metering_core.down.sql +6 -0
- package/modules/metering/migrations/0001_metering_core.up.sql +119 -0
- package/modules/metering/migrations/0002_metering_bucket_export.down.sql +3 -0
- package/modules/metering/migrations/0002_metering_bucket_export.up.sql +5 -0
- package/modules/metering/migrations/README.md +3 -0
- package/modules/metering/module.json +46 -0
- package/modules/metering/package.json +50 -0
- package/modules/metering/spec/module.yaml +269 -0
- package/modules/metering/src/acl/permissions.ts +5 -0
- package/modules/metering/src/api/endpoints.ts +104 -0
- package/modules/metering/src/cli/commands.json +28 -0
- package/modules/metering/src/cli/index.ts +246 -0
- package/modules/metering/src/client/LimitsView.tsrx +170 -0
- package/modules/metering/src/client/UsageSummaryWidget.tsrx +63 -0
- package/modules/metering/src/client/UsageView.tsrx +205 -0
- package/modules/metering/src/client/api.ts +91 -0
- package/modules/metering/src/client/contribution.tsrx +42 -0
- package/modules/metering/src/client/index.ts +26 -0
- package/modules/metering/src/client/navigation.ts +54 -0
- package/modules/metering/src/client/presentation.ts +63 -0
- package/modules/metering/src/client/state.ts +31 -0
- package/modules/metering/src/domain/meters.ts +100 -0
- package/modules/metering/src/domain/types.ts +71 -0
- package/modules/metering/src/index.ts +51 -0
- package/modules/metering/src/platform.ts +84 -0
- package/modules/metering/src/server/index.ts +40 -0
- package/modules/metering/src/server/runtime.ts +165 -0
- package/modules/metering/src/services/data-classes.ts +34 -0
- package/modules/metering/src/services/database-repository.ts +574 -0
- package/modules/metering/src/services/index.ts +9 -0
- package/modules/metering/src/services/meter-registry.ts +145 -0
- package/modules/metering/src/services/metering-service.ts +441 -0
- package/modules/metering/src/services/migration.ts +183 -0
- package/modules/metering/src/services/notifications.ts +75 -0
- package/modules/metering/src/services/reports.ts +105 -0
- package/modules/metering/src/services/repository.ts +132 -0
- package/modules/metering/src/services/service-error.ts +46 -0
- package/modules/metering/src/settings.ts +47 -0
- package/modules/metering/translations/en.json +68 -0
- package/modules/metering/translations/pl.json +68 -0
- package/modules/notifications/migrations/0001_notifications_core.down.sql +1 -0
- package/modules/notifications/migrations/0001_notifications_core.up.sql +26 -0
- package/modules/notifications/migrations/0002_notifications_preferences.down.sql +1 -0
- package/modules/notifications/migrations/0002_notifications_preferences.up.sql +17 -0
- package/modules/notifications/migrations/0003_notifications_webhook_subscriptions.down.sql +1 -0
- package/modules/notifications/migrations/0003_notifications_webhook_subscriptions.up.sql +30 -0
- package/modules/notifications/migrations/0004_notifications_deliveries.down.sql +1 -0
- package/modules/notifications/migrations/0004_notifications_deliveries.up.sql +35 -0
- package/modules/notifications/migrations/0005_notifications_delivery_routing_role.down.sql +2 -0
- package/modules/notifications/migrations/0005_notifications_delivery_routing_role.up.sql +17 -0
- package/modules/notifications/migrations/0006_notifications_delivery_title.down.sql +1 -0
- package/modules/notifications/migrations/0006_notifications_delivery_title.up.sql +11 -0
- package/modules/notifications/migrations/0007_notifications_secret_rotation_inventory.down.sql +2 -0
- package/modules/notifications/migrations/0007_notifications_secret_rotation_inventory.up.sql +19 -0
- package/modules/notifications/migrations/0008_notifications_delivery_claim.down.sql +8 -0
- package/modules/notifications/migrations/0008_notifications_delivery_claim.up.sql +19 -0
- package/modules/notifications/migrations/0009_notifications_kind_approvals.down.sql +16 -0
- package/modules/notifications/migrations/0009_notifications_kind_approvals.up.sql +18 -0
- package/modules/notifications/migrations/0010_notifications_kind_meter_threshold.down.sql +16 -0
- package/modules/notifications/migrations/0010_notifications_kind_meter_threshold.up.sql +18 -0
- package/modules/notifications/migrations/0011_notifications_retention_keyset.down.sql +5 -0
- package/modules/notifications/migrations/0011_notifications_retention_keyset.up.sql +15 -0
- package/modules/notifications/migrations/0012_notifications_email_channel.down.sql +12 -0
- package/modules/notifications/migrations/0012_notifications_email_channel.up.sql +38 -0
- package/modules/notifications/migrations/0013_notifications_list_pages.down.sql +5 -0
- package/modules/notifications/migrations/0013_notifications_list_pages.up.sql +13 -0
- package/modules/notifications/migrations/README.md +3 -0
- package/modules/notifications/module.json +32 -0
- package/modules/notifications/package.json +49 -0
- package/modules/notifications/spec/module.yaml +718 -0
- package/modules/notifications/src/acl/permissions.ts +11 -0
- package/modules/notifications/src/api/endpoints.ts +814 -0
- package/modules/notifications/src/cli/commands.json +17 -0
- package/modules/notifications/src/cli/index.ts +126 -0
- package/modules/notifications/src/client/DeliveriesView.tsrx +483 -0
- package/modules/notifications/src/client/FactList.tsrx +24 -0
- package/modules/notifications/src/client/NotificationPreferencesView.tsrx +240 -0
- package/modules/notifications/src/client/NotificationsInboxView.tsrx +592 -0
- package/modules/notifications/src/client/UnreadCountWidget.tsrx +62 -0
- package/modules/notifications/src/client/WebhookForm.tsrx +297 -0
- package/modules/notifications/src/client/WebhooksView.tsrx +637 -0
- package/modules/notifications/src/client/api.ts +382 -0
- package/modules/notifications/src/client/contribution.tsrx +83 -0
- package/modules/notifications/src/client/inbox.ts +41 -0
- package/modules/notifications/src/client/index.ts +29 -0
- package/modules/notifications/src/client/navigation.ts +92 -0
- package/modules/notifications/src/client/notifications.css +70 -0
- package/modules/notifications/src/client/presentation.ts +81 -0
- package/modules/notifications/src/client/state.ts +246 -0
- package/modules/notifications/src/client/unread-count.ts +26 -0
- package/modules/notifications/src/domain/locale.ts +47 -0
- package/modules/notifications/src/domain/publish.ts +46 -0
- package/modules/notifications/src/domain/types.ts +186 -0
- package/modules/notifications/src/index.ts +62 -0
- package/modules/notifications/src/platform.ts +85 -0
- package/modules/notifications/src/server/index.ts +82 -0
- package/modules/notifications/src/server/runtime.ts +251 -0
- package/modules/notifications/src/services/data-classes.ts +139 -0
- package/modules/notifications/src/services/database-repository.ts +1334 -0
- package/modules/notifications/src/services/delivery-payload.ts +93 -0
- package/modules/notifications/src/services/delivery-runner.ts +170 -0
- package/modules/notifications/src/services/delivery-service.ts +811 -0
- package/modules/notifications/src/services/egress.ts +239 -0
- package/modules/notifications/src/services/email-channel.ts +93 -0
- package/modules/notifications/src/services/index.ts +23 -0
- package/modules/notifications/src/services/migration.ts +572 -0
- package/modules/notifications/src/services/notifications-service.ts +297 -0
- package/modules/notifications/src/services/paging.ts +60 -0
- package/modules/notifications/src/services/publish-service.ts +133 -0
- package/modules/notifications/src/services/repository.ts +342 -0
- package/modules/notifications/src/services/secret-rotation.ts +177 -0
- package/modules/notifications/src/services/secret-vault.ts +168 -0
- package/modules/notifications/src/services/service-error.ts +67 -0
- package/modules/notifications/src/services/signature.ts +81 -0
- package/modules/notifications/src/services/webhook-service.ts +307 -0
- package/modules/notifications/src/settings.ts +130 -0
- package/modules/notifications/translations/en.json +255 -0
- package/modules/notifications/translations/pl.json +255 -0
- package/modules/profile/module.json +3 -3
- package/modules/profile/package.json +1 -1
- package/modules/profile/spec/module.yaml +3 -3
- package/modules/profile/src/services/database-repository.ts +3 -13
- package/modules/reports/module.json +28 -0
- package/modules/reports/package.json +42 -0
- package/modules/reports/spec/module.yaml +231 -0
- package/modules/reports/src/acl/permissions.ts +5 -0
- package/modules/reports/src/api/endpoints.ts +72 -0
- package/modules/reports/src/client/ReportsSummaryWidget.tsrx +52 -0
- package/modules/reports/src/client/ReportsView.tsrx +246 -0
- package/modules/reports/src/client/api.ts +65 -0
- package/modules/reports/src/client/contribution.tsrx +36 -0
- package/modules/reports/src/client/index.ts +18 -0
- package/modules/reports/src/client/navigation.ts +39 -0
- package/modules/reports/src/client/presentation.ts +123 -0
- package/modules/reports/src/client/state.ts +28 -0
- package/modules/reports/src/domain/providers.ts +115 -0
- package/modules/reports/src/domain/types.ts +44 -0
- package/modules/reports/src/index.ts +47 -0
- package/modules/reports/src/platform.ts +37 -0
- package/modules/reports/src/server/index.ts +21 -0
- package/modules/reports/src/server/runtime.ts +40 -0
- package/modules/reports/src/services/index.ts +17 -0
- package/modules/reports/src/services/provider-registry.ts +144 -0
- package/modules/reports/src/services/range.ts +65 -0
- package/modules/reports/src/services/reports-service.ts +316 -0
- package/modules/reports/src/services/service-error.ts +26 -0
- package/modules/reports/src/settings.ts +49 -0
- package/modules/reports/translations/en.json +35 -0
- package/modules/reports/translations/pl.json +35 -0
- package/modules/sandbox/module.json +4 -4
- package/modules/sandbox/package.json +1 -1
- package/modules/sandbox/spec/module.yaml +3 -3
- package/modules/sandbox/src/services/database-repository.ts +14 -23
- package/modules/sandbox/src/services/directory.ts +16 -0
- package/modules/sandbox/src/services/sandbox-service.ts +13 -11
- package/modules/search/migrations/0001_search_core.down.sql +1 -0
- package/modules/search/migrations/0001_search_core.up.sql +22 -0
- package/modules/search/migrations/0002_search_recent_export.down.sql +3 -0
- package/modules/search/migrations/0002_search_recent_export.up.sql +5 -0
- package/modules/search/migrations/README.md +3 -0
- package/modules/search/module.json +28 -0
- package/modules/search/package.json +43 -0
- package/modules/search/spec/module.yaml +177 -0
- package/modules/search/src/acl/permissions.ts +5 -0
- package/modules/search/src/api/endpoints.ts +221 -0
- package/modules/search/src/client/SearchView.tsrx +346 -0
- package/modules/search/src/client/api.ts +157 -0
- package/modules/search/src/client/contribution.tsrx +31 -0
- package/modules/search/src/client/index.ts +17 -0
- package/modules/search/src/client/navigation.ts +97 -0
- package/modules/search/src/client/search.css +56 -0
- package/modules/search/src/client/state.ts +113 -0
- package/modules/search/src/domain/data-classes.ts +73 -0
- package/modules/search/src/domain/providers.ts +88 -0
- package/modules/search/src/domain/types.ts +56 -0
- package/modules/search/src/index.ts +46 -0
- package/modules/search/src/platform.ts +42 -0
- package/modules/search/src/server/index.ts +25 -0
- package/modules/search/src/server/runtime.ts +123 -0
- package/modules/search/src/services/database-repository.ts +163 -0
- package/modules/search/src/services/index.ts +21 -0
- package/modules/search/src/services/migration.ts +65 -0
- package/modules/search/src/services/provider-registry.ts +137 -0
- package/modules/search/src/services/repository.ts +43 -0
- package/modules/search/src/services/search-service.ts +442 -0
- package/modules/search/src/services/service-error.ts +35 -0
- package/modules/search/src/settings.ts +68 -0
- package/modules/search/translations/en.json +45 -0
- package/modules/search/translations/pl.json +45 -0
- package/modules/system/module.json +2 -2
- package/modules/system/package.json +1 -1
- package/modules/system/spec/module.yaml +30 -3
- package/modules/system/src/client/FlagsPanel.tsrx +127 -0
- package/modules/system/src/client/ModuleSettingRow.tsrx +17 -7
- package/modules/system/src/client/ModuleSettingsSection.tsrx +10 -71
- package/modules/system/src/client/ModulesView.tsrx +91 -65
- package/modules/system/src/client/flags.ts +35 -0
- package/modules/system/src/client/index.ts +8 -0
- package/modules/system/src/client/navigation.ts +8 -0
- package/modules/system/src/client/settings-state.ts +159 -0
- package/modules/system/src/client/state.ts +2 -0
- package/modules/system/src/domain/time-zone.ts +105 -0
- package/modules/system/src/index.ts +12 -0
- package/modules/system/src/platform.ts +5 -1
- package/modules/system/src/server/endpoints.ts +72 -21
- package/modules/system/src/settings.ts +28 -0
- package/modules/system/translations/en.json +17 -0
- package/modules/system/translations/pl.json +17 -0
- package/modules/users/module.json +30 -4
- package/modules/users/package.json +4 -1
- package/modules/users/spec/module.yaml +290 -7
- package/modules/users/src/api/endpoints.ts +228 -15
- package/modules/users/src/client/InviteMemberForm.tsrx +2 -2
- package/modules/users/src/client/MemberDrawer.tsrx +142 -26
- package/modules/users/src/client/UserAccountForm.tsrx +2 -2
- package/modules/users/src/client/UsersView.tsrx +432 -71
- package/modules/users/src/client/api.ts +106 -12
- package/modules/users/src/client/contribution.tsrx +5 -0
- package/modules/users/src/client/member-columns.tsrx +22 -9
- package/modules/users/src/client/state.ts +172 -7
- package/modules/users/src/domain/lists.ts +9 -0
- package/modules/users/src/index.ts +11 -0
- package/modules/users/src/platform.ts +50 -1
- package/modules/users/src/services/member-export.ts +51 -0
- package/modules/users/src/services/member-import.ts +249 -0
- package/modules/users/src/services/member-search.ts +145 -0
- package/modules/users/src/services/users-service.ts +117 -4
- package/modules/users/translations/en.json +57 -8
- package/modules/users/translations/pl.json +57 -8
- package/modules/workflows/migrations/0004_payload_rotation_inventory.up.sql +17 -0
- package/modules/workflows/migrations/0005_workflows_human_approval.up.sql +28 -0
- package/modules/workflows/migrations/0006_workflows_retention_indexes.up.sql +11 -0
- package/modules/workflows/migrations/0007_workflows_erasure_subject_indexes.up.sql +17 -0
- package/modules/workflows/migrations/0008_workflows_subject_account.up.sql +24 -0
- package/modules/workflows/migrations/0009_workflows_definition_list_indexes.up.sql +9 -0
- package/modules/workflows/module.json +34 -6
- package/modules/workflows/package.json +2 -1
- package/modules/workflows/spec/module.yaml +26 -9
- package/modules/workflows/src/api/endpoints.ts +131 -25
- package/modules/workflows/src/cli/commands.json +17 -0
- package/modules/workflows/src/cli/index.ts +114 -0
- package/modules/workflows/src/client/WorkflowExecutionHistory.tsrx +36 -27
- package/modules/workflows/src/client/WorkflowInspector.tsrx +116 -0
- package/modules/workflows/src/client/WorkflowNodePalette.tsrx +1 -0
- package/modules/workflows/src/client/WorkflowsView.tsrx +236 -99
- package/modules/workflows/src/client/api.ts +54 -34
- package/modules/workflows/src/client/canvas-model.ts +12 -0
- package/modules/workflows/src/client/state.ts +95 -0
- package/modules/workflows/src/domain/events.ts +25 -11
- package/modules/workflows/src/domain/graph.ts +135 -1
- package/modules/workflows/src/domain/types.ts +91 -4
- package/modules/workflows/src/platform.ts +12 -0
- package/modules/workflows/src/server/runtime.ts +78 -5
- package/modules/workflows/src/services/approvals.ts +62 -0
- package/modules/workflows/src/services/cursors.ts +37 -0
- package/modules/workflows/src/services/data-classes.ts +232 -0
- package/modules/workflows/src/services/database-repository.ts +660 -171
- package/modules/workflows/src/services/migration.ts +240 -0
- package/modules/workflows/src/services/notifications.ts +98 -0
- package/modules/workflows/src/services/payload-codec.ts +51 -28
- package/modules/workflows/src/services/payload-rotation.ts +171 -0
- package/modules/workflows/src/services/repository.ts +123 -8
- package/modules/workflows/src/services/simulation.ts +7 -0
- package/modules/workflows/src/services/worker.ts +446 -25
- package/modules/workflows/src/services/workflows-service.ts +264 -103
- package/modules/workflows/translations/en.json +12 -4
- package/modules/workflows/translations/pl.json +12 -4
- package/modules.json +48 -4
- package/package.json +64 -6
- package/packages/client/package.json +1 -0
- package/packages/client/src/ApplicationShell.tsrx +43 -0
- package/packages/client/src/contributions.ts +55 -0
- package/packages/client/src/i18n/locales/en.json +5 -0
- package/packages/client/src/i18n/locales/pl.json +5 -0
- package/packages/client/src/index.ts +13 -1
- package/packages/client/src/routing.ts +22 -0
- package/packages/client/src/shell/CommandPalette.tsrx +223 -11
- package/packages/client/src/shell/command-search.ts +126 -0
- package/packages/client/src/shell/command.ts +30 -0
- package/packages/client/src/shell/navigation.ts +12 -0
- package/packages/client/src/shell/palette-keys.ts +27 -0
- package/packages/client/src/shell/shell.css +7 -0
- package/packages/contracts/schemas/module-spec.schema.json +287 -1
- package/packages/contracts/schemas/module.schema.json +25 -0
- package/packages/contracts/src/index.ts +144 -1
- package/packages/database/src/backup.ts +180 -0
- package/packages/database/src/decoders.ts +25 -0
- package/packages/database/src/index.ts +17 -0
- package/packages/database/src/migrations.ts +7 -0
- package/packages/database/src/record-history.ts +5 -14
- package/packages/harness/src/index.ts +2 -0
- package/packages/harness/src/runtime.ts +378 -2
- package/packages/harness/src/tool-adapters.ts +20 -9
- package/packages/kernel/src/acl.ts +179 -0
- package/packages/kernel/src/approval-grant.ts +310 -0
- package/packages/kernel/src/capability-registry.ts +69 -2
- package/packages/kernel/src/data-class-registry.ts +354 -0
- package/packages/kernel/src/index.ts +85 -4
- package/packages/kernel/src/keyring.ts +208 -0
- package/packages/kernel/src/module-compatibility.ts +35 -4
- package/packages/kernel/src/module-registry.ts +49 -2
- package/packages/kernel/src/module-settings.ts +189 -27
- package/packages/server/package.json +2 -1
- package/packages/server/src/composition.ts +38 -0
- package/packages/server/src/endpoint.ts +104 -47
- package/packages/server/src/export/csv.ts +34 -0
- package/packages/server/src/export/definition.ts +346 -0
- package/packages/server/src/export/index.ts +61 -0
- package/packages/server/src/export/run.ts +116 -0
- package/packages/server/src/index.ts +155 -0
- package/packages/server/src/jobs/index.ts +93 -0
- package/packages/server/src/jobs/runner.ts +486 -0
- package/packages/server/src/log.ts +291 -0
- package/packages/server/src/mail/config.ts +184 -0
- package/packages/server/src/mail/contracts.ts +251 -0
- package/packages/server/src/mail/index.ts +31 -0
- package/packages/server/src/mail/port.ts +101 -0
- package/packages/server/src/mail/smtp.ts +191 -0
- package/packages/server/src/mail/template.ts +105 -0
- package/packages/server/src/metrics.ts +447 -0
- package/packages/server/src/pagination.ts +246 -0
- package/packages/server/src/trace/context.ts +103 -0
- package/packages/server/src/trace/egress.ts +66 -0
- package/packages/server/src/trace/error-sink.ts +296 -0
- package/packages/server/src/trace/exporter.ts +295 -0
- package/packages/server/src/trace/index.ts +60 -0
- package/packages/server/src/trace/job-sink.ts +172 -0
- package/packages/server/src/trace/tracer.ts +338 -0
- package/packages/server/src/web.ts +5 -1
- package/packages/storage/package.json +40 -0
- package/packages/storage/src/config.ts +189 -0
- package/packages/storage/src/content-type.ts +124 -0
- package/packages/storage/src/contracts.ts +112 -0
- package/packages/storage/src/envelope.ts +238 -0
- package/packages/storage/src/index.ts +49 -0
- package/packages/storage/src/local.ts +75 -0
- package/packages/storage/src/port.ts +262 -0
- package/packages/storage/src/read-token.ts +130 -0
- package/packages/storage/src/reseal.ts +128 -0
- package/packages/storage/src/s3.ts +144 -0
- package/packages/storage/src/scanner.ts +22 -0
- package/packages/storage/src/sigv4.ts +104 -0
- package/packages/storage/src/store.ts +13 -0
- package/packages/ui/package.json +1 -0
- package/packages/ui/src/components/Button.tsrx +3 -0
- package/packages/ui/src/components/ConfirmDialog.tsrx +13 -2
- package/packages/ui/src/components/DateField.tsrx +92 -0
- package/packages/ui/src/components/DatePicker.tsrx +525 -0
- package/packages/ui/src/components/DateRangeField.tsrx +93 -0
- package/packages/ui/src/components/Drawer.tsrx +10 -1
- package/packages/ui/src/components/FileUpload.tsrx +158 -0
- package/packages/ui/src/components/Pagination.tsrx +130 -0
- package/packages/ui/src/components/Select.tsrx +78 -0
- package/packages/ui/src/components/Table.tsrx +498 -81
- package/packages/ui/src/components/Tabs.tsrx +53 -0
- package/packages/ui/src/components/ToastHost.tsrx +48 -0
- package/packages/ui/src/components/calendar.ts +243 -0
- package/packages/ui/src/components/date-field.ts +62 -0
- package/packages/ui/src/components/file-upload.ts +49 -0
- package/packages/ui/src/components/focus-trap.ts +62 -0
- package/packages/ui/src/components/pagination.ts +68 -0
- package/packages/ui/src/components/table-sorting.ts +49 -0
- package/packages/ui/src/components/tabs.ts +61 -0
- package/packages/ui/src/components/toast-store.ts +116 -0
- package/packages/ui/src/icons/Icon.tsrx +5 -0
- package/packages/ui/src/index.ts +45 -0
- package/packages/ui/src/styles/components.css +598 -0
- package/modules/automations-workflows-integration/module.json +0 -27
- package/modules/automations-workflows-integration/spec/module.yaml +0 -93
- package/modules/automations-workflows-integration/src/index.ts +0 -14
- package/modules/automations-workflows-integration/src/platform.ts +0 -24
- package/modules/automations-workflows-integration/src/server/index.ts +0 -11
- package/modules/automations-workflows-integration/translations/en.json +0 -21
- package/modules/automations-workflows-integration/translations/pl.json +0 -21
- package/modules/workflows/src/services/cursor-codec.ts +0 -62
|
@@ -0,0 +1,811 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import type { IncomingMessage } from 'node:http';
|
|
3
|
+
import { Agent, request as httpsRequest } from 'node:https';
|
|
4
|
+
import { MailError, type MailPort } from '@flowdular/sdk/server';
|
|
5
|
+
import { NOTIFICATIONS_PERMISSIONS } from '../acl/permissions.ts';
|
|
6
|
+
import { DEFAULT_MAIL_LOCALE } from '../domain/locale.ts';
|
|
7
|
+
import {
|
|
8
|
+
DELIVERY_STATUSES,
|
|
9
|
+
type DeliveryAttempt,
|
|
10
|
+
type DeliveryErrorClass,
|
|
11
|
+
type DeliveryRouting,
|
|
12
|
+
type NotificationsInbox,
|
|
13
|
+
} from '../domain/types.ts';
|
|
14
|
+
import {
|
|
15
|
+
emailPayloadFingerprint,
|
|
16
|
+
webhookPayloadFingerprint,
|
|
17
|
+
} from './delivery-payload.ts';
|
|
18
|
+
import {
|
|
19
|
+
emailDeliveryAttempt,
|
|
20
|
+
notificationMailMessage,
|
|
21
|
+
} from './email-channel.ts';
|
|
22
|
+
import {
|
|
23
|
+
normalizeHost,
|
|
24
|
+
pinnedLookup,
|
|
25
|
+
WebhookEgressError,
|
|
26
|
+
type ResolvedAddress,
|
|
27
|
+
type WebhookEgressPolicy,
|
|
28
|
+
} from './egress.ts';
|
|
29
|
+
import {
|
|
30
|
+
listPage,
|
|
31
|
+
timeKey,
|
|
32
|
+
type ListPageInput,
|
|
33
|
+
type ListResult,
|
|
34
|
+
} from './paging.ts';
|
|
35
|
+
import type {
|
|
36
|
+
DeliveryFilters,
|
|
37
|
+
NotificationsRepository,
|
|
38
|
+
StoredWebhookSubscription,
|
|
39
|
+
} from './repository.ts';
|
|
40
|
+
import { secretContext, type SecretVault } from './secret-vault.ts';
|
|
41
|
+
import { bounded, NotificationsServiceError, oneOf } from './service-error.ts';
|
|
42
|
+
import { webhookSignatureHeader } from './signature.ts';
|
|
43
|
+
|
|
44
|
+
/** Hard request bounds. Neither is configurable: they are the egress contract. */
|
|
45
|
+
export const DELIVERY_TIMEOUT_MS = 10_000;
|
|
46
|
+
export const DELIVERY_RESPONSE_CAP_BYTES = 64 * 1_024;
|
|
47
|
+
/** First retry gap; each further attempt doubles it up to the tenant maximum. */
|
|
48
|
+
export const DELIVERY_BASE_BACKOFF_MS = 60_000;
|
|
49
|
+
/**
|
|
50
|
+
* A claim older than this was abandoned by a process that died mid-send: the
|
|
51
|
+
* request bound plus room for the ledger write that follows it. Another process
|
|
52
|
+
* takes such an attempt over instead of leaving it stranded.
|
|
53
|
+
*/
|
|
54
|
+
export const DELIVERY_CLAIM_TIMEOUT_MS = DELIVERY_TIMEOUT_MS + 30_000;
|
|
55
|
+
/**
|
|
56
|
+
* How far a held attempt is parked when its subscription is not active. Long
|
|
57
|
+
* enough that a paused backlog cannot keep taking the routing page from every
|
|
58
|
+
* other tenant, short enough that a resumed subscription drains within a minute.
|
|
59
|
+
*/
|
|
60
|
+
export const DELIVERY_HOLD_MS = 60_000;
|
|
61
|
+
/**
|
|
62
|
+
* Routing rows one tick inspects per queue read, and rows one retention pass
|
|
63
|
+
* removes.
|
|
64
|
+
*/
|
|
65
|
+
export const DELIVERY_TICK_LIMIT = 50;
|
|
66
|
+
export const RETENTION_BATCH_LIMIT = 500;
|
|
67
|
+
export const RETENTION_TENANT_LIMIT = 100;
|
|
68
|
+
export const DELIVERY_SEARCH_MAX = 80;
|
|
69
|
+
|
|
70
|
+
export interface TenantDeliverySettings {
|
|
71
|
+
readonly retryMaxAttempts: number;
|
|
72
|
+
readonly retryMaxBackoffMinutes: number;
|
|
73
|
+
readonly retentionDays: number;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** A tenant member and the scopes it currently holds, from auth.core. */
|
|
77
|
+
export interface TenantMemberScopes {
|
|
78
|
+
readonly accountId: string;
|
|
79
|
+
/** Resolved at send time, never copied into this module's tables. */
|
|
80
|
+
readonly email: string;
|
|
81
|
+
readonly scopes: readonly string[];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* One workspace's members as one pass sees them. auth.core is asked once per
|
|
86
|
+
* tenant per pass; the index is what keeps addressing an attempt O(1) rather
|
|
87
|
+
* than a scan of the workspace per queued message.
|
|
88
|
+
*/
|
|
89
|
+
interface TenantMembers {
|
|
90
|
+
readonly list: readonly TenantMemberScopes[];
|
|
91
|
+
readonly byAccount: ReadonlyMap<string, TenantMemberScopes>;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
type MemberLookup = (tenantId: string) => Promise<TenantMembers>;
|
|
95
|
+
|
|
96
|
+
/** One pass over the delivery queue, and the workspace lookups it holds. */
|
|
97
|
+
export interface DeliveryPass {
|
|
98
|
+
/**
|
|
99
|
+
* Reads one routing row again under the workspace it named and takes it. Null
|
|
100
|
+
* for an attempt that moved since the routing read, or one another process is
|
|
101
|
+
* already sending: neither is this pass's work, and neither is an attempt it
|
|
102
|
+
* has to report as one.
|
|
103
|
+
*/
|
|
104
|
+
claim(routing: DeliveryRouting, at: number): Promise<DeliveryAttempt | null>;
|
|
105
|
+
/** Sends one attempt this pass claimed and records what came back. */
|
|
106
|
+
deliver(attempt: DeliveryAttempt, at: number): Promise<void>;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface DeliveryOutcome {
|
|
110
|
+
readonly status: 'succeeded' | 'failed';
|
|
111
|
+
readonly responseStatus: number | null;
|
|
112
|
+
readonly errorClass: DeliveryErrorClass | null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface DeliveryTransport {
|
|
116
|
+
send(request: {
|
|
117
|
+
readonly url: string;
|
|
118
|
+
readonly body: string;
|
|
119
|
+
readonly timestamp: string;
|
|
120
|
+
readonly signature: string;
|
|
121
|
+
/** The addresses the policy accepted; the socket may reach no other. */
|
|
122
|
+
readonly addresses: readonly ResolvedAddress[];
|
|
123
|
+
}): Promise<DeliveryOutcome>;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Test seam for the outbound socket; never reachable from configuration. A
|
|
128
|
+
* deployment leaves it unset, so a delivery dials the address the policy
|
|
129
|
+
* verified and trusts the system store. A test maps that address onto the
|
|
130
|
+
* loopback port its server listens on and adds that server's certificate, which
|
|
131
|
+
* runs the delivery path with exactly the address rules a deployment has. `ca`
|
|
132
|
+
* only ever widens trust: there is no form of this seam that turns verification
|
|
133
|
+
* off.
|
|
134
|
+
*/
|
|
135
|
+
export interface WebhookConnectSeam {
|
|
136
|
+
readonly dial?: ((address: string) => string) | undefined;
|
|
137
|
+
readonly ca?: string | undefined;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export interface DeliveryServiceOptions {
|
|
141
|
+
readonly repository: NotificationsRepository;
|
|
142
|
+
readonly vault: SecretVault;
|
|
143
|
+
readonly policy: () => WebhookEgressPolicy;
|
|
144
|
+
readonly settings: (
|
|
145
|
+
tenantId: string,
|
|
146
|
+
) => TenantDeliverySettings | Promise<TenantDeliverySettings>;
|
|
147
|
+
readonly members: (
|
|
148
|
+
tenantId: string,
|
|
149
|
+
) => Promise<readonly TenantMemberScopes[]>;
|
|
150
|
+
/** Platform-owned outbound mail; the e-mail channel sends through it. */
|
|
151
|
+
readonly mail: MailPort;
|
|
152
|
+
/** The language one workspace's messages say they are written in. */
|
|
153
|
+
readonly locale?: (tenantId: string) => string | Promise<string>;
|
|
154
|
+
readonly now?: () => number;
|
|
155
|
+
readonly transport?: DeliveryTransport;
|
|
156
|
+
readonly connect?: WebhookConnectSeam | undefined;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function classifyResponse(status: number): DeliveryOutcome {
|
|
160
|
+
if (status >= 200 && status < 300) {
|
|
161
|
+
return { status: 'succeeded', responseStatus: status, errorClass: null };
|
|
162
|
+
}
|
|
163
|
+
/* A redirect is a refusal, not a hop: node never follows one, so the 3xx comes
|
|
164
|
+
back and the delivery is recorded as refused rather than chased. */
|
|
165
|
+
const errorClass: DeliveryErrorClass =
|
|
166
|
+
status >= 500
|
|
167
|
+
? 'response-5xx'
|
|
168
|
+
: status >= 400
|
|
169
|
+
? 'response-4xx'
|
|
170
|
+
: status >= 300
|
|
171
|
+
? 'egress-refused'
|
|
172
|
+
: 'network';
|
|
173
|
+
return { status: 'failed', responseStatus: status, errorClass };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function failed(errorClass: DeliveryErrorClass): DeliveryOutcome {
|
|
177
|
+
return { status: 'failed', responseStatus: null, errorClass };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* An e-mail failure no later pass would survive. The ledger records the class
|
|
182
|
+
* and the attempt ends there instead of spending the whole retry budget.
|
|
183
|
+
*/
|
|
184
|
+
interface PermanentEmailFailure {
|
|
185
|
+
readonly deadLetter: DeliveryErrorClass;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const UNREACHABLE_RECIPIENT: PermanentEmailFailure = {
|
|
189
|
+
deadLetter: 'recipient-unknown',
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
/* A rejected message is these bytes being unacceptable to the port, so every
|
|
193
|
+
attempt of the same message is refused the same way and the first one ends
|
|
194
|
+
it. A deployment that composed no transport may still compose one, and a
|
|
195
|
+
transport failure is the relay being unreachable; both keep the budget a
|
|
196
|
+
network error gets, under the class the ledger shows. */
|
|
197
|
+
function classifyMailError(
|
|
198
|
+
error: unknown,
|
|
199
|
+
): DeliveryOutcome | PermanentEmailFailure {
|
|
200
|
+
if (error instanceof MailError) {
|
|
201
|
+
if (error.code === 'MAIL_MESSAGE_REJECTED') {
|
|
202
|
+
return { deadLetter: 'mail-refused' };
|
|
203
|
+
}
|
|
204
|
+
return failed(
|
|
205
|
+
error.code === 'MAIL_DELIVERY_FAILED' ? 'network' : 'mail-refused',
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
return failed('network');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function classifyError(error: unknown): DeliveryOutcome {
|
|
212
|
+
if (error instanceof WebhookEgressError) {
|
|
213
|
+
return failed(
|
|
214
|
+
error.code === 'WEBHOOK_HOST_UNRESOLVED' ? 'dns' : 'egress-refused',
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
const name = (error as { name?: unknown })?.name;
|
|
218
|
+
if (name === 'TimeoutError' || name === 'AbortError')
|
|
219
|
+
return failed('timeout');
|
|
220
|
+
/* node reports the resolver failure on the error, fetch reported it on the
|
|
221
|
+
cause; both shapes are read so the class stays the same either way. */
|
|
222
|
+
const failure = error as { code?: unknown; cause?: { code?: unknown } };
|
|
223
|
+
const code = String(failure?.code ?? failure?.cause?.code);
|
|
224
|
+
if (code === 'ENOTFOUND' || code === 'EAI_AGAIN') return failed('dns');
|
|
225
|
+
return failed('network');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Reads at most the cap and drops the rest. The body is never stored; it is
|
|
230
|
+
* consumed only so the connection is released instead of left half-open.
|
|
231
|
+
*/
|
|
232
|
+
async function drainResponse(response: IncomingMessage): Promise<void> {
|
|
233
|
+
let read = 0;
|
|
234
|
+
try {
|
|
235
|
+
for await (const chunk of response) {
|
|
236
|
+
read += (chunk as Buffer).byteLength;
|
|
237
|
+
if (read >= DELIVERY_RESPONSE_CAP_BYTES) return;
|
|
238
|
+
}
|
|
239
|
+
} finally {
|
|
240
|
+
response.destroy();
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** Reads as an abort so the classifier records a timeout, not a reset. */
|
|
245
|
+
function abortFailure(): Error {
|
|
246
|
+
const error = new Error('The webhook delivery was aborted.');
|
|
247
|
+
error.name = 'AbortError';
|
|
248
|
+
return error;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* One request over TLS to an address the egress policy already accepted. The
|
|
253
|
+
* agent is built per delivery and destroyed with it: a pooled socket would
|
|
254
|
+
* outlive the address check that admitted it, which is the window this closes.
|
|
255
|
+
*/
|
|
256
|
+
function sendPinned(
|
|
257
|
+
target: URL,
|
|
258
|
+
init: {
|
|
259
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
260
|
+
readonly body: string;
|
|
261
|
+
readonly addresses: readonly ResolvedAddress[];
|
|
262
|
+
readonly signal: AbortSignal;
|
|
263
|
+
readonly ca?: string | undefined;
|
|
264
|
+
},
|
|
265
|
+
): Promise<number> {
|
|
266
|
+
const agent = new Agent({
|
|
267
|
+
keepAlive: false,
|
|
268
|
+
maxSockets: 1,
|
|
269
|
+
lookup: pinnedLookup(normalizeHost(target.hostname), init.addresses),
|
|
270
|
+
...(init.ca === undefined ? {} : { ca: init.ca }),
|
|
271
|
+
});
|
|
272
|
+
return new Promise<number>((resolve, reject) => {
|
|
273
|
+
const fail = (error: unknown) =>
|
|
274
|
+
reject(init.signal.aborted ? abortFailure() : error);
|
|
275
|
+
const request = httpsRequest(target, {
|
|
276
|
+
method: 'POST',
|
|
277
|
+
agent,
|
|
278
|
+
/* An explicit length rather than a chunked body: a customer endpoint
|
|
279
|
+
that refuses chunked requests must still see the same request it saw
|
|
280
|
+
before the transport changed. */
|
|
281
|
+
headers: {
|
|
282
|
+
...init.headers,
|
|
283
|
+
'content-length': String(Buffer.byteLength(init.body, 'utf8')),
|
|
284
|
+
},
|
|
285
|
+
signal: init.signal,
|
|
286
|
+
});
|
|
287
|
+
request.on('error', fail);
|
|
288
|
+
request.on('response', (response) => {
|
|
289
|
+
/* The drain shares this rejection: an endpoint that answers with headers
|
|
290
|
+
and then stalls its body makes the read reject on the request
|
|
291
|
+
deadline, and that rejection is this attempt's outcome, not the whole
|
|
292
|
+
pass's. */
|
|
293
|
+
void drainResponse(response).then(
|
|
294
|
+
() => resolve(response.statusCode ?? 0),
|
|
295
|
+
fail,
|
|
296
|
+
);
|
|
297
|
+
});
|
|
298
|
+
request.write(init.body);
|
|
299
|
+
request.end();
|
|
300
|
+
}).finally(() => agent.destroy());
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/* The verified addresses as they are dialled. A deployment dials exactly what
|
|
304
|
+
it verified; the seam is what lets a test reach its own server. */
|
|
305
|
+
function dialable(
|
|
306
|
+
connect: WebhookConnectSeam,
|
|
307
|
+
addresses: readonly ResolvedAddress[],
|
|
308
|
+
): readonly ResolvedAddress[] {
|
|
309
|
+
const dial = connect.dial;
|
|
310
|
+
if (!dial) return addresses;
|
|
311
|
+
return addresses.map((entry) => ({ address: dial(entry.address) }));
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export function createDeliveryTransport(
|
|
315
|
+
connect: WebhookConnectSeam = {},
|
|
316
|
+
): DeliveryTransport {
|
|
317
|
+
return {
|
|
318
|
+
async send(request) {
|
|
319
|
+
try {
|
|
320
|
+
const status = await sendPinned(new URL(request.url), {
|
|
321
|
+
addresses: dialable(connect, request.addresses),
|
|
322
|
+
/* One deadline over the headers and the body, so an endpoint that
|
|
323
|
+
answers and then stalls mid-body is still bounded. */
|
|
324
|
+
signal: AbortSignal.timeout(DELIVERY_TIMEOUT_MS),
|
|
325
|
+
headers: {
|
|
326
|
+
'content-type': 'application/json',
|
|
327
|
+
'x-flowdular-signature': request.signature,
|
|
328
|
+
'x-flowdular-timestamp': request.timestamp,
|
|
329
|
+
},
|
|
330
|
+
body: request.body,
|
|
331
|
+
...(connect.ca === undefined ? {} : { ca: connect.ca }),
|
|
332
|
+
});
|
|
333
|
+
return classifyResponse(status);
|
|
334
|
+
} catch (error) {
|
|
335
|
+
return classifyError(error);
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Drains the delivery queue. The cross-tenant poll reads routing columns on the
|
|
343
|
+
* read-only background lease; every attempt is then read, sent and written
|
|
344
|
+
* again under the tenant the routing row named.
|
|
345
|
+
*/
|
|
346
|
+
export class DeliveryService {
|
|
347
|
+
readonly #options: DeliveryServiceOptions;
|
|
348
|
+
readonly #transport: DeliveryTransport;
|
|
349
|
+
readonly #now: () => number;
|
|
350
|
+
readonly #locale: (tenantId: string) => string | Promise<string>;
|
|
351
|
+
/** Last tenant a retention pass reached; '' restarts the rotation. */
|
|
352
|
+
#retentionCursor = '';
|
|
353
|
+
|
|
354
|
+
constructor(options: DeliveryServiceOptions) {
|
|
355
|
+
this.#options = options;
|
|
356
|
+
this.#transport =
|
|
357
|
+
options.transport ?? createDeliveryTransport(options.connect);
|
|
358
|
+
this.#now = options.now ?? Date.now;
|
|
359
|
+
this.#locale = options.locale ?? (() => DEFAULT_MAIL_LOCALE);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
async list(
|
|
363
|
+
tenantId: string,
|
|
364
|
+
filters: DeliveryFilters = {},
|
|
365
|
+
page: ListPageInput<number> = {},
|
|
366
|
+
): Promise<readonly DeliveryAttempt[]> {
|
|
367
|
+
return (await this.listPage(tenantId, filters, page)).items;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/** One page of the ledger, latest schedule first unless asked otherwise. */
|
|
371
|
+
async listPage(
|
|
372
|
+
tenantId: string,
|
|
373
|
+
filters: DeliveryFilters = {},
|
|
374
|
+
page: ListPageInput<number> = {},
|
|
375
|
+
): Promise<ListResult<DeliveryAttempt, number>> {
|
|
376
|
+
const result = await this.#options.repository.listDeliveries(
|
|
377
|
+
bounded(tenantId, 'tenantId', 1, 128),
|
|
378
|
+
{
|
|
379
|
+
status: filters.status
|
|
380
|
+
? oneOf(filters.status, 'status', DELIVERY_STATUSES)
|
|
381
|
+
: undefined,
|
|
382
|
+
subscriptionId: filters.subscriptionId
|
|
383
|
+
? bounded(filters.subscriptionId, 'subscriptionId', 1, 128)
|
|
384
|
+
: undefined,
|
|
385
|
+
search: filters.search
|
|
386
|
+
? bounded(filters.search, 'search', 1, DELIVERY_SEARCH_MAX)
|
|
387
|
+
: undefined,
|
|
388
|
+
},
|
|
389
|
+
listPage(page, 'desc', timeKey),
|
|
390
|
+
);
|
|
391
|
+
return { items: result.rows, next: result.next };
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/** Queues a fresh attempt run for a dead-lettered delivery. */
|
|
395
|
+
async replay(tenantId: string, id: string): Promise<DeliveryAttempt> {
|
|
396
|
+
const trustedTenantId = bounded(tenantId, 'tenantId', 1, 128);
|
|
397
|
+
const existing = await this.#options.repository.getDelivery(
|
|
398
|
+
trustedTenantId,
|
|
399
|
+
bounded(id, 'deliveryId', 1, 128),
|
|
400
|
+
);
|
|
401
|
+
if (!existing) {
|
|
402
|
+
throw new NotificationsServiceError(
|
|
403
|
+
'DELIVERY_NOT_FOUND',
|
|
404
|
+
'Delivery attempt not found.',
|
|
405
|
+
404,
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
if (existing.status !== 'dead-letter') {
|
|
409
|
+
throw new NotificationsServiceError(
|
|
410
|
+
'DELIVERY_NOT_DEAD_LETTER',
|
|
411
|
+
'Only a dead-lettered delivery can be replayed.',
|
|
412
|
+
409,
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
const sequence =
|
|
416
|
+
(await this.#options.repository.latestDeliverySequence(existing)) + 1;
|
|
417
|
+
const queued = await this.#append(existing, sequence, 1, this.#now());
|
|
418
|
+
if (!queued) {
|
|
419
|
+
throw new NotificationsServiceError(
|
|
420
|
+
'DELIVERY_REPLAY_CONFLICT',
|
|
421
|
+
'A replay of this delivery is already queued.',
|
|
422
|
+
409,
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
return queued;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* One pass over the delivery queue. The workspace lookups it needs are asked
|
|
430
|
+
* for once and held by the pass alone, bounded by the tenants its routing
|
|
431
|
+
* rows named, so a membership or an address change is picked up by the next
|
|
432
|
+
* pass rather than by the next attempt.
|
|
433
|
+
*/
|
|
434
|
+
pass(): DeliveryPass {
|
|
435
|
+
const members = this.#passMembers();
|
|
436
|
+
return {
|
|
437
|
+
/* The routing read crossed tenants; the row is authoritative only now,
|
|
438
|
+
read back and claimed under its own tenant in one statement. The claim
|
|
439
|
+
is what keeps two poll loops from repeating one request. */
|
|
440
|
+
claim: (routing, at) =>
|
|
441
|
+
this.#options.repository.claimDelivery({
|
|
442
|
+
tenantId: routing.tenantId,
|
|
443
|
+
id: routing.id,
|
|
444
|
+
now: at,
|
|
445
|
+
strandedBefore: at - DELIVERY_CLAIM_TIMEOUT_MS,
|
|
446
|
+
}),
|
|
447
|
+
deliver: (attempt, at) => this.#deliver(attempt, at, members),
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
#passMembers(): MemberLookup {
|
|
452
|
+
const pending = new Map<string, Promise<TenantMembers>>();
|
|
453
|
+
return (tenantId) => {
|
|
454
|
+
let members = pending.get(tenantId);
|
|
455
|
+
if (!members) {
|
|
456
|
+
members = this.#options.members(tenantId).then((list) => ({
|
|
457
|
+
list,
|
|
458
|
+
byAccount: new Map(list.map((member) => [member.accountId, member])),
|
|
459
|
+
}));
|
|
460
|
+
pending.set(tenantId, members);
|
|
461
|
+
}
|
|
462
|
+
return members;
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Deletes completed attempts past each tenant's retention window. One pass
|
|
468
|
+
* covers at most RETENTION_TENANT_LIMIT tenants and resumes from where the
|
|
469
|
+
* last one stopped, so no tenant is starved by the ones sorting before it.
|
|
470
|
+
*/
|
|
471
|
+
async collectRetention(now = this.#now()): Promise<number> {
|
|
472
|
+
const tenants = await this.#options.repository.listDeliveryTenants(
|
|
473
|
+
RETENTION_TENANT_LIMIT,
|
|
474
|
+
this.#retentionCursor,
|
|
475
|
+
);
|
|
476
|
+
this.#retentionCursor =
|
|
477
|
+
tenants.length < RETENTION_TENANT_LIMIT
|
|
478
|
+
? ''
|
|
479
|
+
: (tenants[tenants.length - 1] ?? '');
|
|
480
|
+
let removed = 0;
|
|
481
|
+
for (const tenantId of tenants) {
|
|
482
|
+
const { retentionDays } = await this.#options.settings(tenantId);
|
|
483
|
+
removed += await this.#options.repository.deleteCompletedDeliveriesBefore(
|
|
484
|
+
tenantId,
|
|
485
|
+
now - retentionDays * 86_400_000,
|
|
486
|
+
RETENTION_BATCH_LIMIT,
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
return removed;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
async #deliver(
|
|
493
|
+
attempt: DeliveryAttempt,
|
|
494
|
+
now: number,
|
|
495
|
+
members: MemberLookup,
|
|
496
|
+
): Promise<void> {
|
|
497
|
+
const tenantId = attempt.tenantId;
|
|
498
|
+
if (attempt.channel === 'email') {
|
|
499
|
+
const outcome = await this.#sendEmail(attempt, members);
|
|
500
|
+
/* A member who left the workspace or an item that is gone is the e-mail
|
|
501
|
+
channel's missing subscription, and a message the port refuses is
|
|
502
|
+
refused again on every attempt: no later pass would do better, so the
|
|
503
|
+
attempt ends here instead of spending the retry budget. */
|
|
504
|
+
if ('deadLetter' in outcome) {
|
|
505
|
+
await this.#deadLetter(attempt, now, outcome.deadLetter);
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
await this.#record(attempt, null, outcome, now, members);
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
const subscription = await this.#options.repository.getSubscription(
|
|
512
|
+
tenantId,
|
|
513
|
+
attempt.subscriptionId ?? '',
|
|
514
|
+
);
|
|
515
|
+
if (!subscription) {
|
|
516
|
+
await this.#deadLetter(attempt, now, 'egress-refused');
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
/* A paused or disabled subscription holds its queue: the attempt returns to
|
|
520
|
+
it unchanged and nothing is counted against the retry budget. It is
|
|
521
|
+
parked past this pass, because a held backlog larger than the page would
|
|
522
|
+
otherwise take the routing page from every other subscription forever. */
|
|
523
|
+
if (subscription.status !== 'active') {
|
|
524
|
+
await this.#options.repository.releaseDelivery(
|
|
525
|
+
tenantId,
|
|
526
|
+
attempt.id,
|
|
527
|
+
now + DELIVERY_HOLD_MS,
|
|
528
|
+
);
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const payload = webhookPayloadFingerprint({
|
|
533
|
+
tenantId,
|
|
534
|
+
subscriptionId: subscription.id,
|
|
535
|
+
kind: attempt.kind,
|
|
536
|
+
sourceModule: attempt.sourceModule,
|
|
537
|
+
sourceRef: attempt.sourceRef,
|
|
538
|
+
title: attempt.title,
|
|
539
|
+
occurredAt: attempt.occurredAt,
|
|
540
|
+
});
|
|
541
|
+
const outcome = await this.#send(subscription, payload.body, now);
|
|
542
|
+
await this.#record(attempt, subscription, outcome, now, members);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/** Ends one attempt without an outcome to retry; the ledger keeps the class. */
|
|
546
|
+
async #deadLetter(
|
|
547
|
+
attempt: DeliveryAttempt,
|
|
548
|
+
now: number,
|
|
549
|
+
errorClass: DeliveryErrorClass,
|
|
550
|
+
): Promise<void> {
|
|
551
|
+
await this.#options.repository.completeDelivery({
|
|
552
|
+
tenantId: attempt.tenantId,
|
|
553
|
+
id: attempt.id,
|
|
554
|
+
status: 'dead-letter',
|
|
555
|
+
completedAt: now,
|
|
556
|
+
responseStatus: null,
|
|
557
|
+
errorClass,
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* One message to one member, through the platform port. The address comes
|
|
563
|
+
* from auth.core at send time and the body from the member's own inbox item,
|
|
564
|
+
* so nothing about the person is stored in this module's queue. A
|
|
565
|
+
* `deadLetter` result means no later pass would do better.
|
|
566
|
+
*/
|
|
567
|
+
async #sendEmail(
|
|
568
|
+
attempt: DeliveryAttempt,
|
|
569
|
+
members: MemberLookup,
|
|
570
|
+
): Promise<DeliveryOutcome | PermanentEmailFailure> {
|
|
571
|
+
const recipient = attempt.recipientAccountId;
|
|
572
|
+
if (!recipient) return UNREACHABLE_RECIPIENT;
|
|
573
|
+
let member: TenantMemberScopes | undefined;
|
|
574
|
+
try {
|
|
575
|
+
member = (await members(attempt.tenantId)).byAccount.get(recipient);
|
|
576
|
+
} catch {
|
|
577
|
+
/* auth.core being unavailable is not this member's fault: the attempt
|
|
578
|
+
keeps its budget and the next pass asks again. */
|
|
579
|
+
return failed('network');
|
|
580
|
+
}
|
|
581
|
+
/* The workspace no longer holds the member, so there is no address to
|
|
582
|
+
resolve and none is kept here to fall back on. */
|
|
583
|
+
if (!member?.email) return UNREACHABLE_RECIPIENT;
|
|
584
|
+
const item = await this.#options.repository.findInboxItem(
|
|
585
|
+
attempt.tenantId,
|
|
586
|
+
recipient,
|
|
587
|
+
attempt.kind,
|
|
588
|
+
attempt.sourceRef,
|
|
589
|
+
);
|
|
590
|
+
if (!item) return UNREACHABLE_RECIPIENT;
|
|
591
|
+
try {
|
|
592
|
+
await this.#options.mail.send(
|
|
593
|
+
notificationMailMessage(
|
|
594
|
+
item,
|
|
595
|
+
member.email,
|
|
596
|
+
await this.#locale(attempt.tenantId),
|
|
597
|
+
),
|
|
598
|
+
);
|
|
599
|
+
return { status: 'succeeded', responseStatus: null, errorClass: null };
|
|
600
|
+
} catch (error) {
|
|
601
|
+
return classifyMailError(error);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
async #send(
|
|
606
|
+
subscription: StoredWebhookSubscription,
|
|
607
|
+
body: string,
|
|
608
|
+
now: number,
|
|
609
|
+
): Promise<DeliveryOutcome> {
|
|
610
|
+
let url: URL;
|
|
611
|
+
let addresses: readonly ResolvedAddress[];
|
|
612
|
+
try {
|
|
613
|
+
url = new URL(subscription.url);
|
|
614
|
+
/* The save path is the only writer of this row and refuses anything but
|
|
615
|
+
https, but the socket is opened here, so the scheme is refused again
|
|
616
|
+
immediately before it. */
|
|
617
|
+
if (url.protocol !== 'https:') {
|
|
618
|
+
throw new WebhookEgressError(
|
|
619
|
+
'WEBHOOK_URL_BLOCKED',
|
|
620
|
+
'A webhook is delivered over https only.',
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
/* Resolution is re-checked on every delivery: a name that was public at
|
|
624
|
+
save time can start answering with a private address. The accepted
|
|
625
|
+
addresses are the only ones the connection is allowed to reach, so
|
|
626
|
+
nothing between this check and the socket may resolve the name again. */
|
|
627
|
+
addresses = await this.#options.policy().assertResolvable(url.hostname);
|
|
628
|
+
} catch (error) {
|
|
629
|
+
return classifyError(error);
|
|
630
|
+
}
|
|
631
|
+
let secret: string;
|
|
632
|
+
try {
|
|
633
|
+
secret = this.#options.vault.decrypt(
|
|
634
|
+
subscription.secret,
|
|
635
|
+
secretContext(subscription.tenantId, subscription.id),
|
|
636
|
+
);
|
|
637
|
+
} catch {
|
|
638
|
+
return {
|
|
639
|
+
status: 'failed',
|
|
640
|
+
responseStatus: null,
|
|
641
|
+
errorClass: 'egress-refused',
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
const timestamp = String(now);
|
|
645
|
+
return this.#transport.send({
|
|
646
|
+
url: url.toString(),
|
|
647
|
+
body,
|
|
648
|
+
timestamp,
|
|
649
|
+
signature: webhookSignatureHeader(secret, timestamp, body),
|
|
650
|
+
addresses,
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
async #record(
|
|
655
|
+
attempt: DeliveryAttempt,
|
|
656
|
+
subscription: StoredWebhookSubscription | null,
|
|
657
|
+
outcome: DeliveryOutcome,
|
|
658
|
+
now: number,
|
|
659
|
+
members: MemberLookup,
|
|
660
|
+
): Promise<void> {
|
|
661
|
+
const { retryMaxAttempts, retryMaxBackoffMinutes } =
|
|
662
|
+
await this.#options.settings(attempt.tenantId);
|
|
663
|
+
const exhausted = attempt.attemptNumber >= retryMaxAttempts;
|
|
664
|
+
const status =
|
|
665
|
+
outcome.status === 'succeeded'
|
|
666
|
+
? 'succeeded'
|
|
667
|
+
: exhausted
|
|
668
|
+
? 'dead-letter'
|
|
669
|
+
: 'failed';
|
|
670
|
+
const written = await this.#options.repository.completeDelivery({
|
|
671
|
+
tenantId: attempt.tenantId,
|
|
672
|
+
id: attempt.id,
|
|
673
|
+
status,
|
|
674
|
+
completedAt: now,
|
|
675
|
+
responseStatus: outcome.responseStatus,
|
|
676
|
+
errorClass: outcome.errorClass,
|
|
677
|
+
});
|
|
678
|
+
/* Another worker finished this attempt first; it owns what follows. */
|
|
679
|
+
if (!written) return;
|
|
680
|
+
if (subscription) {
|
|
681
|
+
await this.#options.repository.stampSubscriptionDelivery(
|
|
682
|
+
attempt.tenantId,
|
|
683
|
+
subscription.id,
|
|
684
|
+
now,
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
if (status === 'succeeded') return;
|
|
688
|
+
if (status === 'failed') {
|
|
689
|
+
await this.#append(
|
|
690
|
+
attempt,
|
|
691
|
+
attempt.sequence,
|
|
692
|
+
attempt.attemptNumber + 1,
|
|
693
|
+
now + backoffMs(attempt.attemptNumber, retryMaxBackoffMinutes),
|
|
694
|
+
);
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
/* An e-mail attempt has no subscription, and its dead letter writes no
|
|
698
|
+
inbox item either. That item would be mailed to every member who asked
|
|
699
|
+
for mail, fail the same way and dead-letter again under a new source
|
|
700
|
+
reference; the ledger is where a failed message is read. */
|
|
701
|
+
if (!subscription) return;
|
|
702
|
+
await this.#notifyDeadLetter(attempt, subscription, now, members);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
async #append(
|
|
706
|
+
source: DeliveryAttempt,
|
|
707
|
+
sequence: number,
|
|
708
|
+
attemptNumber: number,
|
|
709
|
+
scheduledFor: number,
|
|
710
|
+
): Promise<DeliveryAttempt | null> {
|
|
711
|
+
/* Both fingerprints are taken from the row alone, so the digest of a retry
|
|
712
|
+
and of a replay matches the one the first attempt recorded. */
|
|
713
|
+
const payload =
|
|
714
|
+
source.channel === 'email'
|
|
715
|
+
? emailPayloadFingerprint({
|
|
716
|
+
tenantId: source.tenantId,
|
|
717
|
+
recipientAccountId: source.recipientAccountId ?? '',
|
|
718
|
+
kind: source.kind,
|
|
719
|
+
sourceModule: source.sourceModule,
|
|
720
|
+
sourceRef: source.sourceRef,
|
|
721
|
+
title: source.title,
|
|
722
|
+
occurredAt: source.occurredAt,
|
|
723
|
+
})
|
|
724
|
+
: webhookPayloadFingerprint({
|
|
725
|
+
tenantId: source.tenantId,
|
|
726
|
+
subscriptionId: source.subscriptionId ?? '',
|
|
727
|
+
kind: source.kind,
|
|
728
|
+
sourceModule: source.sourceModule,
|
|
729
|
+
sourceRef: source.sourceRef,
|
|
730
|
+
title: source.title,
|
|
731
|
+
occurredAt: source.occurredAt,
|
|
732
|
+
});
|
|
733
|
+
return this.#options.repository.appendDelivery({
|
|
734
|
+
id: randomUUID(),
|
|
735
|
+
tenantId: source.tenantId,
|
|
736
|
+
channel: source.channel,
|
|
737
|
+
subscriptionId: source.subscriptionId,
|
|
738
|
+
recipientAccountId: source.recipientAccountId,
|
|
739
|
+
kind: source.kind,
|
|
740
|
+
sourceModule: source.sourceModule,
|
|
741
|
+
sourceRef: source.sourceRef,
|
|
742
|
+
title: source.title,
|
|
743
|
+
sequence,
|
|
744
|
+
attemptNumber,
|
|
745
|
+
status: 'pending',
|
|
746
|
+
scheduledFor,
|
|
747
|
+
completedAt: null,
|
|
748
|
+
responseStatus: null,
|
|
749
|
+
errorClass: null,
|
|
750
|
+
payloadDigest: payload.digest,
|
|
751
|
+
payloadBytes: payload.bytes,
|
|
752
|
+
occurredAt: source.occurredAt,
|
|
753
|
+
createdAt: this.#now(),
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
/* Every member who can read the dead letter and has not switched the kind off
|
|
758
|
+
is told about it; the preference is applied by the same inbox write the
|
|
759
|
+
publication path uses. The row is keyed by the exhausted attempt, so a
|
|
760
|
+
second pass writes nothing. */
|
|
761
|
+
async #notifyDeadLetter(
|
|
762
|
+
attempt: DeliveryAttempt,
|
|
763
|
+
subscription: StoredWebhookSubscription,
|
|
764
|
+
now: number,
|
|
765
|
+
lookup: MemberLookup,
|
|
766
|
+
): Promise<void> {
|
|
767
|
+
let members: readonly TenantMemberScopes[];
|
|
768
|
+
try {
|
|
769
|
+
members = (await lookup(attempt.tenantId)).list;
|
|
770
|
+
} catch {
|
|
771
|
+
/* auth.core being unavailable must not undo a recorded dead letter. */
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
const records: NotificationsInbox[] = [];
|
|
775
|
+
for (const member of members) {
|
|
776
|
+
if (!member.scopes.includes(NOTIFICATIONS_PERMISSIONS.deliveriesRead)) {
|
|
777
|
+
continue;
|
|
778
|
+
}
|
|
779
|
+
records.push({
|
|
780
|
+
id: randomUUID(),
|
|
781
|
+
tenantId: attempt.tenantId,
|
|
782
|
+
recipientAccountId: member.accountId,
|
|
783
|
+
kind: 'webhook-dead-letter',
|
|
784
|
+
title: `Webhook delivery failed: ${subscription.name}`,
|
|
785
|
+
body: `Delivery of ${attempt.kind} for ${attempt.sourceRef} was moved to the dead letter after ${attempt.attemptNumber} attempts.`,
|
|
786
|
+
sourceModule: 'notifications.core',
|
|
787
|
+
sourceRef: attempt.id,
|
|
788
|
+
status: 'unread',
|
|
789
|
+
readAt: null,
|
|
790
|
+
createdAt: now,
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
await this.#options.repository.appendInboxItems(
|
|
794
|
+
attempt.tenantId,
|
|
795
|
+
records,
|
|
796
|
+
(item) => emailDeliveryAttempt(item, this.#now()),
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/** One minute, doubling per attempt, clamped to the tenant maximum. */
|
|
802
|
+
export function backoffMs(
|
|
803
|
+
attemptNumber: number,
|
|
804
|
+
maximumMinutes: number,
|
|
805
|
+
): number {
|
|
806
|
+
const doublings = Math.min(Math.max(attemptNumber, 1) - 1, 30);
|
|
807
|
+
return Math.min(
|
|
808
|
+
DELIVERY_BASE_BACKOFF_MS * 2 ** doublings,
|
|
809
|
+
maximumMinutes * 60_000,
|
|
810
|
+
);
|
|
811
|
+
}
|