@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,903 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import type { IncomingMessage } from 'node:http';
|
|
3
|
+
import { Agent, request as httpsRequest } from 'node:https';
|
|
4
|
+
import type {
|
|
5
|
+
ConnectorCallCapability,
|
|
6
|
+
ConnectorCallRequest,
|
|
7
|
+
ConnectorCallResult,
|
|
8
|
+
ConnectorJsonValue,
|
|
9
|
+
} from '../domain/calls.ts';
|
|
10
|
+
import {
|
|
11
|
+
definitionAllowedPorts,
|
|
12
|
+
type ConnectorDefinitionRegistry,
|
|
13
|
+
} from '../domain/definitions.ts';
|
|
14
|
+
import {
|
|
15
|
+
CONNECTOR_CALLERS,
|
|
16
|
+
type ConnectorCall,
|
|
17
|
+
type ConnectorCaller,
|
|
18
|
+
type ConnectorCallOutcome,
|
|
19
|
+
type ConnectorCredentials,
|
|
20
|
+
type ConnectorDefinition,
|
|
21
|
+
type ConnectorErrorClass,
|
|
22
|
+
type ConnectorOperation,
|
|
23
|
+
} from '../domain/types.ts';
|
|
24
|
+
import { credentialContext, type CredentialVault } from './credential-vault.ts';
|
|
25
|
+
import {
|
|
26
|
+
connectorHostAllowlist,
|
|
27
|
+
ConnectorEgressError,
|
|
28
|
+
createConnectorEgressPolicy,
|
|
29
|
+
normalizeHost,
|
|
30
|
+
pinnedLookup,
|
|
31
|
+
type HostAddressResolver,
|
|
32
|
+
type ResolvedAddress,
|
|
33
|
+
} from './egress.ts';
|
|
34
|
+
import type {
|
|
35
|
+
ConnectorsRepository,
|
|
36
|
+
StoredConnectorInstance,
|
|
37
|
+
} from './repository.ts';
|
|
38
|
+
import { bounded, ConnectorsServiceError, oneOf } from './service-error.ts';
|
|
39
|
+
|
|
40
|
+
/** Bytes of the response kept as readable text for a test call. */
|
|
41
|
+
export const CALL_BODY_PREVIEW_BYTES = 4_096;
|
|
42
|
+
/** Bytes a request body may carry. Independent of the response cap. */
|
|
43
|
+
export const MAX_REQUEST_BYTES = 256 * 1_024;
|
|
44
|
+
/** Query parameters one call may append. */
|
|
45
|
+
export const MAX_QUERY_PARAMETERS = 32;
|
|
46
|
+
/** Bytes of an OAuth2 token response that are read before it is refused. */
|
|
47
|
+
export const TOKEN_RESPONSE_CAP_BYTES = 16 * 1_024;
|
|
48
|
+
/**
|
|
49
|
+
* Instances whose access token is cached at once. Tokens are per instance and
|
|
50
|
+
* short lived, so the cap is a leak guard, not a tuning knob: the oldest entry
|
|
51
|
+
* is dropped when a new one arrives.
|
|
52
|
+
*/
|
|
53
|
+
export const MAX_CACHED_TOKENS = 256;
|
|
54
|
+
/** Taken off a token's lifetime so it is never used in its final seconds. */
|
|
55
|
+
export const TOKEN_EXPIRY_MARGIN_MS = 30_000;
|
|
56
|
+
export const MAX_TOKEN_LIFETIME_MS = 60 * 60 * 1_000;
|
|
57
|
+
/**
|
|
58
|
+
* How long a claimed idempotency key stays in flight before another attempt may
|
|
59
|
+
* take it over. It is well past the highest call timeout the settings allow, so
|
|
60
|
+
* a retake only ever follows a process that died mid-call; without it a crash
|
|
61
|
+
* between the claim and the call log would block that key forever.
|
|
62
|
+
*/
|
|
63
|
+
export const CALL_KEY_CLAIM_MS = 5 * 60_000;
|
|
64
|
+
|
|
65
|
+
export interface ConnectorCallLimits {
|
|
66
|
+
readonly timeoutMs: number;
|
|
67
|
+
readonly maxResponseBytes: number;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Test seam for the outbound socket; never reachable from configuration. A
|
|
72
|
+
* deployment leaves it unset, so a call dials the address the policy verified
|
|
73
|
+
* and trusts the system store. A test maps that address onto the loopback port
|
|
74
|
+
* its server listens on and adds that server's certificate, which runs the call
|
|
75
|
+
* path with exactly the address rules a deployment has. `ca` only ever widens
|
|
76
|
+
* trust: there is no form of this seam that turns verification off.
|
|
77
|
+
*/
|
|
78
|
+
export interface ConnectorConnectSeam {
|
|
79
|
+
readonly dial?: ((address: string) => string) | undefined;
|
|
80
|
+
readonly ca?: string | undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface ConnectorCallServiceOptions {
|
|
84
|
+
readonly repository: ConnectorsRepository;
|
|
85
|
+
readonly vault: CredentialVault;
|
|
86
|
+
readonly definitions: () => ConnectorDefinitionRegistry;
|
|
87
|
+
/** Live platform settings, read again for every call. */
|
|
88
|
+
readonly limits: () => ConnectorCallLimits;
|
|
89
|
+
/** Test seam for the address check; never reachable from configuration. */
|
|
90
|
+
readonly hostResolver?: HostAddressResolver | undefined;
|
|
91
|
+
readonly connect?: ConnectorConnectSeam | undefined;
|
|
92
|
+
readonly now?: () => number;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
interface CachedToken {
|
|
96
|
+
readonly token: string;
|
|
97
|
+
readonly expiresAt: number;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
interface PreparedRequest {
|
|
101
|
+
readonly target: URL;
|
|
102
|
+
readonly body: string | null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
class CallRefusal extends Error {
|
|
106
|
+
constructor(
|
|
107
|
+
readonly outcome: Exclude<ConnectorCallOutcome, 'succeeded'>,
|
|
108
|
+
readonly errorClass: ConnectorErrorClass,
|
|
109
|
+
readonly status: number | null = null,
|
|
110
|
+
) {
|
|
111
|
+
super(errorClass);
|
|
112
|
+
this.name = 'CallRefusal';
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* A separator no tenant id can carry, so two workspaces can never be read as
|
|
117
|
+
one key. The credential fingerprint closes the key, so a token the previous
|
|
118
|
+
client secret obtained is never served for the one that replaced it. */
|
|
119
|
+
function tokenCacheKey(
|
|
120
|
+
tenantId: string,
|
|
121
|
+
instanceId: string,
|
|
122
|
+
fingerprint: string | null,
|
|
123
|
+
): string {
|
|
124
|
+
return `${tenantId}\u0000${instanceId}\u0000${fingerprint ?? ''}`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function tokenCachePrefix(tenantId: string, instanceId: string): string {
|
|
128
|
+
return `${tenantId}\u0000${instanceId}\u0000`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function jsonBody(value: unknown): ConnectorJsonValue | null {
|
|
132
|
+
try {
|
|
133
|
+
return JSON.parse(String(value)) as ConnectorJsonValue;
|
|
134
|
+
} catch {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Encodes every segment while keeping the path structure of the template. */
|
|
140
|
+
function encodePath(value: string): string {
|
|
141
|
+
return value
|
|
142
|
+
.split('/')
|
|
143
|
+
.map((segment) => encodeURIComponent(segment))
|
|
144
|
+
.join('/');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function templateValue(
|
|
148
|
+
input: Readonly<Record<string, unknown>>,
|
|
149
|
+
name: string,
|
|
150
|
+
): string {
|
|
151
|
+
const value = input[name];
|
|
152
|
+
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
153
|
+
throw new CallRefusal('refused', 'invalid-input');
|
|
154
|
+
}
|
|
155
|
+
const normalized = value.trim();
|
|
156
|
+
if (normalized.length > 1_024 || /[\u0000-\u001f\u007f]/.test(normalized)) {
|
|
157
|
+
throw new CallRefusal('refused', 'invalid-input');
|
|
158
|
+
}
|
|
159
|
+
/* A dot segment survives both encoders and `new URL` then resolves it away,
|
|
160
|
+
which is how a value alone could reach a path the definition never
|
|
161
|
+
declared. Refused for either branch: the value names a resource, never a
|
|
162
|
+
piece of path syntax, so a single segment has no use for one either. */
|
|
163
|
+
if (
|
|
164
|
+
normalized.split('/').some((segment) => segment === '.' || segment === '..')
|
|
165
|
+
) {
|
|
166
|
+
throw new CallRefusal('refused', 'invalid-input');
|
|
167
|
+
}
|
|
168
|
+
return normalized;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Expands `{name}` as one escaped segment and `{+name}` as a whole path. The
|
|
173
|
+
* caller supplies only values, never the shape of the path, so a definition
|
|
174
|
+
* keeps control of which resources its instances can reach.
|
|
175
|
+
*/
|
|
176
|
+
export function expandPath(
|
|
177
|
+
template: string,
|
|
178
|
+
input: Readonly<Record<string, unknown>>,
|
|
179
|
+
): string {
|
|
180
|
+
return template.replace(
|
|
181
|
+
/\{(\+?)([a-z][a-zA-Z0-9_]*)\}/g,
|
|
182
|
+
(_match, reserved: string, name: string) => {
|
|
183
|
+
const value = templateValue(input, name);
|
|
184
|
+
if (reserved === '') return encodeURIComponent(value);
|
|
185
|
+
if (
|
|
186
|
+
!value.startsWith('/') ||
|
|
187
|
+
value.includes('?') ||
|
|
188
|
+
value.includes('#')
|
|
189
|
+
) {
|
|
190
|
+
throw new CallRefusal('refused', 'invalid-input');
|
|
191
|
+
}
|
|
192
|
+
return encodePath(value);
|
|
193
|
+
},
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function queryParameters(
|
|
198
|
+
value: unknown,
|
|
199
|
+
): readonly (readonly [string, string])[] {
|
|
200
|
+
if (value === undefined || value === null) return [];
|
|
201
|
+
if (typeof value !== 'object' || Array.isArray(value)) {
|
|
202
|
+
throw new CallRefusal('refused', 'invalid-input');
|
|
203
|
+
}
|
|
204
|
+
const entries = Object.entries(value as Record<string, unknown>);
|
|
205
|
+
if (entries.length > MAX_QUERY_PARAMETERS) {
|
|
206
|
+
throw new CallRefusal('refused', 'invalid-input');
|
|
207
|
+
}
|
|
208
|
+
return entries.map(([key, entry]) => {
|
|
209
|
+
if (
|
|
210
|
+
key.length === 0 ||
|
|
211
|
+
key.length > 64 ||
|
|
212
|
+
(typeof entry !== 'string' &&
|
|
213
|
+
typeof entry !== 'number' &&
|
|
214
|
+
typeof entry !== 'boolean')
|
|
215
|
+
) {
|
|
216
|
+
throw new CallRefusal('refused', 'invalid-input');
|
|
217
|
+
}
|
|
218
|
+
const text = String(entry);
|
|
219
|
+
if (text.length > 1_024) throw new CallRefusal('refused', 'invalid-input');
|
|
220
|
+
return [key, text] as const;
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function requestBody(
|
|
225
|
+
operation: ConnectorOperation,
|
|
226
|
+
input: Readonly<Record<string, unknown>>,
|
|
227
|
+
): string | null {
|
|
228
|
+
if (operation.method === 'GET' || operation.method === 'DELETE') return null;
|
|
229
|
+
const body = input.body;
|
|
230
|
+
if (body === undefined) return null;
|
|
231
|
+
let serialized: string;
|
|
232
|
+
try {
|
|
233
|
+
serialized = JSON.stringify(body);
|
|
234
|
+
} catch {
|
|
235
|
+
throw new CallRefusal('refused', 'invalid-input');
|
|
236
|
+
}
|
|
237
|
+
if (serialized === undefined)
|
|
238
|
+
throw new CallRefusal('refused', 'invalid-input');
|
|
239
|
+
if (Buffer.byteLength(serialized, 'utf8') > MAX_REQUEST_BYTES) {
|
|
240
|
+
throw new CallRefusal('refused', 'invalid-input');
|
|
241
|
+
}
|
|
242
|
+
return serialized;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** Builds the absolute target under the instance base URL, or refuses. */
|
|
246
|
+
export function prepareRequest(
|
|
247
|
+
baseUrl: string,
|
|
248
|
+
operation: ConnectorOperation,
|
|
249
|
+
input: Readonly<Record<string, unknown>>,
|
|
250
|
+
): PreparedRequest {
|
|
251
|
+
const base = new URL(baseUrl);
|
|
252
|
+
const prefix = base.pathname.replace(/\/+$/, '');
|
|
253
|
+
const target = new URL(prefix + expandPath(operation.path, input), base);
|
|
254
|
+
/* `new URL` normalizes `..`, so this is what stops a path value from
|
|
255
|
+
climbing out of the base path or reaching another origin. */
|
|
256
|
+
if (target.origin !== base.origin || !target.pathname.startsWith(prefix)) {
|
|
257
|
+
throw new CallRefusal('refused', 'invalid-input');
|
|
258
|
+
}
|
|
259
|
+
for (const [key, value] of queryParameters(input.query)) {
|
|
260
|
+
target.searchParams.append(key, value);
|
|
261
|
+
}
|
|
262
|
+
return { target, body: requestBody(operation, input) };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
interface HttpExchange {
|
|
266
|
+
readonly status: number;
|
|
267
|
+
readonly text: string;
|
|
268
|
+
readonly bytes: number;
|
|
269
|
+
readonly exceeded: boolean;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async function readBounded(
|
|
273
|
+
response: IncomingMessage,
|
|
274
|
+
cap: number,
|
|
275
|
+
): Promise<{
|
|
276
|
+
readonly text: string;
|
|
277
|
+
readonly bytes: number;
|
|
278
|
+
readonly exceeded: boolean;
|
|
279
|
+
}> {
|
|
280
|
+
const chunks: Buffer[] = [];
|
|
281
|
+
let bytes = 0;
|
|
282
|
+
try {
|
|
283
|
+
for await (const chunk of response) {
|
|
284
|
+
const buffer = chunk as Buffer;
|
|
285
|
+
bytes += buffer.byteLength;
|
|
286
|
+
/* The socket goes down with the first chunk past the cap, so a sender
|
|
287
|
+
that keeps streaming cannot hold the call open past it. */
|
|
288
|
+
if (bytes > cap) return { text: '', bytes, exceeded: true };
|
|
289
|
+
chunks.push(buffer);
|
|
290
|
+
}
|
|
291
|
+
} finally {
|
|
292
|
+
response.destroy();
|
|
293
|
+
}
|
|
294
|
+
return {
|
|
295
|
+
text: Buffer.concat(chunks).toString('utf8'),
|
|
296
|
+
bytes,
|
|
297
|
+
exceeded: false,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/** Reads as an abort so the transport classifier records a timeout, not a reset. */
|
|
302
|
+
function abortFailure(): Error {
|
|
303
|
+
const error = new Error('The connector request was aborted.');
|
|
304
|
+
error.name = 'AbortError';
|
|
305
|
+
return error;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* One request over TLS to an address the egress policy already accepted. The
|
|
310
|
+
* agent is built per call and destroyed with it: a pooled socket would outlive
|
|
311
|
+
* the address check that admitted it, which is the window this closes.
|
|
312
|
+
*/
|
|
313
|
+
function sendPinned(
|
|
314
|
+
target: URL,
|
|
315
|
+
init: {
|
|
316
|
+
readonly method: string;
|
|
317
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
318
|
+
readonly body: string | null;
|
|
319
|
+
readonly addresses: readonly ResolvedAddress[];
|
|
320
|
+
readonly cap: number;
|
|
321
|
+
readonly signal: AbortSignal;
|
|
322
|
+
readonly ca?: string | undefined;
|
|
323
|
+
/** Called once the request has been handed to the operating system. */
|
|
324
|
+
readonly onWritten?: (() => void) | undefined;
|
|
325
|
+
},
|
|
326
|
+
): Promise<HttpExchange> {
|
|
327
|
+
const agent = new Agent({
|
|
328
|
+
keepAlive: false,
|
|
329
|
+
maxSockets: 1,
|
|
330
|
+
lookup: pinnedLookup(normalizeHost(target.hostname), init.addresses),
|
|
331
|
+
...(init.ca === undefined ? {} : { ca: init.ca }),
|
|
332
|
+
});
|
|
333
|
+
return new Promise<HttpExchange>((resolve, reject) => {
|
|
334
|
+
const fail = (error: unknown) =>
|
|
335
|
+
reject(init.signal.aborted ? abortFailure() : error);
|
|
336
|
+
/* A redirect is a refusal, not a hop: node never follows one, so the 3xx
|
|
337
|
+
comes back and the call is recorded rather than chased to another host. */
|
|
338
|
+
const request = httpsRequest(target, {
|
|
339
|
+
method: init.method,
|
|
340
|
+
agent,
|
|
341
|
+
/* An explicit length rather than a chunked body: an external system
|
|
342
|
+
that refuses chunked requests must still see the same request it
|
|
343
|
+
saw before the transport changed. */
|
|
344
|
+
headers:
|
|
345
|
+
init.body === null
|
|
346
|
+
? init.headers
|
|
347
|
+
: {
|
|
348
|
+
...init.headers,
|
|
349
|
+
'content-length': String(Buffer.byteLength(init.body, 'utf8')),
|
|
350
|
+
},
|
|
351
|
+
signal: init.signal,
|
|
352
|
+
});
|
|
353
|
+
request.on('error', fail);
|
|
354
|
+
if (init.onWritten) request.on('finish', init.onWritten);
|
|
355
|
+
request.on('response', (response) => {
|
|
356
|
+
void readBounded(response, init.cap).then(
|
|
357
|
+
(payload) => resolve({ status: response.statusCode ?? 0, ...payload }),
|
|
358
|
+
fail,
|
|
359
|
+
);
|
|
360
|
+
});
|
|
361
|
+
if (init.body !== null) request.write(init.body);
|
|
362
|
+
request.end();
|
|
363
|
+
}).finally(() => agent.destroy());
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function transportFailure(error: unknown): CallRefusal {
|
|
367
|
+
if (error instanceof CallRefusal) return error;
|
|
368
|
+
if (error instanceof ConnectorEgressError) {
|
|
369
|
+
return error.code === 'CONNECTOR_HOST_UNRESOLVED'
|
|
370
|
+
? new CallRefusal('failed', 'dns')
|
|
371
|
+
: new CallRefusal('refused', 'egress-refused');
|
|
372
|
+
}
|
|
373
|
+
const name = (error as { name?: unknown })?.name;
|
|
374
|
+
if (name === 'TimeoutError' || name === 'AbortError') {
|
|
375
|
+
return new CallRefusal('failed', 'timeout');
|
|
376
|
+
}
|
|
377
|
+
/* node reports the resolver failure on the error, fetch reported it on the
|
|
378
|
+
cause; both shapes are read so the class stays the same either way. */
|
|
379
|
+
const failure = error as { code?: unknown; cause?: { code?: unknown } };
|
|
380
|
+
const code = String(failure?.code ?? failure?.cause?.code);
|
|
381
|
+
if (code === 'ENOTFOUND' || code === 'EAI_AGAIN') {
|
|
382
|
+
return new CallRefusal('failed', 'dns');
|
|
383
|
+
}
|
|
384
|
+
return new CallRefusal('failed', 'network');
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/* A definition names the ports its instances may reach and 443 is the default,
|
|
388
|
+
so an instance cannot be aimed at an internal service that happens to answer
|
|
389
|
+
on a public address. */
|
|
390
|
+
function assertAllowedPort(definition: ConnectorDefinition, url: URL): void {
|
|
391
|
+
const port = url.port === '' ? 443 : Number(url.port);
|
|
392
|
+
if (!definitionAllowedPorts(definition).includes(port)) {
|
|
393
|
+
throw new CallRefusal('refused', 'egress-refused');
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/** Key order never changes the digest, so a retry of the same call matches. */
|
|
398
|
+
function canonicalJson(value: unknown): string {
|
|
399
|
+
if (value === null || typeof value !== 'object') {
|
|
400
|
+
const encoded = JSON.stringify(value);
|
|
401
|
+
if (encoded === undefined)
|
|
402
|
+
throw new CallRefusal('refused', 'invalid-input');
|
|
403
|
+
return encoded;
|
|
404
|
+
}
|
|
405
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(',')}]`;
|
|
406
|
+
return `{${Object.entries(value as Record<string, unknown>)
|
|
407
|
+
.filter(([, entry]) => entry !== undefined)
|
|
408
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
409
|
+
.map(([key, entry]) => `${JSON.stringify(key)}:${canonicalJson(entry)}`)
|
|
410
|
+
.join(',')}}`;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function inputDigest(value: unknown): string {
|
|
414
|
+
return createHash('sha256').update(canonicalJson(value)).digest('hex');
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Runs one operation of one instance. Every refusal and every failure lands in
|
|
419
|
+
* the call log with its class, and no request or response body is ever stored.
|
|
420
|
+
*/
|
|
421
|
+
export class ConnectorCallService implements ConnectorCallCapability {
|
|
422
|
+
readonly #options: ConnectorCallServiceOptions;
|
|
423
|
+
readonly #now: () => number;
|
|
424
|
+
readonly #tokens = new Map<string, CachedToken>();
|
|
425
|
+
|
|
426
|
+
constructor(options: ConnectorCallServiceOptions) {
|
|
427
|
+
this.#options = options;
|
|
428
|
+
this.#now = options.now ?? Date.now;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Drops every cached token of an instance after it or its credential changed.
|
|
433
|
+
* The scan is over a map bounded by MAX_CACHED_TOKENS and runs on a
|
|
434
|
+
* configuration change, never on the call path.
|
|
435
|
+
*/
|
|
436
|
+
forget(tenantId: string, instanceId: string): void {
|
|
437
|
+
const prefix = tokenCachePrefix(tenantId, instanceId);
|
|
438
|
+
for (const key of this.#tokens.keys()) {
|
|
439
|
+
if (key.startsWith(prefix)) this.#tokens.delete(key);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
async consented(
|
|
444
|
+
tenantId: string,
|
|
445
|
+
instanceId: string,
|
|
446
|
+
caller: ConnectorCaller,
|
|
447
|
+
): Promise<boolean> {
|
|
448
|
+
const instance = await this.#options.repository.findInstance(
|
|
449
|
+
bounded(tenantId, 'tenantId', 1, 128),
|
|
450
|
+
bounded(instanceId, 'instanceId', 1, 128),
|
|
451
|
+
);
|
|
452
|
+
return instance !== null && this.#admits(instance, caller);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
async call(request: ConnectorCallRequest): Promise<ConnectorCallResult> {
|
|
456
|
+
const tenantId = bounded(request.tenantId, 'tenantId', 1, 128);
|
|
457
|
+
const instanceId = bounded(request.instanceId, 'instanceId', 1, 128);
|
|
458
|
+
const caller = oneOf(request.caller, 'caller', CONNECTOR_CALLERS);
|
|
459
|
+
const operationKey = bounded(request.operation, 'operation', 1, 64);
|
|
460
|
+
const callerRef =
|
|
461
|
+
request.callerRef === undefined || request.callerRef === ''
|
|
462
|
+
? null
|
|
463
|
+
: bounded(request.callerRef, 'callerRef', 1, 200);
|
|
464
|
+
const idempotencyKey =
|
|
465
|
+
request.idempotencyKey === undefined || request.idempotencyKey === ''
|
|
466
|
+
? null
|
|
467
|
+
: bounded(request.idempotencyKey, 'idempotencyKey', 8, 128);
|
|
468
|
+
const instance = await this.#options.repository.findInstance(
|
|
469
|
+
tenantId,
|
|
470
|
+
instanceId,
|
|
471
|
+
);
|
|
472
|
+
if (!instance) {
|
|
473
|
+
throw new ConnectorsServiceError(
|
|
474
|
+
'INSTANCE_NOT_FOUND',
|
|
475
|
+
'No connector instance with this id exists in the workspace.',
|
|
476
|
+
404,
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
const input =
|
|
480
|
+
request.input && typeof request.input === 'object'
|
|
481
|
+
? request.input
|
|
482
|
+
: ({} as Record<string, unknown>);
|
|
483
|
+
const started = this.#now();
|
|
484
|
+
let requestBytes = 0;
|
|
485
|
+
let claimed = false;
|
|
486
|
+
let requestWritten = false;
|
|
487
|
+
try {
|
|
488
|
+
if (instance.status !== 'active') {
|
|
489
|
+
throw new CallRefusal('refused', 'instance-disabled');
|
|
490
|
+
}
|
|
491
|
+
if (!this.#admits(instance, caller)) {
|
|
492
|
+
throw new CallRefusal('refused', 'consent-missing');
|
|
493
|
+
}
|
|
494
|
+
const definition = this.#options
|
|
495
|
+
.definitions()
|
|
496
|
+
.get(instance.definitionKey);
|
|
497
|
+
if (!definition) throw new CallRefusal('refused', 'definition-missing');
|
|
498
|
+
const operation = definition.operations.find(
|
|
499
|
+
(candidate) => candidate.key === operationKey,
|
|
500
|
+
);
|
|
501
|
+
if (!operation) throw new CallRefusal('refused', 'operation-unknown');
|
|
502
|
+
const prepared = prepareRequest(instance.baseUrl, operation, input);
|
|
503
|
+
requestBytes =
|
|
504
|
+
prepared.body === null ? 0 : Buffer.byteLength(prepared.body, 'utf8');
|
|
505
|
+
const addresses = await this.#assertReachable(
|
|
506
|
+
instance,
|
|
507
|
+
definition,
|
|
508
|
+
prepared.target,
|
|
509
|
+
);
|
|
510
|
+
/* The credential is resolved first, token exchange included: that
|
|
511
|
+
exchange is an outbound call of its own that can be refused or fail,
|
|
512
|
+
and a key claimed before it would answer every later attempt with a
|
|
513
|
+
call the external system never saw. */
|
|
514
|
+
const headers = await this.#headers(instance, definition);
|
|
515
|
+
/* The last point before anything can leave, and after every refusal
|
|
516
|
+
that never reaches the network: a call stopped by consent, by the
|
|
517
|
+
egress rules, by a credential or by a bad input leaves the key free
|
|
518
|
+
for the retry that follows the fix. */
|
|
519
|
+
if (idempotencyKey) {
|
|
520
|
+
const replay = await this.#claim(
|
|
521
|
+
tenantId,
|
|
522
|
+
instanceId,
|
|
523
|
+
operationKey,
|
|
524
|
+
input,
|
|
525
|
+
idempotencyKey,
|
|
526
|
+
);
|
|
527
|
+
if (replay) return replay;
|
|
528
|
+
claimed = true;
|
|
529
|
+
}
|
|
530
|
+
const outcome = await this.#send(
|
|
531
|
+
operation,
|
|
532
|
+
prepared,
|
|
533
|
+
headers,
|
|
534
|
+
addresses,
|
|
535
|
+
request.signal,
|
|
536
|
+
() => {
|
|
537
|
+
requestWritten = true;
|
|
538
|
+
},
|
|
539
|
+
);
|
|
540
|
+
return await this.#record(claimed ? idempotencyKey : null, {
|
|
541
|
+
tenantId,
|
|
542
|
+
instanceId,
|
|
543
|
+
operation: operationKey,
|
|
544
|
+
caller,
|
|
545
|
+
callerRef,
|
|
546
|
+
outcome: outcome.outcome,
|
|
547
|
+
status: outcome.status,
|
|
548
|
+
errorClass: outcome.errorClass,
|
|
549
|
+
durationMs: this.#now() - started,
|
|
550
|
+
requestBytes,
|
|
551
|
+
responseBytes: outcome.responseBytes,
|
|
552
|
+
body: outcome.body,
|
|
553
|
+
bodyPreview: outcome.bodyPreview,
|
|
554
|
+
});
|
|
555
|
+
} catch (error) {
|
|
556
|
+
/* A ledger decision is the caller's answer, not the outcome of a call:
|
|
557
|
+
nothing reached the network, so it must not become a log row. */
|
|
558
|
+
if (error instanceof ConnectorsServiceError) throw error;
|
|
559
|
+
const refusal = transportFailure(error);
|
|
560
|
+
/* The key stays bound only once the request is on the wire, because
|
|
561
|
+
from there an answer may have been lost rather than never given. A
|
|
562
|
+
refusal reaches no network at all, and a failure raised before the
|
|
563
|
+
request was written leaves the external system untouched: both free
|
|
564
|
+
the key for the retry that follows the fix. */
|
|
565
|
+
const bindKey =
|
|
566
|
+
claimed && requestWritten && refusal.outcome !== 'refused';
|
|
567
|
+
return await this.#record(bindKey ? idempotencyKey : null, {
|
|
568
|
+
tenantId,
|
|
569
|
+
instanceId,
|
|
570
|
+
operation: operationKey,
|
|
571
|
+
caller,
|
|
572
|
+
callerRef,
|
|
573
|
+
outcome: refusal.outcome,
|
|
574
|
+
status: refusal.status,
|
|
575
|
+
errorClass: refusal.errorClass,
|
|
576
|
+
durationMs: this.#now() - started,
|
|
577
|
+
requestBytes,
|
|
578
|
+
responseBytes: 0,
|
|
579
|
+
body: null,
|
|
580
|
+
bodyPreview: '',
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Binds one idempotency key to one call before anything leaves the process.
|
|
587
|
+
* A repeat of a key that already produced a call answers that call instead of
|
|
588
|
+
* making a second one, so a workflow node replayed after a crash cannot hit
|
|
589
|
+
* the external system twice. A recorded failure counts as a call: a
|
|
590
|
+
* non-idempotent write whose answer was lost is never retried under its key.
|
|
591
|
+
*/
|
|
592
|
+
async #claim(
|
|
593
|
+
tenantId: string,
|
|
594
|
+
instanceId: string,
|
|
595
|
+
operation: string,
|
|
596
|
+
input: Readonly<Record<string, unknown>>,
|
|
597
|
+
key: string,
|
|
598
|
+
): Promise<ConnectorCallResult | null> {
|
|
599
|
+
const now = this.#now();
|
|
600
|
+
const claim = await this.#options.repository.claimCallKey(tenantId, key, {
|
|
601
|
+
operationId: `${instanceId}:${operation}`,
|
|
602
|
+
inputDigest: inputDigest(input),
|
|
603
|
+
claimedAt: now,
|
|
604
|
+
staleBefore: now - CALL_KEY_CLAIM_MS,
|
|
605
|
+
});
|
|
606
|
+
if (claim.state === 'claimed') return null;
|
|
607
|
+
if (claim.state === 'conflict') {
|
|
608
|
+
throw new ConnectorsServiceError(
|
|
609
|
+
'CALL_IDEMPOTENCY_CONFLICT',
|
|
610
|
+
'This idempotency key is already bound to another connector call.',
|
|
611
|
+
409,
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
if (claim.state === 'in-flight') {
|
|
615
|
+
throw new ConnectorsServiceError(
|
|
616
|
+
'CALL_IN_FLIGHT',
|
|
617
|
+
'A connector call with this idempotency key is still running.',
|
|
618
|
+
409,
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
const recorded = await this.#options.repository.findCall(
|
|
622
|
+
tenantId,
|
|
623
|
+
claim.callId,
|
|
624
|
+
);
|
|
625
|
+
if (!recorded) {
|
|
626
|
+
throw new ConnectorsServiceError(
|
|
627
|
+
'CALL_NOT_FOUND',
|
|
628
|
+
'The call this idempotency key is bound to is no longer in the log.',
|
|
629
|
+
409,
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
/* The log keeps no body, so a replay answers the recorded diagnosis and
|
|
633
|
+
says so rather than presenting an empty body as the call's own. */
|
|
634
|
+
return {
|
|
635
|
+
callId: recorded.id,
|
|
636
|
+
outcome: recorded.outcome,
|
|
637
|
+
status: recorded.status,
|
|
638
|
+
errorClass: recorded.errorClass,
|
|
639
|
+
durationMs: recorded.durationMs,
|
|
640
|
+
requestBytes: recorded.requestBytes,
|
|
641
|
+
responseBytes: recorded.responseBytes,
|
|
642
|
+
body: null,
|
|
643
|
+
bodyPreview: '',
|
|
644
|
+
replayed: true,
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
#admits(instance: StoredConnectorInstance, caller: ConnectorCaller): boolean {
|
|
649
|
+
if (instance.status !== 'active') return false;
|
|
650
|
+
if (caller === 'workflow') return instance.allowWorkflows;
|
|
651
|
+
if (caller === 'agent') return instance.allowAgents;
|
|
652
|
+
return true;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* Scheme, port, allowlist and addresses, immediately before the socket. It
|
|
657
|
+
* answers the addresses it accepted so the connection can be pinned to them:
|
|
658
|
+
* nothing between this check and the request may resolve the name again.
|
|
659
|
+
*/
|
|
660
|
+
async #assertReachable(
|
|
661
|
+
instance: StoredConnectorInstance,
|
|
662
|
+
definition: ConnectorDefinition,
|
|
663
|
+
url: URL,
|
|
664
|
+
): Promise<readonly ResolvedAddress[]> {
|
|
665
|
+
const policy = createConnectorEgressPolicy({
|
|
666
|
+
allowlist: connectorHostAllowlist(instance.allowedHosts),
|
|
667
|
+
resolve: this.#options.hostResolver,
|
|
668
|
+
});
|
|
669
|
+
policy.assertHttps(url);
|
|
670
|
+
assertAllowedPort(definition, url);
|
|
671
|
+
return policy.assertResolvable(normalizeHost(url.hostname));
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
#credentials(instance: StoredConnectorInstance): ConnectorCredentials {
|
|
675
|
+
if (instance.authKind === 'none' || instance.credential === null) {
|
|
676
|
+
return { kind: 'none' };
|
|
677
|
+
}
|
|
678
|
+
let opened: string;
|
|
679
|
+
try {
|
|
680
|
+
opened = this.#options.vault.open(
|
|
681
|
+
instance.credential,
|
|
682
|
+
credentialContext(instance.tenantId, instance.id),
|
|
683
|
+
);
|
|
684
|
+
} catch {
|
|
685
|
+
throw new CallRefusal('failed', 'credential-unavailable');
|
|
686
|
+
}
|
|
687
|
+
try {
|
|
688
|
+
return JSON.parse(opened) as ConnectorCredentials;
|
|
689
|
+
} catch {
|
|
690
|
+
throw new CallRefusal('failed', 'credential-unavailable');
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
async #headers(
|
|
695
|
+
instance: StoredConnectorInstance,
|
|
696
|
+
definition: ConnectorDefinition,
|
|
697
|
+
): Promise<Record<string, string>> {
|
|
698
|
+
const credentials = this.#credentials(instance);
|
|
699
|
+
if (credentials.kind === 'none') return {};
|
|
700
|
+
if (credentials.kind === 'api-key') {
|
|
701
|
+
return { [credentials.header]: credentials.value };
|
|
702
|
+
}
|
|
703
|
+
if (credentials.kind === 'bearer') {
|
|
704
|
+
return { authorization: `Bearer ${credentials.token}` };
|
|
705
|
+
}
|
|
706
|
+
return {
|
|
707
|
+
authorization: `Bearer ${await this.#accessToken(
|
|
708
|
+
instance,
|
|
709
|
+
definition,
|
|
710
|
+
credentials,
|
|
711
|
+
)}`,
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
async #accessToken(
|
|
716
|
+
instance: StoredConnectorInstance,
|
|
717
|
+
definition: ConnectorDefinition,
|
|
718
|
+
credentials: Extract<
|
|
719
|
+
ConnectorCredentials,
|
|
720
|
+
{ kind: 'oauth2-client-credentials' }
|
|
721
|
+
>,
|
|
722
|
+
): Promise<string> {
|
|
723
|
+
const key = tokenCacheKey(
|
|
724
|
+
instance.tenantId,
|
|
725
|
+
instance.id,
|
|
726
|
+
instance.credentialFingerprint,
|
|
727
|
+
);
|
|
728
|
+
const cached = this.#tokens.get(key);
|
|
729
|
+
if (cached && cached.expiresAt > this.#now()) return cached.token;
|
|
730
|
+
this.#tokens.delete(key);
|
|
731
|
+
const tokenUrl = new URL(credentials.tokenUrl);
|
|
732
|
+
/* The token endpoint is another outbound call of this instance: same
|
|
733
|
+
scheme, port, allowlist and address rules, and the same pinning. */
|
|
734
|
+
const addresses = await this.#assertReachable(
|
|
735
|
+
instance,
|
|
736
|
+
definition,
|
|
737
|
+
tokenUrl,
|
|
738
|
+
);
|
|
739
|
+
const body = new URLSearchParams({ grant_type: 'client_credentials' });
|
|
740
|
+
if (credentials.scope) body.set('scope', credentials.scope);
|
|
741
|
+
const authorization = Buffer.from(
|
|
742
|
+
`${credentials.clientId}:${credentials.clientSecret}`,
|
|
743
|
+
'utf8',
|
|
744
|
+
).toString('base64');
|
|
745
|
+
let payload: HttpExchange;
|
|
746
|
+
try {
|
|
747
|
+
payload = await sendPinned(tokenUrl, {
|
|
748
|
+
method: 'POST',
|
|
749
|
+
addresses: this.#dialable(addresses),
|
|
750
|
+
cap: TOKEN_RESPONSE_CAP_BYTES,
|
|
751
|
+
signal: AbortSignal.timeout(this.#options.limits().timeoutMs),
|
|
752
|
+
headers: {
|
|
753
|
+
'content-type': 'application/x-www-form-urlencoded',
|
|
754
|
+
accept: 'application/json',
|
|
755
|
+
authorization: `Basic ${authorization}`,
|
|
756
|
+
},
|
|
757
|
+
body: body.toString(),
|
|
758
|
+
...(this.#options.connect?.ca === undefined
|
|
759
|
+
? {}
|
|
760
|
+
: { ca: this.#options.connect.ca }),
|
|
761
|
+
});
|
|
762
|
+
} catch {
|
|
763
|
+
throw new CallRefusal('failed', 'credential-unavailable');
|
|
764
|
+
}
|
|
765
|
+
if (payload.status < 200 || payload.status >= 300 || payload.exceeded) {
|
|
766
|
+
throw new CallRefusal('failed', 'credential-unavailable');
|
|
767
|
+
}
|
|
768
|
+
const parsed = jsonBody(payload.text) as {
|
|
769
|
+
readonly access_token?: unknown;
|
|
770
|
+
readonly expires_in?: unknown;
|
|
771
|
+
} | null;
|
|
772
|
+
const token = parsed?.access_token;
|
|
773
|
+
if (typeof token !== 'string' || token.length === 0) {
|
|
774
|
+
throw new CallRefusal('failed', 'credential-unavailable');
|
|
775
|
+
}
|
|
776
|
+
const lifetime = Number(parsed?.expires_in);
|
|
777
|
+
const expiresIn = Number.isFinite(lifetime)
|
|
778
|
+
? Math.min(lifetime * 1_000, MAX_TOKEN_LIFETIME_MS)
|
|
779
|
+
: TOKEN_EXPIRY_MARGIN_MS * 2;
|
|
780
|
+
/* Insertion order is eviction order: one instance can never push the
|
|
781
|
+
cache past its bound. */
|
|
782
|
+
if (this.#tokens.size >= MAX_CACHED_TOKENS) {
|
|
783
|
+
const oldest = this.#tokens.keys().next();
|
|
784
|
+
if (!oldest.done) this.#tokens.delete(oldest.value);
|
|
785
|
+
}
|
|
786
|
+
this.#tokens.set(key, {
|
|
787
|
+
token,
|
|
788
|
+
expiresAt:
|
|
789
|
+
this.#now() + Math.max(expiresIn - TOKEN_EXPIRY_MARGIN_MS, 1_000),
|
|
790
|
+
});
|
|
791
|
+
return token;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
/* The verified addresses as they are dialled. A deployment dials exactly what
|
|
795
|
+
it verified; the seam is what lets a test reach its own server. */
|
|
796
|
+
#dialable(addresses: readonly ResolvedAddress[]): readonly ResolvedAddress[] {
|
|
797
|
+
const dial = this.#options.connect?.dial;
|
|
798
|
+
if (!dial) return addresses;
|
|
799
|
+
return addresses.map((entry) => ({ address: dial(entry.address) }));
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
async #send(
|
|
803
|
+
operation: ConnectorOperation,
|
|
804
|
+
prepared: PreparedRequest,
|
|
805
|
+
headers: Record<string, string>,
|
|
806
|
+
addresses: readonly ResolvedAddress[],
|
|
807
|
+
callerSignal: AbortSignal | undefined,
|
|
808
|
+
onWritten: () => void,
|
|
809
|
+
): Promise<{
|
|
810
|
+
readonly outcome: ConnectorCallOutcome;
|
|
811
|
+
readonly status: number;
|
|
812
|
+
readonly errorClass: ConnectorErrorClass | null;
|
|
813
|
+
readonly responseBytes: number;
|
|
814
|
+
readonly body: ConnectorJsonValue | null;
|
|
815
|
+
readonly bodyPreview: string;
|
|
816
|
+
}> {
|
|
817
|
+
const limits = this.#options.limits();
|
|
818
|
+
const signals = [AbortSignal.timeout(limits.timeoutMs)];
|
|
819
|
+
if (callerSignal) signals.push(callerSignal);
|
|
820
|
+
let payload: HttpExchange;
|
|
821
|
+
try {
|
|
822
|
+
payload = await sendPinned(prepared.target, {
|
|
823
|
+
method: operation.method,
|
|
824
|
+
addresses: this.#dialable(addresses),
|
|
825
|
+
cap: limits.maxResponseBytes,
|
|
826
|
+
/* One deadline over the headers and the body, so a sender that
|
|
827
|
+
answers and then stalls mid-body is still bounded. */
|
|
828
|
+
signal: AbortSignal.any(signals),
|
|
829
|
+
headers: {
|
|
830
|
+
accept: 'application/json',
|
|
831
|
+
...(prepared.body === null
|
|
832
|
+
? {}
|
|
833
|
+
: { 'content-type': 'application/json' }),
|
|
834
|
+
...headers,
|
|
835
|
+
},
|
|
836
|
+
body: prepared.body,
|
|
837
|
+
onWritten,
|
|
838
|
+
...(this.#options.connect?.ca === undefined
|
|
839
|
+
? {}
|
|
840
|
+
: { ca: this.#options.connect.ca }),
|
|
841
|
+
});
|
|
842
|
+
} catch (error) {
|
|
843
|
+
throw transportFailure(error);
|
|
844
|
+
}
|
|
845
|
+
if (payload.exceeded) {
|
|
846
|
+
return {
|
|
847
|
+
outcome: 'failed',
|
|
848
|
+
status: payload.status,
|
|
849
|
+
errorClass: 'response-too-large',
|
|
850
|
+
responseBytes: payload.bytes,
|
|
851
|
+
body: null,
|
|
852
|
+
bodyPreview: '',
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
const status = payload.status;
|
|
856
|
+
const errorClass: ConnectorErrorClass | null =
|
|
857
|
+
status >= 200 && status < 300
|
|
858
|
+
? null
|
|
859
|
+
: status >= 500
|
|
860
|
+
? 'response-5xx'
|
|
861
|
+
: status >= 400
|
|
862
|
+
? 'response-4xx'
|
|
863
|
+
: 'egress-refused';
|
|
864
|
+
return {
|
|
865
|
+
outcome: errorClass === null ? 'succeeded' : 'failed',
|
|
866
|
+
status,
|
|
867
|
+
errorClass,
|
|
868
|
+
responseBytes: payload.bytes,
|
|
869
|
+
body: jsonBody(payload.text),
|
|
870
|
+
bodyPreview: payload.text.slice(0, CALL_BODY_PREVIEW_BYTES),
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
async #record(
|
|
875
|
+
idempotencyKey: string | null,
|
|
876
|
+
result: Omit<ConnectorCall, 'id' | 'occurredAt'> & {
|
|
877
|
+
readonly body: ConnectorJsonValue | null;
|
|
878
|
+
readonly bodyPreview: string;
|
|
879
|
+
},
|
|
880
|
+
): Promise<ConnectorCallResult> {
|
|
881
|
+
const { body, bodyPreview, ...call } = result;
|
|
882
|
+
const record: ConnectorCall = {
|
|
883
|
+
...call,
|
|
884
|
+
id: randomUUID(),
|
|
885
|
+
occurredAt: this.#now(),
|
|
886
|
+
};
|
|
887
|
+
/* The claimed key is bound to this call in the same transaction, so a
|
|
888
|
+
retry either sees the binding or sees no call at all. */
|
|
889
|
+
await this.#options.repository.recordCall(record, idempotencyKey);
|
|
890
|
+
return {
|
|
891
|
+
callId: record.id,
|
|
892
|
+
outcome: record.outcome,
|
|
893
|
+
status: record.status,
|
|
894
|
+
errorClass: record.errorClass,
|
|
895
|
+
durationMs: record.durationMs,
|
|
896
|
+
requestBytes: record.requestBytes,
|
|
897
|
+
responseBytes: record.responseBytes,
|
|
898
|
+
body,
|
|
899
|
+
bodyPreview,
|
|
900
|
+
replayed: false,
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
}
|