@shipfox/api-integration-core 12.2.0 → 12.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/.turbo/turbo-build.log +11 -8
  2. package/CHANGELOG.md +25 -0
  3. package/dist/core/secret-cleanup.d.ts +20 -0
  4. package/dist/core/secret-cleanup.d.ts.map +1 -0
  5. package/dist/core/secret-cleanup.js +178 -0
  6. package/dist/core/secret-cleanup.js.map +1 -0
  7. package/dist/db/db.d.ts +562 -0
  8. package/dist/db/db.d.ts.map +1 -1
  9. package/dist/db/db.js +2 -0
  10. package/dist/db/db.js.map +1 -1
  11. package/dist/db/schema/secret-cleanups.d.ts +284 -0
  12. package/dist/db/schema/secret-cleanups.d.ts.map +1 -0
  13. package/dist/db/schema/secret-cleanups.js +39 -0
  14. package/dist/db/schema/secret-cleanups.js.map +1 -0
  15. package/dist/db/secret-cleanups.d.ts +52 -0
  16. package/dist/db/secret-cleanups.d.ts.map +1 -0
  17. package/dist/db/secret-cleanups.js +105 -0
  18. package/dist/db/secret-cleanups.js.map +1 -0
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +8 -1
  21. package/dist/index.js.map +1 -1
  22. package/dist/presentation/routes/manage-connections.d.ts.map +1 -1
  23. package/dist/presentation/routes/manage-connections.js +57 -18
  24. package/dist/presentation/routes/manage-connections.js.map +1 -1
  25. package/dist/providers/jira.d.ts.map +1 -1
  26. package/dist/providers/jira.js +51 -14
  27. package/dist/providers/jira.js.map +1 -1
  28. package/dist/temporal/activities/index.d.ts +5 -1
  29. package/dist/temporal/activities/index.d.ts.map +1 -1
  30. package/dist/temporal/activities/index.js +8 -2
  31. package/dist/temporal/activities/index.js.map +1 -1
  32. package/dist/temporal/constants.d.ts +1 -0
  33. package/dist/temporal/constants.d.ts.map +1 -1
  34. package/dist/temporal/constants.js +1 -0
  35. package/dist/temporal/constants.js.map +1 -1
  36. package/dist/temporal/workflows/cleanup-integration-secrets-cron.d.ts +2 -0
  37. package/dist/temporal/workflows/cleanup-integration-secrets-cron.d.ts.map +1 -0
  38. package/dist/temporal/workflows/cleanup-integration-secrets-cron.js +21 -0
  39. package/dist/temporal/workflows/cleanup-integration-secrets-cron.js.map +1 -0
  40. package/dist/temporal/workflows/index.bundle.js +64 -3
  41. package/dist/temporal/workflows/index.d.ts +1 -0
  42. package/dist/temporal/workflows/index.d.ts.map +1 -1
  43. package/dist/temporal/workflows/index.js +1 -0
  44. package/dist/temporal/workflows/index.js.map +1 -1
  45. package/dist/tsconfig.test.tsbuildinfo +1 -1
  46. package/drizzle/0001_durable_secret_cleanup.sql +22 -0
  47. package/drizzle/meta/0001_snapshot.json +180 -2
  48. package/drizzle/meta/_journal.json +7 -0
  49. package/package.json +11 -10
  50. package/src/core/secret-cleanup.test.ts +129 -0
  51. package/src/core/secret-cleanup.ts +229 -0
  52. package/src/db/db.ts +2 -0
  53. package/src/db/schema/secret-cleanups.ts +42 -0
  54. package/src/db/secret-cleanups.test.ts +113 -0
  55. package/src/db/secret-cleanups.ts +209 -0
  56. package/src/index.ts +6 -1
  57. package/src/presentation/routes/manage-connections.test.ts +232 -0
  58. package/src/presentation/routes/manage-connections.ts +45 -11
  59. package/src/providers/jira.ts +61 -13
  60. package/src/temporal/activities/index.ts +11 -1
  61. package/src/temporal/constants.ts +2 -0
  62. package/src/temporal/workflows/cleanup-integration-secrets-cron.ts +20 -0
  63. package/src/temporal/workflows/index.ts +1 -0
  64. package/test/env.ts +3 -0
  65. package/test/globalSetup.ts +1 -0
  66. package/test/route-utils.ts +3 -0
  67. package/test/setup.ts +12 -0
  68. package/tsconfig.build.tsbuildinfo +1 -1
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {dirname, resolve} from 'node:path';\nimport {fileURLToPath} from 'node:url';\nimport {\n integrationsEventSchemas,\n type StoredWebhookRequest,\n type WebhookProcessingResult,\n type WebhookRequestProcessor,\n type WebhookRouteId,\n} from '@shipfox/api-integration-spi';\nimport type {WorkflowsModuleClient} from '@shipfox/api-workflows-dto/inter-module';\nimport type {WorkspacesInterModuleClient} from '@shipfox/api-workspaces-dto/inter-module';\nimport {reportError} from '@shipfox/node-error-monitoring';\nimport type {ModuleService, ShipfoxModule} from '@shipfox/node-module';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport type {IntegrationProvider} from '#core/entities/provider.js';\nimport {WebhookProcessorNotConfiguredError} from '#core/errors.js';\nimport {\n createIntegrationProviderRegistry,\n type IntegrationProviderRegistry,\n} from '#core/providers/registry.js';\nimport {\n createSourceControlIntegrationService,\n type IntegrationSourceControlService,\n} from '#core/source-control-service.js';\nimport {getIntegrationConnectionById} from '#db/connections.js';\nimport {db} from '#db/db.js';\nimport {migrationsPath} from '#db/migrations.js';\nimport {integrationsOutbox} from '#db/schema/outbox.js';\nimport {createIntegrationsInterModulePresentation} from '#presentation/inter-module.js';\nimport {createIntegrationRoutes} from '#presentation/routes/index.js';\nimport {loadEnabledProviderModules} from '#providers/modules.js';\nimport type {\n IntegrationModuleParts,\n IntegrationProviderSecrets,\n WebhookProcessorRegistration,\n} from '#providers/types.js';\nimport {createIntegrationsMaintenanceActivities} from '#temporal/activities/index.js';\nimport {INTEGRATIONS_MAINTENANCE_TASK_QUEUE} from '#temporal/constants.js';\n\nconst packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');\nconst maintenanceWorkflowsPath = resolve(packageRoot, 'dist/temporal/workflows/index.js');\n\nexport type {\n StoredWebhookRequest,\n WebhookProcessingResult,\n WebhookRequestProcessor,\n WebhookRouteId,\n} from '@shipfox/api-integration-spi';\nexport {\n buildProviderRepositoryId,\n MAX_REPOSITORY_FILE_BYTES,\n parseProviderRepositoryId,\n} from '@shipfox/api-integration-spi';\nexport type {\n AgentToolCatalogs,\n AgentToolSelectionCatalogs,\n LoadWorkspaceConnectionSnapshot,\n WorkspaceConnectionSnapshot,\n WorkspaceConnectionSnapshotEntry,\n} from '#core/agent-tool-selection.js';\nexport {\n buildAgentToolCatalogs,\n buildAgentToolSelectionCatalogs,\n createWorkspaceConnectionSnapshotLoader,\n} from '#core/agent-tool-selection.js';\nexport type {\n IntegrationConnection,\n IntegrationConnectionLifecycleStatus,\n} from '#core/entities/connection.js';\nexport type {\n IntegrationCapability,\n IntegrationProvider,\n IntegrationProviderAdapters,\n IntegrationProviderKind,\n RegisteredIntegrationProvider,\n} from '#core/entities/provider.js';\nexport type {IntegrationProviderErrorReason} from '#core/errors.js';\nexport {\n ConnectionSlugConflictError,\n IntegrationCapabilityUnavailableError,\n IntegrationCheckoutUnsupportedError,\n IntegrationConnectionInactiveError,\n IntegrationConnectionNotFoundError,\n IntegrationConnectionWorkspaceMismatchError,\n IntegrationProviderError,\n IntegrationProviderUnavailableError,\n WebhookProcessorNotConfiguredError,\n} from '#core/errors.js';\nexport type {\n AgentToolCallInput,\n AgentToolCatalogEntry,\n AgentToolCatalogMethod,\n AgentToolJsonSchema,\n AgentToolSensitivity,\n AgentToolSession,\n AgentToolsProvider,\n OpenAgentToolsSessionInput,\n} from '#core/providers/agent-tools.js';\nexport {redactCheckoutSpec} from '#core/providers/redact-checkout-spec.js';\nexport type {IntegrationProviderRegistry} from '#core/providers/registry.js';\nexport type {\n CheckoutCredentials,\n CheckoutPermissions,\n CheckoutSpec,\n CreateCheckoutSpecInput,\n FetchFileInput,\n FileEntry,\n FilePage,\n FileSnapshot,\n ListFilesInput,\n ListRepositoriesInput,\n RepositoryPage,\n RepositorySnapshot,\n RepositoryVisibility,\n ResolveRepositoryInput,\n SourceControlProvider,\n TriggerReference,\n} from '#core/providers/source-control.js';\nexport type {IntegrationSourceControlService} from '#core/source-control-service.js';\nexport {createSourceControlIntegrationService} from '#core/source-control-service.js';\nexport type {\n GetIntegrationConnectionByIdFn,\n GetIntegrationConnectionBySlugFn,\n} from '#db/connections.js';\nexport {getIntegrationConnectionById, getIntegrationConnectionBySlug} from '#db/connections.js';\nexport type {\n ClaimWebhookDeliveryFn,\n PublishIntegrationEventReceivedFn,\n PublishIntegrationEventReceivedParams,\n PublishIntegrationEventReceivedResult,\n PublishSourcePushFn,\n PublishSourcePushParams,\n PublishSourceRepositoryUpdatedFn,\n PublishSourceRepositoryUpdatedParams,\n RecordDeliveryOnlyFn,\n RecordDeliveryOnlyParams,\n} from '#db/webhook-deliveries.js';\nexport {\n claimWebhookDelivery,\n pruneWebhookDeliveries,\n publishSourceRepositoryUpdated,\n} from '#db/webhook-deliveries.js';\nexport {integrationRouteErrorHandler} from '#presentation/routes/errors.js';\n\nexport interface CreateIntegrationsModuleOptions {\n providers?: IntegrationProvider[] | undefined;\n /**\n * Pre-built module parts, bypassing config-gated provider loading. Test-only seam\n * for exercising a provider's database, workers, or startup tasks directly. Takes\n * precedence over `providers`.\n */\n parts?: IntegrationModuleParts[] | undefined;\n secrets?: IntegrationProviderSecrets | undefined;\n workspaces?: WorkspacesInterModuleClient | undefined;\n agentTools?:\n | {\n workflows: WorkflowsModuleClient;\n }\n | undefined;\n webhookDeliverySource?: WebhookDeliverySource | undefined;\n}\n\n/**\n * Hosted runtimes implement this port to receive stored webhook requests. The\n * integration module starts its returned service after migrations complete.\n */\nexport interface WebhookDeliverySource {\n createService(processor: WebhookRequestProcessor): ModuleService;\n}\n\nexport interface IntegrationsContext {\n module: ShipfoxModule;\n registry: IntegrationProviderRegistry;\n capabilities: {\n sourceControl: IntegrationSourceControlService;\n };\n sourceControl: IntegrationSourceControlService;\n webhookProcessor: WebhookRequestProcessor;\n /**\n * Runs every enabled provider's one-shot boot-time tasks, after modules are initialized\n * (migrations done). Failures are isolated and logged, never rethrown, so a provider task\n * can never gate API boot. No-op when no enabled provider contributes a task.\n */\n runStartupTasks: () => Promise<void>;\n}\n\nexport async function createIntegrationsModule(\n options: CreateIntegrationsModuleOptions = {},\n): Promise<ShipfoxModule> {\n return (await createIntegrationsContext(options)).module;\n}\n\nexport async function createIntegrationsContext(\n options: CreateIntegrationsModuleOptions = {},\n): Promise<IntegrationsContext> {\n const workspaces = options.workspaces;\n const parts: IntegrationModuleParts[] =\n options.parts ??\n (options.providers\n ? options.providers.map((provider) => ({\n provider,\n webhookProcessors: provider.webhookProcessors,\n }))\n : await loadEnabledProviderModules({\n secrets: options.secrets,\n ...(workspaces\n ? {\n requireActiveWorkspaceMembership: (input: {\n workspaceId: string;\n userId: string;\n memberships: ReadonlyArray<\n import('@shipfox/api-auth-context').UserContextMembership\n >;\n }) =>\n workspaces.requireActiveMembership({\n ...input,\n memberships: [...input.memberships],\n }),\n }\n : {}),\n }));\n\n const registry = createIntegrationProviderRegistry(parts.map((part) => part.provider));\n const sourceControl = createSourceControlIntegrationService({\n registry,\n getIntegrationConnectionById,\n });\n const webhookProcessor = createComposedWebhookProcessor(\n parts.flatMap((part) => part.webhookProcessors ?? []),\n );\n\n async function runStartupTasks(): Promise<void> {\n for (const task of parts.flatMap((part) => part.startupTasks ?? [])) {\n // A provider convenience must never gate API boot.\n try {\n await task();\n } catch (error) {\n logger().error({err: error}, 'Integration startup task failed, continuing boot');\n reportError(error, {boundary: 'integration.startup'});\n }\n }\n }\n\n const module: ShipfoxModule = {\n name: 'integrations',\n interModulePresentations: [\n createIntegrationsInterModulePresentation({registry, sourceControl}),\n ],\n startupTasks: runStartupTasks,\n database: [\n {db, migrationsPath, databaseNamespace: 'integrations'},\n ...parts.flatMap((part) => (part.database ? [part.database] : [])),\n ],\n routes: createIntegrationRoutes(registry, sourceControl, {\n agentTools: options.agentTools\n ? {\n workflows: options.agentTools.workflows,\n getIntegrationConnectionById,\n }\n : undefined,\n }),\n e2eRoutes: parts.flatMap((part) => part.e2eRoutes ?? []),\n publishers: [\n {name: 'integrations', table: integrationsOutbox, db, eventSchemas: integrationsEventSchemas},\n ],\n workers: [\n {\n taskQueue: INTEGRATIONS_MAINTENANCE_TASK_QUEUE,\n workflowsPath: maintenanceWorkflowsPath,\n activities: createIntegrationsMaintenanceActivities,\n workflows: [\n {\n name: 'pruneWebhookDeliveriesCron',\n id: 'integrations-prune-webhook-deliveries',\n cronSchedule: '0 3 * * *',\n },\n ],\n },\n ...parts.flatMap((part) => part.workers ?? []),\n ],\n ...(options.webhookDeliverySource\n ? {services: [options.webhookDeliverySource.createService(webhookProcessor)]}\n : {}),\n };\n\n return {\n module,\n registry,\n capabilities: {sourceControl},\n sourceControl,\n webhookProcessor,\n runStartupTasks,\n };\n}\n\nfunction createComposedWebhookProcessor(\n registrations: WebhookProcessorRegistration[],\n): WebhookRequestProcessor {\n const processors = new Map<WebhookRouteId, WebhookRequestProcessor>();\n for (const registration of registrations) {\n for (const routeId of registration.routeIds) {\n if (processors.has(routeId)) {\n throw new Error(`Webhook processor is registered more than once for ${routeId}`);\n }\n processors.set(routeId, registration.processor);\n }\n }\n\n return {\n async process(request: StoredWebhookRequest): Promise<WebhookProcessingResult> {\n const processor = processors.get(request.route_id);\n if (!processor) {\n throw new WebhookProcessorNotConfiguredError(request.route_id);\n }\n return await processor.process(request);\n },\n };\n}\n"],"names":["dirname","resolve","fileURLToPath","integrationsEventSchemas","reportError","logger","WebhookProcessorNotConfiguredError","createIntegrationProviderRegistry","createSourceControlIntegrationService","getIntegrationConnectionById","db","migrationsPath","integrationsOutbox","createIntegrationsInterModulePresentation","createIntegrationRoutes","loadEnabledProviderModules","createIntegrationsMaintenanceActivities","INTEGRATIONS_MAINTENANCE_TASK_QUEUE","packageRoot","url","maintenanceWorkflowsPath","buildProviderRepositoryId","MAX_REPOSITORY_FILE_BYTES","parseProviderRepositoryId","buildAgentToolCatalogs","buildAgentToolSelectionCatalogs","createWorkspaceConnectionSnapshotLoader","ConnectionSlugConflictError","IntegrationCapabilityUnavailableError","IntegrationCheckoutUnsupportedError","IntegrationConnectionInactiveError","IntegrationConnectionNotFoundError","IntegrationConnectionWorkspaceMismatchError","IntegrationProviderError","IntegrationProviderUnavailableError","redactCheckoutSpec","getIntegrationConnectionBySlug","claimWebhookDelivery","pruneWebhookDeliveries","publishSourceRepositoryUpdated","integrationRouteErrorHandler","createIntegrationsModule","options","createIntegrationsContext","module","workspaces","parts","providers","map","provider","webhookProcessors","secrets","requireActiveWorkspaceMembership","input","requireActiveMembership","memberships","registry","part","sourceControl","webhookProcessor","createComposedWebhookProcessor","flatMap","runStartupTasks","task","startupTasks","error","err","boundary","name","interModulePresentations","database","databaseNamespace","routes","agentTools","workflows","undefined","e2eRoutes","publishers","table","eventSchemas","workers","taskQueue","workflowsPath","activities","id","cronSchedule","webhookDeliverySource","services","createService","capabilities","registrations","processors","Map","registration","routeId","routeIds","has","Error","set","processor","process","request","get","route_id"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {dirname, resolve} from 'node:path';\nimport {fileURLToPath} from 'node:url';\nimport {\n integrationsEventSchemas,\n type StoredWebhookRequest,\n type WebhookProcessingResult,\n type WebhookRequestProcessor,\n type WebhookRouteId,\n} from '@shipfox/api-integration-spi';\nimport type {WorkflowsModuleClient} from '@shipfox/api-workflows-dto/inter-module';\nimport type {WorkspacesInterModuleClient} from '@shipfox/api-workspaces-dto/inter-module';\nimport {reportError} from '@shipfox/node-error-monitoring';\nimport type {ModuleService, ShipfoxModule} from '@shipfox/node-module';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport type {IntegrationProvider} from '#core/entities/provider.js';\nimport {WebhookProcessorNotConfiguredError} from '#core/errors.js';\nimport {\n createIntegrationProviderRegistry,\n type IntegrationProviderRegistry,\n} from '#core/providers/registry.js';\nimport {\n createSourceControlIntegrationService,\n type IntegrationSourceControlService,\n} from '#core/source-control-service.js';\nimport {getIntegrationConnectionById} from '#db/connections.js';\nimport {db} from '#db/db.js';\nimport {migrationsPath} from '#db/migrations.js';\nimport {integrationsOutbox} from '#db/schema/outbox.js';\nimport {createIntegrationsInterModulePresentation} from '#presentation/inter-module.js';\nimport {createIntegrationRoutes} from '#presentation/routes/index.js';\nimport {loadEnabledProviderModules} from '#providers/modules.js';\nimport type {\n IntegrationModuleParts,\n IntegrationProviderSecrets,\n WebhookProcessorRegistration,\n} from '#providers/types.js';\nimport {createIntegrationsMaintenanceActivities} from '#temporal/activities/index.js';\nimport {INTEGRATIONS_MAINTENANCE_TASK_QUEUE} from '#temporal/constants.js';\n\nconst packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');\nconst maintenanceWorkflowsPath = resolve(packageRoot, 'dist/temporal/workflows/index.js');\n\nexport type {\n StoredWebhookRequest,\n WebhookProcessingResult,\n WebhookRequestProcessor,\n WebhookRouteId,\n} from '@shipfox/api-integration-spi';\nexport {\n buildProviderRepositoryId,\n MAX_REPOSITORY_FILE_BYTES,\n parseProviderRepositoryId,\n} from '@shipfox/api-integration-spi';\nexport type {\n AgentToolCatalogs,\n AgentToolSelectionCatalogs,\n LoadWorkspaceConnectionSnapshot,\n WorkspaceConnectionSnapshot,\n WorkspaceConnectionSnapshotEntry,\n} from '#core/agent-tool-selection.js';\nexport {\n buildAgentToolCatalogs,\n buildAgentToolSelectionCatalogs,\n createWorkspaceConnectionSnapshotLoader,\n} from '#core/agent-tool-selection.js';\nexport type {\n IntegrationConnection,\n IntegrationConnectionLifecycleStatus,\n} from '#core/entities/connection.js';\nexport type {\n IntegrationCapability,\n IntegrationProvider,\n IntegrationProviderAdapters,\n IntegrationProviderKind,\n RegisteredIntegrationProvider,\n} from '#core/entities/provider.js';\nexport type {IntegrationProviderErrorReason} from '#core/errors.js';\nexport {\n ConnectionSlugConflictError,\n IntegrationCapabilityUnavailableError,\n IntegrationCheckoutUnsupportedError,\n IntegrationConnectionInactiveError,\n IntegrationConnectionNotFoundError,\n IntegrationConnectionWorkspaceMismatchError,\n IntegrationProviderError,\n IntegrationProviderUnavailableError,\n WebhookProcessorNotConfiguredError,\n} from '#core/errors.js';\nexport type {\n AgentToolCallInput,\n AgentToolCatalogEntry,\n AgentToolCatalogMethod,\n AgentToolJsonSchema,\n AgentToolSensitivity,\n AgentToolSession,\n AgentToolsProvider,\n OpenAgentToolsSessionInput,\n} from '#core/providers/agent-tools.js';\nexport {redactCheckoutSpec} from '#core/providers/redact-checkout-spec.js';\nexport type {IntegrationProviderRegistry} from '#core/providers/registry.js';\nexport type {\n CheckoutCredentials,\n CheckoutPermissions,\n CheckoutSpec,\n CreateCheckoutSpecInput,\n FetchFileInput,\n FileEntry,\n FilePage,\n FileSnapshot,\n ListFilesInput,\n ListRepositoriesInput,\n RepositoryPage,\n RepositorySnapshot,\n RepositoryVisibility,\n ResolveRepositoryInput,\n SourceControlProvider,\n TriggerReference,\n} from '#core/providers/source-control.js';\nexport type {IntegrationSourceControlService} from '#core/source-control-service.js';\nexport {createSourceControlIntegrationService} from '#core/source-control-service.js';\nexport type {\n GetIntegrationConnectionByIdFn,\n GetIntegrationConnectionBySlugFn,\n} from '#db/connections.js';\nexport {getIntegrationConnectionById, getIntegrationConnectionBySlug} from '#db/connections.js';\nexport type {\n ClaimWebhookDeliveryFn,\n PublishIntegrationEventReceivedFn,\n PublishIntegrationEventReceivedParams,\n PublishIntegrationEventReceivedResult,\n PublishSourcePushFn,\n PublishSourcePushParams,\n PublishSourceRepositoryUpdatedFn,\n PublishSourceRepositoryUpdatedParams,\n RecordDeliveryOnlyFn,\n RecordDeliveryOnlyParams,\n} from '#db/webhook-deliveries.js';\nexport {\n claimWebhookDelivery,\n pruneWebhookDeliveries,\n publishSourceRepositoryUpdated,\n} from '#db/webhook-deliveries.js';\nexport {integrationRouteErrorHandler} from '#presentation/routes/errors.js';\n\nexport interface CreateIntegrationsModuleOptions {\n providers?: IntegrationProvider[] | undefined;\n /**\n * Pre-built module parts, bypassing config-gated provider loading. Test-only seam\n * for exercising a provider's database, workers, or startup tasks directly. Takes\n * precedence over `providers`.\n */\n parts?: IntegrationModuleParts[] | undefined;\n secrets?: IntegrationProviderSecrets | undefined;\n workspaces?: WorkspacesInterModuleClient | undefined;\n agentTools?:\n | {\n workflows: WorkflowsModuleClient;\n }\n | undefined;\n webhookDeliverySource?: WebhookDeliverySource | undefined;\n}\n\n/**\n * Hosted runtimes implement this port to receive stored webhook requests. The\n * integration module starts its returned service after migrations complete.\n */\nexport interface WebhookDeliverySource {\n createService(processor: WebhookRequestProcessor): ModuleService;\n}\n\nexport interface IntegrationsContext {\n module: ShipfoxModule;\n registry: IntegrationProviderRegistry;\n capabilities: {\n sourceControl: IntegrationSourceControlService;\n };\n sourceControl: IntegrationSourceControlService;\n webhookProcessor: WebhookRequestProcessor;\n /**\n * Runs every enabled provider's one-shot boot-time tasks, after modules are initialized\n * (migrations done). Failures are isolated and logged, never rethrown, so a provider task\n * can never gate API boot. No-op when no enabled provider contributes a task.\n */\n runStartupTasks: () => Promise<void>;\n}\n\nexport async function createIntegrationsModule(\n options: CreateIntegrationsModuleOptions = {},\n): Promise<ShipfoxModule> {\n return (await createIntegrationsContext(options)).module;\n}\n\nexport async function createIntegrationsContext(\n options: CreateIntegrationsModuleOptions = {},\n): Promise<IntegrationsContext> {\n const workspaces = options.workspaces;\n const parts: IntegrationModuleParts[] =\n options.parts ??\n (options.providers\n ? options.providers.map((provider) => ({\n provider,\n webhookProcessors: provider.webhookProcessors,\n }))\n : await loadEnabledProviderModules({\n secrets: options.secrets,\n ...(workspaces\n ? {\n requireActiveWorkspaceMembership: (input: {\n workspaceId: string;\n userId: string;\n memberships: ReadonlyArray<\n import('@shipfox/api-auth-context').UserContextMembership\n >;\n }) =>\n workspaces.requireActiveMembership({\n ...input,\n memberships: [...input.memberships],\n }),\n }\n : {}),\n }));\n\n const registry = createIntegrationProviderRegistry(parts.map((part) => part.provider));\n const sourceControl = createSourceControlIntegrationService({\n registry,\n getIntegrationConnectionById,\n });\n const webhookProcessor = createComposedWebhookProcessor(\n parts.flatMap((part) => part.webhookProcessors ?? []),\n );\n\n async function runStartupTasks(): Promise<void> {\n for (const task of parts.flatMap((part) => part.startupTasks ?? [])) {\n // A provider convenience must never gate API boot.\n try {\n await task();\n } catch (error) {\n logger().error({err: error}, 'Integration startup task failed, continuing boot');\n reportError(error, {boundary: 'integration.startup'});\n }\n }\n }\n\n const module: ShipfoxModule = {\n name: 'integrations',\n interModulePresentations: [\n createIntegrationsInterModulePresentation({registry, sourceControl}),\n ],\n startupTasks: runStartupTasks,\n database: [\n {db, migrationsPath, databaseNamespace: 'integrations'},\n ...parts.flatMap((part) => (part.database ? [part.database] : [])),\n ],\n routes: createIntegrationRoutes(registry, sourceControl, {\n agentTools: options.agentTools\n ? {\n workflows: options.agentTools.workflows,\n getIntegrationConnectionById,\n }\n : undefined,\n }),\n e2eRoutes: parts.flatMap((part) => part.e2eRoutes ?? []),\n publishers: [\n {name: 'integrations', table: integrationsOutbox, db, eventSchemas: integrationsEventSchemas},\n ],\n workers: [\n {\n taskQueue: INTEGRATIONS_MAINTENANCE_TASK_QUEUE,\n workflowsPath: maintenanceWorkflowsPath,\n activities: () => createIntegrationsMaintenanceActivities({registry}),\n workflows: [\n {\n name: 'pruneWebhookDeliveriesCron',\n id: 'integrations-prune-webhook-deliveries',\n cronSchedule: '0 3 * * *',\n },\n {\n name: 'cleanupIntegrationSecretsCron',\n id: 'integrations-cleanup-secret-namespaces',\n cronSchedule: '*/5 * * * *',\n },\n ],\n },\n ...parts.flatMap((part) => part.workers ?? []),\n ],\n ...(options.webhookDeliverySource\n ? {services: [options.webhookDeliverySource.createService(webhookProcessor)]}\n : {}),\n };\n\n return {\n module,\n registry,\n capabilities: {sourceControl},\n sourceControl,\n webhookProcessor,\n runStartupTasks,\n };\n}\n\nfunction createComposedWebhookProcessor(\n registrations: WebhookProcessorRegistration[],\n): WebhookRequestProcessor {\n const processors = new Map<WebhookRouteId, WebhookRequestProcessor>();\n for (const registration of registrations) {\n for (const routeId of registration.routeIds) {\n if (processors.has(routeId)) {\n throw new Error(`Webhook processor is registered more than once for ${routeId}`);\n }\n processors.set(routeId, registration.processor);\n }\n }\n\n return {\n async process(request: StoredWebhookRequest): Promise<WebhookProcessingResult> {\n const processor = processors.get(request.route_id);\n if (!processor) {\n throw new WebhookProcessorNotConfiguredError(request.route_id);\n }\n return await processor.process(request);\n },\n };\n}\n"],"names":["dirname","resolve","fileURLToPath","integrationsEventSchemas","reportError","logger","WebhookProcessorNotConfiguredError","createIntegrationProviderRegistry","createSourceControlIntegrationService","getIntegrationConnectionById","db","migrationsPath","integrationsOutbox","createIntegrationsInterModulePresentation","createIntegrationRoutes","loadEnabledProviderModules","createIntegrationsMaintenanceActivities","INTEGRATIONS_MAINTENANCE_TASK_QUEUE","packageRoot","url","maintenanceWorkflowsPath","buildProviderRepositoryId","MAX_REPOSITORY_FILE_BYTES","parseProviderRepositoryId","buildAgentToolCatalogs","buildAgentToolSelectionCatalogs","createWorkspaceConnectionSnapshotLoader","ConnectionSlugConflictError","IntegrationCapabilityUnavailableError","IntegrationCheckoutUnsupportedError","IntegrationConnectionInactiveError","IntegrationConnectionNotFoundError","IntegrationConnectionWorkspaceMismatchError","IntegrationProviderError","IntegrationProviderUnavailableError","redactCheckoutSpec","getIntegrationConnectionBySlug","claimWebhookDelivery","pruneWebhookDeliveries","publishSourceRepositoryUpdated","integrationRouteErrorHandler","createIntegrationsModule","options","createIntegrationsContext","module","workspaces","parts","providers","map","provider","webhookProcessors","secrets","requireActiveWorkspaceMembership","input","requireActiveMembership","memberships","registry","part","sourceControl","webhookProcessor","createComposedWebhookProcessor","flatMap","runStartupTasks","task","startupTasks","error","err","boundary","name","interModulePresentations","database","databaseNamespace","routes","agentTools","workflows","undefined","e2eRoutes","publishers","table","eventSchemas","workers","taskQueue","workflowsPath","activities","id","cronSchedule","webhookDeliverySource","services","createService","capabilities","registrations","processors","Map","registration","routeId","routeIds","has","Error","set","processor","process","request","get","route_id"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"manage-connections.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/manage-connections.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAC,2BAA2B,EAAC,MAAM,6BAA6B,CAAC;AAa7E,wBAAgB,sCAAsC,CAAC,QAAQ,EAAE,2BAA2B,mDAgC3F;AAED,wBAAgB,sCAAsC,CAAC,QAAQ,EAAE,2BAA2B,mDA8C3F"}
1
+ {"version":3,"file":"manage-connections.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/manage-connections.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAC,2BAA2B,EAAC,MAAM,6BAA6B,CAAC;AAe7E,wBAAgB,sCAAsC,CAAC,QAAQ,EAAE,2BAA2B,mDAgC3F;AAED,wBAAgB,sCAAsC,CAAC,QAAQ,EAAE,2BAA2B,mDA8E3F"}
@@ -2,8 +2,10 @@ import { AUTH_USER, requireWorkspaceAccess } from '@shipfox/api-auth-context';
2
2
  import { integrationConnectionDtoSchema, updateIntegrationConnectionBodySchema } from '@shipfox/api-integration-spi';
3
3
  import { ClientError, defineRoute } from '@shipfox/node-fastify';
4
4
  import { z } from 'zod';
5
+ import { processIntegrationSecretCleanups } from '#core/secret-cleanup.js';
5
6
  import { deleteIntegrationConnection, getIntegrationConnectionById, updateIntegrationConnectionLifecycleStatus } from '#db/connections.js';
6
7
  import { db } from '#db/db.js';
8
+ import { enqueueIntegrationSecretCleanup } from '#db/secret-cleanups.js';
7
9
  import { toIntegrationConnectionDto } from '#presentation/dto/integrations.js';
8
10
  const connectionParamsSchema = z.object({
9
11
  connectionId: z.string().uuid()
@@ -72,31 +74,68 @@ export function createDeleteIntegrationConnectionRoute(registry) {
72
74
  workspaceId: connection.workspaceId
73
75
  });
74
76
  const provider = registry.list().find((candidate)=>candidate.provider === connection.provider);
75
- const hasCleanupHooks = provider?.deleteConnectionRecords !== undefined || provider?.deleteConnectionSecrets !== undefined;
77
+ const hasCleanupHooks = provider?.deleteConnectionRemoteResources !== undefined || provider?.deleteConnectionRecords !== undefined || provider?.deleteConnectionSecrets !== undefined;
76
78
  if (!hasCleanupHooks) {
77
79
  request.log.warn({
78
80
  connectionId: connection.id,
79
81
  provider: connection.provider
80
82
  }, 'Deleting integration connection without provider cleanup');
81
83
  }
82
- await db().transaction(async (tx)=>{
83
- await provider?.deleteConnectionRecords?.(connection, {
84
- tx
84
+ const deleteConnection = async ()=>{
85
+ let deleteRemoteResources;
86
+ try {
87
+ deleteRemoteResources = await provider?.deleteConnectionRemoteResources?.(connection);
88
+ } catch (error) {
89
+ request.log.error({
90
+ connectionId: connection.id,
91
+ provider: connection.provider,
92
+ err: error
93
+ }, 'Integration connection remote cleanup preparation failed');
94
+ }
95
+ await db().transaction(async (tx)=>{
96
+ await provider?.deleteConnectionRecords?.(connection, {
97
+ tx
98
+ });
99
+ await enqueueIntegrationSecretCleanup({
100
+ connection
101
+ }, {
102
+ tx
103
+ });
104
+ await deleteIntegrationConnection({
105
+ id: connection.id
106
+ }, {
107
+ tx
108
+ });
85
109
  });
86
- await deleteIntegrationConnection({
87
- id: connection.id
88
- }, {
89
- tx
90
- });
91
- });
92
- try {
93
- await provider?.deleteConnectionSecrets?.(connection);
94
- } catch (error) {
95
- request.log.error({
96
- connectionId: connection.id,
97
- provider: connection.provider,
98
- err: error
99
- }, 'Integration connection secret cleanup failed after connection deletion');
110
+ try {
111
+ await deleteRemoteResources?.();
112
+ } catch (error) {
113
+ request.log.error({
114
+ connectionId: connection.id,
115
+ provider: connection.provider,
116
+ err: error
117
+ }, 'Integration connection remote cleanup failed after deletion');
118
+ }
119
+ try {
120
+ await processIntegrationSecretCleanups({
121
+ registry,
122
+ connectionId: connection.id,
123
+ connection,
124
+ limit: 1
125
+ });
126
+ } catch (error) {
127
+ // The durable cleanup row survives, so a later sweep retries this connection.
128
+ request.log.error({
129
+ connectionId: connection.id,
130
+ provider: connection.provider,
131
+ err: error
132
+ }, 'Integration connection secret cleanup failed after connection deletion');
133
+ }
134
+ };
135
+ if (provider?.withConnectionDeletionLock) {
136
+ await provider.withConnectionDeletionLock(connection, deleteConnection);
137
+ } else {
138
+ await deleteConnection();
100
139
  }
101
140
  reply.status(204);
102
141
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/presentation/routes/manage-connections.ts"],"sourcesContent":["import {AUTH_USER, requireWorkspaceAccess} from '@shipfox/api-auth-context';\nimport {\n integrationConnectionDtoSchema,\n updateIntegrationConnectionBodySchema,\n} from '@shipfox/api-integration-spi';\nimport {ClientError, defineRoute} from '@shipfox/node-fastify';\nimport {z} from 'zod';\nimport type {IntegrationProviderRegistry} from '#core/providers/registry.js';\nimport {\n deleteIntegrationConnection,\n getIntegrationConnectionById,\n updateIntegrationConnectionLifecycleStatus,\n} from '#db/connections.js';\nimport {db} from '#db/db.js';\nimport {toIntegrationConnectionDto} from '#presentation/dto/integrations.js';\n\nconst connectionParamsSchema = z.object({\n connectionId: z.string().uuid(),\n});\n\nexport function createUpdateIntegrationConnectionRoute(registry: IntegrationProviderRegistry) {\n return defineRoute({\n method: 'PATCH',\n path: '/integration-connections/:connectionId',\n auth: AUTH_USER,\n description: 'Update an integration connection.',\n schema: {\n params: connectionParamsSchema,\n body: updateIntegrationConnectionBodySchema,\n response: {\n 200: integrationConnectionDtoSchema,\n },\n },\n handler: async (request) => {\n const connection = await getIntegrationConnectionById(request.params.connectionId);\n if (!connection) {\n throw new ClientError('Integration connection not found', 'not-found', {status: 404});\n }\n\n requireWorkspaceAccess({request, workspaceId: connection.workspaceId});\n const updated = await updateIntegrationConnectionLifecycleStatus({\n id: connection.id,\n lifecycleStatus: request.body.lifecycle_status,\n });\n if (!updated) {\n throw new ClientError('Integration connection not found', 'not-found', {status: 404});\n }\n\n const provider = registry.list().find((candidate) => candidate.provider === updated.provider);\n return toIntegrationConnectionDto(updated, {capabilities: provider?.capabilities ?? []});\n },\n });\n}\n\nexport function createDeleteIntegrationConnectionRoute(registry: IntegrationProviderRegistry) {\n return defineRoute({\n method: 'DELETE',\n path: '/integration-connections/:connectionId',\n auth: AUTH_USER,\n description: 'Delete an integration connection.',\n schema: {\n params: connectionParamsSchema,\n response: {\n 204: z.void(),\n },\n },\n handler: async (request, reply) => {\n const connection = await getIntegrationConnectionById(request.params.connectionId);\n if (!connection) {\n throw new ClientError('Integration connection not found', 'not-found', {status: 404});\n }\n\n requireWorkspaceAccess({request, workspaceId: connection.workspaceId});\n const provider = registry\n .list()\n .find((candidate) => candidate.provider === connection.provider);\n const hasCleanupHooks =\n provider?.deleteConnectionRecords !== undefined ||\n provider?.deleteConnectionSecrets !== undefined;\n if (!hasCleanupHooks) {\n request.log.warn(\n {connectionId: connection.id, provider: connection.provider},\n 'Deleting integration connection without provider cleanup',\n );\n }\n await db().transaction(async (tx) => {\n await provider?.deleteConnectionRecords?.(connection, {tx});\n await deleteIntegrationConnection({id: connection.id}, {tx});\n });\n try {\n await provider?.deleteConnectionSecrets?.(connection);\n } catch (error) {\n request.log.error(\n {connectionId: connection.id, provider: connection.provider, err: error},\n 'Integration connection secret cleanup failed after connection deletion',\n );\n }\n reply.status(204);\n },\n });\n}\n"],"names":["AUTH_USER","requireWorkspaceAccess","integrationConnectionDtoSchema","updateIntegrationConnectionBodySchema","ClientError","defineRoute","z","deleteIntegrationConnection","getIntegrationConnectionById","updateIntegrationConnectionLifecycleStatus","db","toIntegrationConnectionDto","connectionParamsSchema","object","connectionId","string","uuid","createUpdateIntegrationConnectionRoute","registry","method","path","auth","description","schema","params","body","response","handler","request","connection","status","workspaceId","updated","id","lifecycleStatus","lifecycle_status","provider","list","find","candidate","capabilities","createDeleteIntegrationConnectionRoute","void","reply","hasCleanupHooks","deleteConnectionRecords","undefined","deleteConnectionSecrets","log","warn","transaction","tx","error","err"],"mappings":"AAAA,SAAQA,SAAS,EAAEC,sBAAsB,QAAO,4BAA4B;AAC5E,SACEC,8BAA8B,EAC9BC,qCAAqC,QAChC,+BAA+B;AACtC,SAAQC,WAAW,EAAEC,WAAW,QAAO,wBAAwB;AAC/D,SAAQC,CAAC,QAAO,MAAM;AAEtB,SACEC,2BAA2B,EAC3BC,4BAA4B,EAC5BC,0CAA0C,QACrC,qBAAqB;AAC5B,SAAQC,EAAE,QAAO,YAAY;AAC7B,SAAQC,0BAA0B,QAAO,oCAAoC;AAE7E,MAAMC,yBAAyBN,EAAEO,MAAM,CAAC;IACtCC,cAAcR,EAAES,MAAM,GAAGC,IAAI;AAC/B;AAEA,OAAO,SAASC,uCAAuCC,QAAqC;IAC1F,OAAOb,YAAY;QACjBc,QAAQ;QACRC,MAAM;QACNC,MAAMrB;QACNsB,aAAa;QACbC,QAAQ;YACNC,QAAQZ;YACRa,MAAMtB;YACNuB,UAAU;gBACR,KAAKxB;YACP;QACF;QACAyB,SAAS,OAAOC;YACd,MAAMC,aAAa,MAAMrB,6BAA6BoB,QAAQJ,MAAM,CAACV,YAAY;YACjF,IAAI,CAACe,YAAY;gBACf,MAAM,IAAIzB,YAAY,oCAAoC,aAAa;oBAAC0B,QAAQ;gBAAG;YACrF;YAEA7B,uBAAuB;gBAAC2B;gBAASG,aAAaF,WAAWE,WAAW;YAAA;YACpE,MAAMC,UAAU,MAAMvB,2CAA2C;gBAC/DwB,IAAIJ,WAAWI,EAAE;gBACjBC,iBAAiBN,QAAQH,IAAI,CAACU,gBAAgB;YAChD;YACA,IAAI,CAACH,SAAS;gBACZ,MAAM,IAAI5B,YAAY,oCAAoC,aAAa;oBAAC0B,QAAQ;gBAAG;YACrF;YAEA,MAAMM,WAAWlB,SAASmB,IAAI,GAAGC,IAAI,CAAC,CAACC,YAAcA,UAAUH,QAAQ,KAAKJ,QAAQI,QAAQ;YAC5F,OAAOzB,2BAA2BqB,SAAS;gBAACQ,cAAcJ,UAAUI,gBAAgB,EAAE;YAAA;QACxF;IACF;AACF;AAEA,OAAO,SAASC,uCAAuCvB,QAAqC;IAC1F,OAAOb,YAAY;QACjBc,QAAQ;QACRC,MAAM;QACNC,MAAMrB;QACNsB,aAAa;QACbC,QAAQ;YACNC,QAAQZ;YACRc,UAAU;gBACR,KAAKpB,EAAEoC,IAAI;YACb;QACF;QACAf,SAAS,OAAOC,SAASe;YACvB,MAAMd,aAAa,MAAMrB,6BAA6BoB,QAAQJ,MAAM,CAACV,YAAY;YACjF,IAAI,CAACe,YAAY;gBACf,MAAM,IAAIzB,YAAY,oCAAoC,aAAa;oBAAC0B,QAAQ;gBAAG;YACrF;YAEA7B,uBAAuB;gBAAC2B;gBAASG,aAAaF,WAAWE,WAAW;YAAA;YACpE,MAAMK,WAAWlB,SACdmB,IAAI,GACJC,IAAI,CAAC,CAACC,YAAcA,UAAUH,QAAQ,KAAKP,WAAWO,QAAQ;YACjE,MAAMQ,kBACJR,UAAUS,4BAA4BC,aACtCV,UAAUW,4BAA4BD;YACxC,IAAI,CAACF,iBAAiB;gBACpBhB,QAAQoB,GAAG,CAACC,IAAI,CACd;oBAACnC,cAAce,WAAWI,EAAE;oBAAEG,UAAUP,WAAWO,QAAQ;gBAAA,GAC3D;YAEJ;YACA,MAAM1B,KAAKwC,WAAW,CAAC,OAAOC;gBAC5B,MAAMf,UAAUS,0BAA0BhB,YAAY;oBAACsB;gBAAE;gBACzD,MAAM5C,4BAA4B;oBAAC0B,IAAIJ,WAAWI,EAAE;gBAAA,GAAG;oBAACkB;gBAAE;YAC5D;YACA,IAAI;gBACF,MAAMf,UAAUW,0BAA0BlB;YAC5C,EAAE,OAAOuB,OAAO;gBACdxB,QAAQoB,GAAG,CAACI,KAAK,CACf;oBAACtC,cAAce,WAAWI,EAAE;oBAAEG,UAAUP,WAAWO,QAAQ;oBAAEiB,KAAKD;gBAAK,GACvE;YAEJ;YACAT,MAAMb,MAAM,CAAC;QACf;IACF;AACF"}
1
+ {"version":3,"sources":["../../../src/presentation/routes/manage-connections.ts"],"sourcesContent":["import {AUTH_USER, requireWorkspaceAccess} from '@shipfox/api-auth-context';\nimport {\n integrationConnectionDtoSchema,\n updateIntegrationConnectionBodySchema,\n} from '@shipfox/api-integration-spi';\nimport {ClientError, defineRoute} from '@shipfox/node-fastify';\nimport {z} from 'zod';\nimport type {IntegrationProviderRegistry} from '#core/providers/registry.js';\nimport {processIntegrationSecretCleanups} from '#core/secret-cleanup.js';\nimport {\n deleteIntegrationConnection,\n getIntegrationConnectionById,\n updateIntegrationConnectionLifecycleStatus,\n} from '#db/connections.js';\nimport {db} from '#db/db.js';\nimport {enqueueIntegrationSecretCleanup} from '#db/secret-cleanups.js';\nimport {toIntegrationConnectionDto} from '#presentation/dto/integrations.js';\n\nconst connectionParamsSchema = z.object({\n connectionId: z.string().uuid(),\n});\n\nexport function createUpdateIntegrationConnectionRoute(registry: IntegrationProviderRegistry) {\n return defineRoute({\n method: 'PATCH',\n path: '/integration-connections/:connectionId',\n auth: AUTH_USER,\n description: 'Update an integration connection.',\n schema: {\n params: connectionParamsSchema,\n body: updateIntegrationConnectionBodySchema,\n response: {\n 200: integrationConnectionDtoSchema,\n },\n },\n handler: async (request) => {\n const connection = await getIntegrationConnectionById(request.params.connectionId);\n if (!connection) {\n throw new ClientError('Integration connection not found', 'not-found', {status: 404});\n }\n\n requireWorkspaceAccess({request, workspaceId: connection.workspaceId});\n const updated = await updateIntegrationConnectionLifecycleStatus({\n id: connection.id,\n lifecycleStatus: request.body.lifecycle_status,\n });\n if (!updated) {\n throw new ClientError('Integration connection not found', 'not-found', {status: 404});\n }\n\n const provider = registry.list().find((candidate) => candidate.provider === updated.provider);\n return toIntegrationConnectionDto(updated, {capabilities: provider?.capabilities ?? []});\n },\n });\n}\n\nexport function createDeleteIntegrationConnectionRoute(registry: IntegrationProviderRegistry) {\n return defineRoute({\n method: 'DELETE',\n path: '/integration-connections/:connectionId',\n auth: AUTH_USER,\n description: 'Delete an integration connection.',\n schema: {\n params: connectionParamsSchema,\n response: {\n 204: z.void(),\n },\n },\n handler: async (request, reply) => {\n const connection = await getIntegrationConnectionById(request.params.connectionId);\n if (!connection) {\n throw new ClientError('Integration connection not found', 'not-found', {status: 404});\n }\n\n requireWorkspaceAccess({request, workspaceId: connection.workspaceId});\n const provider = registry\n .list()\n .find((candidate) => candidate.provider === connection.provider);\n const hasCleanupHooks =\n provider?.deleteConnectionRemoteResources !== undefined ||\n provider?.deleteConnectionRecords !== undefined ||\n provider?.deleteConnectionSecrets !== undefined;\n if (!hasCleanupHooks) {\n request.log.warn(\n {connectionId: connection.id, provider: connection.provider},\n 'Deleting integration connection without provider cleanup',\n );\n }\n const deleteConnection = async (): Promise<void> => {\n let deleteRemoteResources: (() => Promise<void>) | undefined;\n try {\n deleteRemoteResources = await provider?.deleteConnectionRemoteResources?.(connection);\n } catch (error) {\n request.log.error(\n {connectionId: connection.id, provider: connection.provider, err: error},\n 'Integration connection remote cleanup preparation failed',\n );\n }\n await db().transaction(async (tx) => {\n await provider?.deleteConnectionRecords?.(connection, {tx});\n await enqueueIntegrationSecretCleanup({connection}, {tx});\n await deleteIntegrationConnection({id: connection.id}, {tx});\n });\n try {\n await deleteRemoteResources?.();\n } catch (error) {\n request.log.error(\n {connectionId: connection.id, provider: connection.provider, err: error},\n 'Integration connection remote cleanup failed after deletion',\n );\n }\n try {\n await processIntegrationSecretCleanups({\n registry,\n connectionId: connection.id,\n connection,\n limit: 1,\n });\n } catch (error) {\n // The durable cleanup row survives, so a later sweep retries this connection.\n request.log.error(\n {connectionId: connection.id, provider: connection.provider, err: error},\n 'Integration connection secret cleanup failed after connection deletion',\n );\n }\n };\n if (provider?.withConnectionDeletionLock) {\n await provider.withConnectionDeletionLock(connection, deleteConnection);\n } else {\n await deleteConnection();\n }\n reply.status(204);\n },\n });\n}\n"],"names":["AUTH_USER","requireWorkspaceAccess","integrationConnectionDtoSchema","updateIntegrationConnectionBodySchema","ClientError","defineRoute","z","processIntegrationSecretCleanups","deleteIntegrationConnection","getIntegrationConnectionById","updateIntegrationConnectionLifecycleStatus","db","enqueueIntegrationSecretCleanup","toIntegrationConnectionDto","connectionParamsSchema","object","connectionId","string","uuid","createUpdateIntegrationConnectionRoute","registry","method","path","auth","description","schema","params","body","response","handler","request","connection","status","workspaceId","updated","id","lifecycleStatus","lifecycle_status","provider","list","find","candidate","capabilities","createDeleteIntegrationConnectionRoute","void","reply","hasCleanupHooks","deleteConnectionRemoteResources","undefined","deleteConnectionRecords","deleteConnectionSecrets","log","warn","deleteConnection","deleteRemoteResources","error","err","transaction","tx","limit","withConnectionDeletionLock"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"jira.d.ts","sourceRoot":"","sources":["../../src/providers/jira.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAyB,yBAAyB,EAAC,MAAM,qBAAqB,CAAC;AAiM3F,eAAO,MAAM,kBAAkB,EAAE,yBAIhC,CAAC"}
1
+ {"version":3,"file":"jira.d.ts","sourceRoot":"","sources":["../../src/providers/jira.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAyB,yBAAyB,EAAC,MAAM,qBAAqB,CAAC;AAiP3F,eAAO,MAAM,kBAAkB,EAAE,yBAIhC,CAAC"}
@@ -5,7 +5,8 @@ import { publishIntegrationEventReceived, recordDeliveryOnly } from '#db/webhook
5
5
  import { retryConnectionSlugCollision, slugifyConnectionSlug } from '#providers/connection-slug.js';
6
6
  const JIRA_SECRETS_NAMESPACE_PREFIX = 'system/integrations/jira/';
7
7
  async function loadJiraModuleParts(options = {}) {
8
- const { createJiraIntegrationProvider, createJiraMaintenanceWorker, createJiraPendingSelectionStore, createJiraTokenStore, db: jiraDb, deleteJiraInstallationByConnectionId, disconnectJiraInstallation: disconnectJiraInstallationRecords, getJiraInstallationByCloudId, jiraSecretsNamespace, migrationsPath, upsertJiraInstallation, withJiraRefreshLockAndWait } = await import('@shipfox/api-integration-jira');
8
+ const { createJiraApiClient, createJiraIntegrationProvider, createJiraMaintenanceWorker, createJiraPendingSelectionStore, createJiraTokenStore, db: jiraDb, deregisterJiraWebhooks, deleteJiraInstallationByConnectionId, disconnectJiraInstallation: disconnectJiraInstallationRecords, getJiraInstallationByCloudId, getJiraInstallationByConnectionId, jiraSecretsNamespace, jiraWebhookUrl, migrationsPath, prepareJiraWebhookDeregistration, upsertJiraInstallation, withJiraRefreshLockAndWait, withJiraWebhookRegistrationLock } = await import('@shipfox/api-integration-jira');
9
+ const jira = createJiraApiClient();
9
10
  async function getExistingJiraConnection(input) {
10
11
  const installation = await getJiraInstallationByCloudId(input.cloudId);
11
12
  if (!installation) return undefined;
@@ -49,18 +50,34 @@ async function loadJiraModuleParts(options = {}) {
49
50
  }));
50
51
  }
51
52
  async function disconnectJiraInstallation(input) {
52
- await disconnectJiraInstallationRecords({
53
- connectionId: input.connectionId,
54
- getConnection: getIntegrationConnectionById,
55
- deleteSecrets: (params)=>options.secrets?.jira?.deleteSecrets({
56
- ...params,
57
- namespace: jiraNamespaceSuffix(params.namespace)
58
- }) ?? Promise.resolve(0),
59
- transaction: (fn)=>db().transaction((tx)=>fn(tx)),
60
- deleteConnection: (params, transactionOptions)=>deleteIntegrationConnection({
61
- id: params.connectionId
62
- }, transactionOptions)
63
- });
53
+ const disconnect = ()=>disconnectJiraInstallationRecords({
54
+ connectionId: input.connectionId,
55
+ getConnection: getIntegrationConnectionById,
56
+ deleteSecrets: (params)=>options.secrets?.jira?.deleteSecrets({
57
+ ...params,
58
+ namespace: jiraNamespaceSuffix(params.namespace)
59
+ }) ?? Promise.resolve(0),
60
+ deregisterWebhooks: ()=>deregisterJiraWebhooks({
61
+ connectionId: input.connectionId,
62
+ getInstallation: getJiraInstallationByConnectionId,
63
+ tokenStore,
64
+ jira
65
+ }),
66
+ transaction: (fn)=>db().transaction((tx)=>fn(tx)),
67
+ deleteConnection: (params, transactionOptions)=>deleteIntegrationConnection({
68
+ id: params.connectionId
69
+ }, transactionOptions)
70
+ });
71
+ if (input.lockAlreadyHeld) {
72
+ await disconnect();
73
+ return;
74
+ }
75
+ const installation = await getJiraInstallationByConnectionId(input.connectionId);
76
+ if (!installation) {
77
+ await disconnect();
78
+ return;
79
+ }
80
+ await withJiraWebhookRegistrationLock(installation.cloudId, disconnect);
64
81
  }
65
82
  const fallbackSecrets = {
66
83
  getSecret: ()=>Promise.resolve(null),
@@ -82,6 +99,7 @@ async function loadJiraModuleParts(options = {}) {
82
99
  }) ?? Promise.resolve(0)
83
100
  } : fallbackSecrets;
84
101
  const tokenStore = createJiraTokenStore({
102
+ client: jira,
85
103
  resolveConnection: getIntegrationConnectionById,
86
104
  secrets,
87
105
  markConnectionError: async ({ connectionId })=>{
@@ -95,10 +113,27 @@ async function loadJiraModuleParts(options = {}) {
95
113
  secrets
96
114
  });
97
115
  const integrationProvider = createJiraIntegrationProvider({
116
+ jira,
98
117
  agentTools: {
99
118
  tokenStore
100
119
  },
101
120
  cleanup: {
121
+ deleteConnectionRemoteResources: async (connection)=>{
122
+ return await prepareJiraWebhookDeregistration({
123
+ connectionId: connection.id,
124
+ getInstallation: getJiraInstallationByConnectionId,
125
+ tokenStore,
126
+ jira
127
+ });
128
+ },
129
+ withConnectionDeletionLock: async (connection, fn)=>{
130
+ const installation = await getJiraInstallationByConnectionId(connection.id);
131
+ if (!installation) {
132
+ await fn();
133
+ return;
134
+ }
135
+ await withJiraWebhookRegistrationLock(installation.cloudId, fn);
136
+ },
102
137
  deleteConnectionRecords: async (connection, { tx })=>{
103
138
  await deleteJiraInstallationByConnectionId(connection.id, {
104
139
  tx
@@ -149,8 +184,10 @@ async function loadJiraModuleParts(options = {}) {
149
184
  provider: integrationProvider,
150
185
  workers: [
151
186
  createJiraMaintenanceWorker({
187
+ jira,
152
188
  tokenStore,
153
- resolveConnection: getIntegrationConnectionById
189
+ resolveConnection: getIntegrationConnectionById,
190
+ webhookUrlForConnection: jiraWebhookUrl
154
191
  })
155
192
  ],
156
193
  webhookProcessors: integrationProvider.webhookProcessors,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/providers/jira.ts"],"sourcesContent":["import type {\n ConnectJiraInstallationInput,\n JiraPendingSelectionSecretsStore,\n JiraSecretsStore,\n} from '@shipfox/api-integration-jira';\nimport type {IntegrationConnection as CoreIntegrationConnection} from '@shipfox/api-integration-spi';\nimport {config} from '#config.js';\nimport {\n deleteIntegrationConnection,\n getIntegrationConnectionById,\n resolveUniqueConnectionSlug,\n updateIntegrationConnectionLifecycleStatus,\n upsertIntegrationConnection,\n} from '#db/connections.js';\nimport {db} from '#db/db.js';\nimport {publishIntegrationEventReceived, recordDeliveryOnly} from '#db/webhook-deliveries.js';\nimport {retryConnectionSlugCollision, slugifyConnectionSlug} from '#providers/connection-slug.js';\nimport type {IntegrationModuleParts, IntegrationProviderModule} from '#providers/types.js';\n\nconst JIRA_SECRETS_NAMESPACE_PREFIX = 'system/integrations/jira/';\ntype IntegrationDb = ReturnType<typeof db>;\ntype IntegrationTx = Parameters<Parameters<IntegrationDb['transaction']>[0]>[0];\n\nasync function loadJiraModuleParts(\n options: Parameters<IntegrationProviderModule['load']>[0] = {},\n): Promise<IntegrationModuleParts> {\n const {\n createJiraIntegrationProvider,\n createJiraMaintenanceWorker,\n createJiraPendingSelectionStore,\n createJiraTokenStore,\n db: jiraDb,\n deleteJiraInstallationByConnectionId,\n disconnectJiraInstallation: disconnectJiraInstallationRecords,\n getJiraInstallationByCloudId,\n jiraSecretsNamespace,\n migrationsPath,\n upsertJiraInstallation,\n withJiraRefreshLockAndWait,\n } = await import('@shipfox/api-integration-jira');\n\n async function getExistingJiraConnection(input: {\n cloudId: string;\n }): Promise<CoreIntegrationConnection<'jira'> | undefined> {\n const installation = await getJiraInstallationByCloudId(input.cloudId);\n if (!installation) return undefined;\n return (await getIntegrationConnectionById(installation.connectionId)) as\n | CoreIntegrationConnection<'jira'>\n | undefined;\n }\n\n function connectJiraInstallation(\n input: ConnectJiraInstallationInput,\n ): Promise<CoreIntegrationConnection<'jira'>> {\n return retryConnectionSlugCollision(() =>\n db().transaction(async (tx) => {\n const slug = await resolveUniqueConnectionSlug(\n {\n workspaceId: input.workspaceId,\n provider: 'jira',\n externalAccountId: input.cloudId,\n baseSlug: slugifyConnectionSlug(`jira_${input.siteName || input.cloudId}`, {\n fallback: 'jira',\n }),\n },\n {tx},\n );\n const connection = await upsertIntegrationConnection(\n {\n workspaceId: input.workspaceId,\n provider: 'jira',\n externalAccountId: input.cloudId,\n slug,\n displayName: input.displayName,\n lifecycleStatus: 'active',\n },\n {tx},\n );\n await upsertJiraInstallation(\n {\n connectionId: connection.id,\n cloudId: input.cloudId,\n siteUrl: input.siteUrl,\n siteName: input.siteName,\n authorizingAccountId: input.authorizingAccountId,\n scopes: input.scopes,\n status: 'installed',\n tokenExpiresAt: input.tokenExpiresAt,\n },\n {tx},\n );\n return connection as CoreIntegrationConnection<'jira'>;\n }),\n );\n }\n\n async function disconnectJiraInstallation(input: {connectionId: string}): Promise<void> {\n await disconnectJiraInstallationRecords<IntegrationTx>({\n connectionId: input.connectionId,\n getConnection: getIntegrationConnectionById,\n deleteSecrets: (params) =>\n options.secrets?.jira?.deleteSecrets({\n ...params,\n namespace: jiraNamespaceSuffix(params.namespace),\n }) ?? Promise.resolve(0),\n transaction: (fn) => db().transaction((tx) => fn(tx)),\n deleteConnection: (params, transactionOptions) =>\n deleteIntegrationConnection({id: params.connectionId}, transactionOptions),\n });\n }\n\n const fallbackSecrets: JiraSecretsStore & JiraPendingSelectionSecretsStore = {\n getSecret: () => Promise.resolve(null),\n setSecrets: () => Promise.reject(new Error('Jira token storage is not configured')),\n deleteSecrets: () => Promise.resolve(0),\n };\n const secrets: JiraSecretsStore & JiraPendingSelectionSecretsStore = options.secrets?.jira\n ? {\n getSecret: (params) =>\n options.secrets?.jira?.getSecret({\n ...params,\n namespace: jiraNamespaceSuffix(params.namespace),\n }) ?? Promise.resolve(null),\n setSecrets: (params) =>\n options.secrets?.jira?.setSecrets({\n ...params,\n namespace: jiraNamespaceSuffix(params.namespace),\n }) ?? Promise.resolve(),\n deleteSecrets: (params) =>\n options.secrets?.jira?.deleteSecrets({\n ...params,\n namespace: jiraNamespaceSuffix(params.namespace),\n }) ?? Promise.resolve(0),\n }\n : fallbackSecrets;\n const tokenStore = createJiraTokenStore({\n resolveConnection: getIntegrationConnectionById,\n secrets,\n markConnectionError: async ({connectionId}) => {\n await updateIntegrationConnectionLifecycleStatus({\n id: connectionId,\n lifecycleStatus: 'error',\n });\n },\n });\n const pendingStore = createJiraPendingSelectionStore({secrets});\n\n const integrationProvider = createJiraIntegrationProvider({\n agentTools: {tokenStore},\n cleanup: {\n deleteConnectionRecords: async (connection, {tx}) => {\n await deleteJiraInstallationByConnectionId(connection.id, {tx});\n },\n deleteConnectionSecrets: async (connection) => {\n await withJiraRefreshLockAndWait(connection.id, async () => {\n // Scoped secrets accept the provider-local suffix, after this helper validates its prefix.\n await (options.secrets?.jira?.deleteSecrets({\n workspaceId: connection.workspaceId,\n namespace: jiraNamespaceSuffix(jiraSecretsNamespace(connection.id)),\n }) ?? Promise.resolve());\n });\n },\n },\n routes: {\n tokenStore,\n pendingStore,\n getExistingJiraConnection,\n connectJiraInstallation,\n disconnectJiraInstallation,\n coreDb: db,\n publishIntegrationEventReceived,\n recordDeliveryOnly,\n getIntegrationConnectionById,\n markConnectionActive: async ({connectionId, tx}) => {\n await updateIntegrationConnectionLifecycleStatus(\n {\n id: connectionId,\n lifecycleStatus: 'active',\n },\n tx === undefined ? {} : {tx: tx as IntegrationTx},\n );\n },\n markConnectionError: async ({connectionId, tx}) => {\n await updateIntegrationConnectionLifecycleStatus(\n {\n id: connectionId,\n lifecycleStatus: 'error',\n },\n tx === undefined ? {} : {tx: tx as IntegrationTx},\n );\n },\n ...(options.requireActiveWorkspaceMembership\n ? {requireActiveWorkspaceMembership: options.requireActiveWorkspaceMembership}\n : {}),\n },\n });\n\n return {\n provider: integrationProvider,\n workers: [\n createJiraMaintenanceWorker({\n tokenStore,\n resolveConnection: getIntegrationConnectionById,\n }),\n ],\n webhookProcessors: integrationProvider.webhookProcessors,\n database: {db: jiraDb, migrationsPath, databaseNamespace: 'integrations_jira'},\n };\n}\n\nexport const jiraProviderModule: IntegrationProviderModule = {\n id: 'jira',\n enabled: config.INTEGRATIONS_ENABLE_JIRA_PROVIDER,\n load: loadJiraModuleParts,\n};\n\nfunction jiraNamespaceSuffix(namespace: string): string {\n if (!namespace.startsWith(JIRA_SECRETS_NAMESPACE_PREFIX)) {\n throw new Error('Jira provider attempted to access an unscoped secret namespace');\n }\n return namespace.slice(JIRA_SECRETS_NAMESPACE_PREFIX.length);\n}\n"],"names":["config","deleteIntegrationConnection","getIntegrationConnectionById","resolveUniqueConnectionSlug","updateIntegrationConnectionLifecycleStatus","upsertIntegrationConnection","db","publishIntegrationEventReceived","recordDeliveryOnly","retryConnectionSlugCollision","slugifyConnectionSlug","JIRA_SECRETS_NAMESPACE_PREFIX","loadJiraModuleParts","options","createJiraIntegrationProvider","createJiraMaintenanceWorker","createJiraPendingSelectionStore","createJiraTokenStore","jiraDb","deleteJiraInstallationByConnectionId","disconnectJiraInstallation","disconnectJiraInstallationRecords","getJiraInstallationByCloudId","jiraSecretsNamespace","migrationsPath","upsertJiraInstallation","withJiraRefreshLockAndWait","getExistingJiraConnection","input","installation","cloudId","undefined","connectionId","connectJiraInstallation","transaction","tx","slug","workspaceId","provider","externalAccountId","baseSlug","siteName","fallback","connection","displayName","lifecycleStatus","id","siteUrl","authorizingAccountId","scopes","status","tokenExpiresAt","getConnection","deleteSecrets","params","secrets","jira","namespace","jiraNamespaceSuffix","Promise","resolve","fn","deleteConnection","transactionOptions","fallbackSecrets","getSecret","setSecrets","reject","Error","tokenStore","resolveConnection","markConnectionError","pendingStore","integrationProvider","agentTools","cleanup","deleteConnectionRecords","deleteConnectionSecrets","routes","coreDb","markConnectionActive","requireActiveWorkspaceMembership","workers","webhookProcessors","database","databaseNamespace","jiraProviderModule","enabled","INTEGRATIONS_ENABLE_JIRA_PROVIDER","load","startsWith","slice","length"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/providers/jira.ts"],"sourcesContent":["import type {\n ConnectJiraInstallationInput,\n JiraPendingSelectionSecretsStore,\n JiraSecretsStore,\n} from '@shipfox/api-integration-jira';\nimport type {IntegrationConnection as CoreIntegrationConnection} from '@shipfox/api-integration-spi';\nimport {config} from '#config.js';\nimport {\n deleteIntegrationConnection,\n getIntegrationConnectionById,\n resolveUniqueConnectionSlug,\n updateIntegrationConnectionLifecycleStatus,\n upsertIntegrationConnection,\n} from '#db/connections.js';\nimport {db} from '#db/db.js';\nimport {publishIntegrationEventReceived, recordDeliveryOnly} from '#db/webhook-deliveries.js';\nimport {retryConnectionSlugCollision, slugifyConnectionSlug} from '#providers/connection-slug.js';\nimport type {IntegrationModuleParts, IntegrationProviderModule} from '#providers/types.js';\n\nconst JIRA_SECRETS_NAMESPACE_PREFIX = 'system/integrations/jira/';\ntype IntegrationDb = ReturnType<typeof db>;\ntype IntegrationTx = Parameters<Parameters<IntegrationDb['transaction']>[0]>[0];\n\nasync function loadJiraModuleParts(\n options: Parameters<IntegrationProviderModule['load']>[0] = {},\n): Promise<IntegrationModuleParts> {\n const {\n createJiraApiClient,\n createJiraIntegrationProvider,\n createJiraMaintenanceWorker,\n createJiraPendingSelectionStore,\n createJiraTokenStore,\n db: jiraDb,\n deregisterJiraWebhooks,\n deleteJiraInstallationByConnectionId,\n disconnectJiraInstallation: disconnectJiraInstallationRecords,\n getJiraInstallationByCloudId,\n getJiraInstallationByConnectionId,\n jiraSecretsNamespace,\n jiraWebhookUrl,\n migrationsPath,\n prepareJiraWebhookDeregistration,\n upsertJiraInstallation,\n withJiraRefreshLockAndWait,\n withJiraWebhookRegistrationLock,\n } = await import('@shipfox/api-integration-jira');\n const jira = createJiraApiClient();\n\n async function getExistingJiraConnection(input: {\n cloudId: string;\n }): Promise<CoreIntegrationConnection<'jira'> | undefined> {\n const installation = await getJiraInstallationByCloudId(input.cloudId);\n if (!installation) return undefined;\n return (await getIntegrationConnectionById(installation.connectionId)) as\n | CoreIntegrationConnection<'jira'>\n | undefined;\n }\n\n function connectJiraInstallation(\n input: ConnectJiraInstallationInput,\n ): Promise<CoreIntegrationConnection<'jira'>> {\n return retryConnectionSlugCollision(() =>\n db().transaction(async (tx) => {\n const slug = await resolveUniqueConnectionSlug(\n {\n workspaceId: input.workspaceId,\n provider: 'jira',\n externalAccountId: input.cloudId,\n baseSlug: slugifyConnectionSlug(`jira_${input.siteName || input.cloudId}`, {\n fallback: 'jira',\n }),\n },\n {tx},\n );\n const connection = await upsertIntegrationConnection(\n {\n workspaceId: input.workspaceId,\n provider: 'jira',\n externalAccountId: input.cloudId,\n slug,\n displayName: input.displayName,\n lifecycleStatus: 'active',\n },\n {tx},\n );\n await upsertJiraInstallation(\n {\n connectionId: connection.id,\n cloudId: input.cloudId,\n siteUrl: input.siteUrl,\n siteName: input.siteName,\n authorizingAccountId: input.authorizingAccountId,\n scopes: input.scopes,\n status: 'installed',\n tokenExpiresAt: input.tokenExpiresAt,\n },\n {tx},\n );\n return connection as CoreIntegrationConnection<'jira'>;\n }),\n );\n }\n\n async function disconnectJiraInstallation(input: {\n connectionId: string;\n lockAlreadyHeld?: boolean | undefined;\n }): Promise<void> {\n const disconnect = () =>\n disconnectJiraInstallationRecords<IntegrationTx>({\n connectionId: input.connectionId,\n getConnection: getIntegrationConnectionById,\n deleteSecrets: (params) =>\n options.secrets?.jira?.deleteSecrets({\n ...params,\n namespace: jiraNamespaceSuffix(params.namespace),\n }) ?? Promise.resolve(0),\n deregisterWebhooks: () =>\n deregisterJiraWebhooks({\n connectionId: input.connectionId,\n getInstallation: getJiraInstallationByConnectionId,\n tokenStore,\n jira,\n }),\n transaction: (fn) => db().transaction((tx) => fn(tx)),\n deleteConnection: (params, transactionOptions) =>\n deleteIntegrationConnection({id: params.connectionId}, transactionOptions),\n });\n if (input.lockAlreadyHeld) {\n await disconnect();\n return;\n }\n const installation = await getJiraInstallationByConnectionId(input.connectionId);\n if (!installation) {\n await disconnect();\n return;\n }\n await withJiraWebhookRegistrationLock(installation.cloudId, disconnect);\n }\n\n const fallbackSecrets: JiraSecretsStore & JiraPendingSelectionSecretsStore = {\n getSecret: () => Promise.resolve(null),\n setSecrets: () => Promise.reject(new Error('Jira token storage is not configured')),\n deleteSecrets: () => Promise.resolve(0),\n };\n const secrets: JiraSecretsStore & JiraPendingSelectionSecretsStore = options.secrets?.jira\n ? {\n getSecret: (params) =>\n options.secrets?.jira?.getSecret({\n ...params,\n namespace: jiraNamespaceSuffix(params.namespace),\n }) ?? Promise.resolve(null),\n setSecrets: (params) =>\n options.secrets?.jira?.setSecrets({\n ...params,\n namespace: jiraNamespaceSuffix(params.namespace),\n }) ?? Promise.resolve(),\n deleteSecrets: (params) =>\n options.secrets?.jira?.deleteSecrets({\n ...params,\n namespace: jiraNamespaceSuffix(params.namespace),\n }) ?? Promise.resolve(0),\n }\n : fallbackSecrets;\n const tokenStore = createJiraTokenStore({\n client: jira,\n resolveConnection: getIntegrationConnectionById,\n secrets,\n markConnectionError: async ({connectionId}) => {\n await updateIntegrationConnectionLifecycleStatus({\n id: connectionId,\n lifecycleStatus: 'error',\n });\n },\n });\n const pendingStore = createJiraPendingSelectionStore({secrets});\n\n const integrationProvider = createJiraIntegrationProvider({\n jira,\n agentTools: {tokenStore},\n cleanup: {\n deleteConnectionRemoteResources: async (connection) => {\n return await prepareJiraWebhookDeregistration({\n connectionId: connection.id,\n getInstallation: getJiraInstallationByConnectionId,\n tokenStore,\n jira,\n });\n },\n withConnectionDeletionLock: async (connection, fn) => {\n const installation = await getJiraInstallationByConnectionId(connection.id);\n if (!installation) {\n await fn();\n return;\n }\n await withJiraWebhookRegistrationLock(installation.cloudId, fn);\n },\n deleteConnectionRecords: async (connection, {tx}) => {\n await deleteJiraInstallationByConnectionId(connection.id, {tx});\n },\n deleteConnectionSecrets: async (connection) => {\n await withJiraRefreshLockAndWait(connection.id, async () => {\n // Scoped secrets accept the provider-local suffix, after this helper validates its prefix.\n await (options.secrets?.jira?.deleteSecrets({\n workspaceId: connection.workspaceId,\n namespace: jiraNamespaceSuffix(jiraSecretsNamespace(connection.id)),\n }) ?? Promise.resolve());\n });\n },\n },\n routes: {\n tokenStore,\n pendingStore,\n getExistingJiraConnection,\n connectJiraInstallation,\n disconnectJiraInstallation,\n coreDb: db,\n publishIntegrationEventReceived,\n recordDeliveryOnly,\n getIntegrationConnectionById,\n markConnectionActive: async ({connectionId, tx}) => {\n await updateIntegrationConnectionLifecycleStatus(\n {\n id: connectionId,\n lifecycleStatus: 'active',\n },\n tx === undefined ? {} : {tx: tx as IntegrationTx},\n );\n },\n markConnectionError: async ({connectionId, tx}) => {\n await updateIntegrationConnectionLifecycleStatus(\n {\n id: connectionId,\n lifecycleStatus: 'error',\n },\n tx === undefined ? {} : {tx: tx as IntegrationTx},\n );\n },\n ...(options.requireActiveWorkspaceMembership\n ? {requireActiveWorkspaceMembership: options.requireActiveWorkspaceMembership}\n : {}),\n },\n });\n\n return {\n provider: integrationProvider,\n workers: [\n createJiraMaintenanceWorker({\n jira,\n tokenStore,\n resolveConnection: getIntegrationConnectionById,\n webhookUrlForConnection: jiraWebhookUrl,\n }),\n ],\n webhookProcessors: integrationProvider.webhookProcessors,\n database: {db: jiraDb, migrationsPath, databaseNamespace: 'integrations_jira'},\n };\n}\n\nexport const jiraProviderModule: IntegrationProviderModule = {\n id: 'jira',\n enabled: config.INTEGRATIONS_ENABLE_JIRA_PROVIDER,\n load: loadJiraModuleParts,\n};\n\nfunction jiraNamespaceSuffix(namespace: string): string {\n if (!namespace.startsWith(JIRA_SECRETS_NAMESPACE_PREFIX)) {\n throw new Error('Jira provider attempted to access an unscoped secret namespace');\n }\n return namespace.slice(JIRA_SECRETS_NAMESPACE_PREFIX.length);\n}\n"],"names":["config","deleteIntegrationConnection","getIntegrationConnectionById","resolveUniqueConnectionSlug","updateIntegrationConnectionLifecycleStatus","upsertIntegrationConnection","db","publishIntegrationEventReceived","recordDeliveryOnly","retryConnectionSlugCollision","slugifyConnectionSlug","JIRA_SECRETS_NAMESPACE_PREFIX","loadJiraModuleParts","options","createJiraApiClient","createJiraIntegrationProvider","createJiraMaintenanceWorker","createJiraPendingSelectionStore","createJiraTokenStore","jiraDb","deregisterJiraWebhooks","deleteJiraInstallationByConnectionId","disconnectJiraInstallation","disconnectJiraInstallationRecords","getJiraInstallationByCloudId","getJiraInstallationByConnectionId","jiraSecretsNamespace","jiraWebhookUrl","migrationsPath","prepareJiraWebhookDeregistration","upsertJiraInstallation","withJiraRefreshLockAndWait","withJiraWebhookRegistrationLock","jira","getExistingJiraConnection","input","installation","cloudId","undefined","connectionId","connectJiraInstallation","transaction","tx","slug","workspaceId","provider","externalAccountId","baseSlug","siteName","fallback","connection","displayName","lifecycleStatus","id","siteUrl","authorizingAccountId","scopes","status","tokenExpiresAt","disconnect","getConnection","deleteSecrets","params","secrets","namespace","jiraNamespaceSuffix","Promise","resolve","deregisterWebhooks","getInstallation","tokenStore","fn","deleteConnection","transactionOptions","lockAlreadyHeld","fallbackSecrets","getSecret","setSecrets","reject","Error","client","resolveConnection","markConnectionError","pendingStore","integrationProvider","agentTools","cleanup","deleteConnectionRemoteResources","withConnectionDeletionLock","deleteConnectionRecords","deleteConnectionSecrets","routes","coreDb","markConnectionActive","requireActiveWorkspaceMembership","workers","webhookUrlForConnection","webhookProcessors","database","databaseNamespace","jiraProviderModule","enabled","INTEGRATIONS_ENABLE_JIRA_PROVIDER","load","startsWith","slice","length"],"mappings":"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"}
@@ -1,5 +1,9 @@
1
+ import type { IntegrationProviderRegistry } from '#core/providers/registry.js';
1
2
  import { pruneWebhookDeliveriesActivity } from './prune-webhook-deliveries.js';
2
- export declare function createIntegrationsMaintenanceActivities(): {
3
+ export declare function createIntegrationsMaintenanceActivities(options: {
4
+ registry: IntegrationProviderRegistry;
5
+ }): {
3
6
  pruneWebhookDeliveriesActivity: typeof pruneWebhookDeliveriesActivity;
7
+ cleanupIntegrationSecretsActivity: () => Promise<import("#core/secret-cleanup.js").ProcessIntegrationSecretCleanupsResult>;
4
8
  };
5
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/temporal/activities/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,8BAA8B,EAAC,MAAM,+BAA+B,CAAC;AAE7E,wBAAgB,uCAAuC;;EAItD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/temporal/activities/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,2BAA2B,EAAC,MAAM,6BAA6B,CAAC;AAE7E,OAAO,EAAC,8BAA8B,EAAC,MAAM,+BAA+B,CAAC;AAE7E,wBAAgB,uCAAuC,CAAC,OAAO,EAAE;IAC/D,QAAQ,EAAE,2BAA2B,CAAC;CACvC;;;EASA"}
@@ -1,7 +1,13 @@
1
+ import { Context } from '@temporalio/activity';
2
+ import { processIntegrationSecretCleanups } from '#core/secret-cleanup.js';
1
3
  import { pruneWebhookDeliveriesActivity } from './prune-webhook-deliveries.js';
2
- export function createIntegrationsMaintenanceActivities() {
4
+ export function createIntegrationsMaintenanceActivities(options) {
3
5
  return {
4
- pruneWebhookDeliveriesActivity
6
+ pruneWebhookDeliveriesActivity,
7
+ cleanupIntegrationSecretsActivity: ()=>processIntegrationSecretCleanups({
8
+ registry: options.registry,
9
+ heartbeat: ()=>Context.current().heartbeat()
10
+ })
5
11
  };
6
12
  }
7
13
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/temporal/activities/index.ts"],"sourcesContent":["import {pruneWebhookDeliveriesActivity} from './prune-webhook-deliveries.js';\n\nexport function createIntegrationsMaintenanceActivities() {\n return {\n pruneWebhookDeliveriesActivity,\n };\n}\n"],"names":["pruneWebhookDeliveriesActivity","createIntegrationsMaintenanceActivities"],"mappings":"AAAA,SAAQA,8BAA8B,QAAO,gCAAgC;AAE7E,OAAO,SAASC;IACd,OAAO;QACLD;IACF;AACF"}
1
+ {"version":3,"sources":["../../../src/temporal/activities/index.ts"],"sourcesContent":["import {Context} from '@temporalio/activity';\nimport type {IntegrationProviderRegistry} from '#core/providers/registry.js';\nimport {processIntegrationSecretCleanups} from '#core/secret-cleanup.js';\nimport {pruneWebhookDeliveriesActivity} from './prune-webhook-deliveries.js';\n\nexport function createIntegrationsMaintenanceActivities(options: {\n registry: IntegrationProviderRegistry;\n}) {\n return {\n pruneWebhookDeliveriesActivity,\n cleanupIntegrationSecretsActivity: () =>\n processIntegrationSecretCleanups({\n registry: options.registry,\n heartbeat: () => Context.current().heartbeat(),\n }),\n };\n}\n"],"names":["Context","processIntegrationSecretCleanups","pruneWebhookDeliveriesActivity","createIntegrationsMaintenanceActivities","options","cleanupIntegrationSecretsActivity","registry","heartbeat","current"],"mappings":"AAAA,SAAQA,OAAO,QAAO,uBAAuB;AAE7C,SAAQC,gCAAgC,QAAO,0BAA0B;AACzE,SAAQC,8BAA8B,QAAO,gCAAgC;AAE7E,OAAO,SAASC,wCAAwCC,OAEvD;IACC,OAAO;QACLF;QACAG,mCAAmC,IACjCJ,iCAAiC;gBAC/BK,UAAUF,QAAQE,QAAQ;gBAC1BC,WAAW,IAAMP,QAAQQ,OAAO,GAAGD,SAAS;YAC9C;IACJ;AACF"}
@@ -1,3 +1,4 @@
1
1
  export declare const INTEGRATIONS_MAINTENANCE_TASK_QUEUE = "integrations-maintenance";
2
2
  export declare const WEBHOOK_DELIVERY_RETENTION_DAYS = 30;
3
+ export declare const CLEANUP_SECRETS_ACTIVITY_TIMEOUT_MS: number;
3
4
  //# sourceMappingURL=constants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/temporal/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mCAAmC,6BAA6B,CAAC;AAE9E,eAAO,MAAM,+BAA+B,KAAK,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/temporal/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mCAAmC,6BAA6B,CAAC;AAE9E,eAAO,MAAM,+BAA+B,KAAK,CAAC;AAElD,eAAO,MAAM,mCAAmC,QAAiB,CAAC"}
@@ -1,4 +1,5 @@
1
1
  export const INTEGRATIONS_MAINTENANCE_TASK_QUEUE = 'integrations-maintenance';
2
2
  export const WEBHOOK_DELIVERY_RETENTION_DAYS = 30;
3
+ export const CLEANUP_SECRETS_ACTIVITY_TIMEOUT_MS = 5 * 60 * 1_000;
3
4
 
4
5
  //# sourceMappingURL=constants.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/temporal/constants.ts"],"sourcesContent":["export const INTEGRATIONS_MAINTENANCE_TASK_QUEUE = 'integrations-maintenance';\n\nexport const WEBHOOK_DELIVERY_RETENTION_DAYS = 30;\n"],"names":["INTEGRATIONS_MAINTENANCE_TASK_QUEUE","WEBHOOK_DELIVERY_RETENTION_DAYS"],"mappings":"AAAA,OAAO,MAAMA,sCAAsC,2BAA2B;AAE9E,OAAO,MAAMC,kCAAkC,GAAG"}
1
+ {"version":3,"sources":["../../src/temporal/constants.ts"],"sourcesContent":["export const INTEGRATIONS_MAINTENANCE_TASK_QUEUE = 'integrations-maintenance';\n\nexport const WEBHOOK_DELIVERY_RETENTION_DAYS = 30;\n\nexport const CLEANUP_SECRETS_ACTIVITY_TIMEOUT_MS = 5 * 60 * 1_000;\n"],"names":["INTEGRATIONS_MAINTENANCE_TASK_QUEUE","WEBHOOK_DELIVERY_RETENTION_DAYS","CLEANUP_SECRETS_ACTIVITY_TIMEOUT_MS"],"mappings":"AAAA,OAAO,MAAMA,sCAAsC,2BAA2B;AAE9E,OAAO,MAAMC,kCAAkC,GAAG;AAElD,OAAO,MAAMC,sCAAsC,IAAI,KAAK,MAAM"}
@@ -0,0 +1,2 @@
1
+ export declare function cleanupIntegrationSecretsCron(): Promise<void>;
2
+ //# sourceMappingURL=cleanup-integration-secrets-cron.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cleanup-integration-secrets-cron.d.ts","sourceRoot":"","sources":["../../../src/temporal/workflows/cleanup-integration-secrets-cron.ts"],"names":[],"mappings":"AAcA,wBAAsB,6BAA6B,IAAI,OAAO,CAAC,IAAI,CAAC,CAKnE"}
@@ -0,0 +1,21 @@
1
+ import { log, proxyActivities } from '@temporalio/workflow';
2
+ import { CLEANUP_SECRETS_ACTIVITY_TIMEOUT_MS } from '../constants.js';
3
+ const { cleanupIntegrationSecretsActivity } = proxyActivities({
4
+ startToCloseTimeout: CLEANUP_SECRETS_ACTIVITY_TIMEOUT_MS,
5
+ heartbeatTimeout: '1 minute',
6
+ // The activity records a per-row retry schedule. A later cron run retries the
7
+ // row without replaying the whole sweep in Temporal.
8
+ retry: {
9
+ maximumAttempts: 1
10
+ }
11
+ });
12
+ export async function cleanupIntegrationSecretsCron() {
13
+ const result = await cleanupIntegrationSecretsActivity();
14
+ if (result.claimed > 0) {
15
+ log.info('Completed integration connection secret cleanup sweep', {
16
+ ...result
17
+ });
18
+ }
19
+ }
20
+
21
+ //# sourceMappingURL=cleanup-integration-secrets-cron.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/temporal/workflows/cleanup-integration-secrets-cron.ts"],"sourcesContent":["import {log, proxyActivities} from '@temporalio/workflow';\nimport type {createIntegrationsMaintenanceActivities} from '../activities/index.js';\nimport {CLEANUP_SECRETS_ACTIVITY_TIMEOUT_MS} from '../constants.js';\n\nconst {cleanupIntegrationSecretsActivity} = proxyActivities<\n ReturnType<typeof createIntegrationsMaintenanceActivities>\n>({\n startToCloseTimeout: CLEANUP_SECRETS_ACTIVITY_TIMEOUT_MS,\n heartbeatTimeout: '1 minute',\n // The activity records a per-row retry schedule. A later cron run retries the\n // row without replaying the whole sweep in Temporal.\n retry: {maximumAttempts: 1},\n});\n\nexport async function cleanupIntegrationSecretsCron(): Promise<void> {\n const result = await cleanupIntegrationSecretsActivity();\n if (result.claimed > 0) {\n log.info('Completed integration connection secret cleanup sweep', {...result});\n }\n}\n"],"names":["log","proxyActivities","CLEANUP_SECRETS_ACTIVITY_TIMEOUT_MS","cleanupIntegrationSecretsActivity","startToCloseTimeout","heartbeatTimeout","retry","maximumAttempts","cleanupIntegrationSecretsCron","result","claimed","info"],"mappings":"AAAA,SAAQA,GAAG,EAAEC,eAAe,QAAO,uBAAuB;AAE1D,SAAQC,mCAAmC,QAAO,kBAAkB;AAEpE,MAAM,EAACC,iCAAiC,EAAC,GAAGF,gBAE1C;IACAG,qBAAqBF;IACrBG,kBAAkB;IAClB,8EAA8E;IAC9E,qDAAqD;IACrDC,OAAO;QAACC,iBAAiB;IAAC;AAC5B;AAEA,OAAO,eAAeC;IACpB,MAAMC,SAAS,MAAMN;IACrB,IAAIM,OAAOC,OAAO,GAAG,GAAG;QACtBV,IAAIW,IAAI,CAAC,yDAAyD;YAAC,GAAGF,MAAM;QAAA;IAC9E;AACF"}