@mastra/factory 0.24.0-alpha.0 → 0.24.0-alpha.10
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/README.md +106 -4
- package/dist/auth.d.ts +14 -0
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +45 -9
- package/dist/auth.js.map +1 -1
- package/dist/boards/review.d.ts.map +1 -1
- package/dist/boards/review.js +21 -1
- package/dist/boards/review.js.map +1 -1
- package/dist/boards/work-transition-policy.d.ts.map +1 -1
- package/dist/boards/work-transition-policy.js +12 -1
- package/dist/boards/work-transition-policy.js.map +1 -1
- package/dist/boards/work.d.ts +14 -0
- package/dist/boards/work.d.ts.map +1 -1
- package/dist/boards/work.js +70 -7
- package/dist/boards/work.js.map +1 -1
- package/dist/capabilities/version-control.d.ts +26 -0
- package/dist/capabilities/version-control.d.ts.map +1 -1
- package/dist/factory.d.ts +8 -0
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +354 -27
- package/dist/factory.js.map +1 -1
- package/dist/integrations/base.d.ts +1 -1
- package/dist/integrations/base.d.ts.map +1 -1
- package/dist/integrations/github/default-rules.d.ts.map +1 -1
- package/dist/integrations/github/default-rules.js +1 -16
- package/dist/integrations/github/default-rules.js.map +1 -1
- package/dist/integrations/github/integration.d.ts.map +1 -1
- package/dist/integrations/github/integration.js +37 -1
- package/dist/integrations/github/integration.js.map +1 -1
- package/dist/integrations/github/routes.d.ts +1 -1
- package/dist/integrations/github/routes.d.ts.map +1 -1
- package/dist/integrations/github/routes.js +19 -11
- package/dist/integrations/github/routes.js.map +1 -1
- package/dist/integrations/github/rules.d.ts.map +1 -1
- package/dist/integrations/github/rules.js +26 -22
- package/dist/integrations/github/rules.js.map +1 -1
- package/dist/integrations/github/sandbox-release.d.ts +15 -3
- package/dist/integrations/github/sandbox-release.d.ts.map +1 -1
- package/dist/integrations/github/sandbox-release.js +20 -3
- package/dist/integrations/github/sandbox-release.js.map +1 -1
- package/dist/integrations/github/sandbox.d.ts +2 -3
- package/dist/integrations/github/sandbox.d.ts.map +1 -1
- package/dist/integrations/github/sandbox.js +78 -7
- package/dist/integrations/github/sandbox.js.map +1 -1
- package/dist/integrations/github/session-subscriptions.d.ts.map +1 -1
- package/dist/integrations/github/session-subscriptions.js +1 -2
- package/dist/integrations/github/session-subscriptions.js.map +1 -1
- package/dist/integrations/github/subscriptions.d.ts +7 -4
- package/dist/integrations/github/subscriptions.d.ts.map +1 -1
- package/dist/integrations/github/subscriptions.js +5 -5
- package/dist/integrations/github/subscriptions.js.map +1 -1
- package/dist/integrations/github/webhook.d.ts +4 -2
- package/dist/integrations/github/webhook.d.ts.map +1 -1
- package/dist/integrations/github/webhook.js +59 -36
- package/dist/integrations/github/webhook.js.map +1 -1
- package/dist/integrations/gitlab/api.d.ts +16 -1
- package/dist/integrations/gitlab/api.d.ts.map +1 -1
- package/dist/integrations/gitlab/api.js +9 -0
- package/dist/integrations/gitlab/api.js.map +1 -1
- package/dist/integrations/gitlab/routes.d.ts.map +1 -1
- package/dist/integrations/gitlab/routes.js +10 -6
- package/dist/integrations/gitlab/routes.js.map +1 -1
- package/dist/integrations/gitlab/rules.d.ts.map +1 -1
- package/dist/integrations/gitlab/rules.js +28 -21
- package/dist/integrations/gitlab/rules.js.map +1 -1
- package/dist/integrations/gitlab/session-subscriptions.d.ts.map +1 -1
- package/dist/integrations/gitlab/session-subscriptions.js +1 -2
- package/dist/integrations/gitlab/session-subscriptions.js.map +1 -1
- package/dist/integrations/gitlab/subscriptions.d.ts +7 -4
- package/dist/integrations/gitlab/subscriptions.d.ts.map +1 -1
- package/dist/integrations/gitlab/subscriptions.js +5 -5
- package/dist/integrations/gitlab/subscriptions.js.map +1 -1
- package/dist/integrations/gitlab/version-control.d.ts.map +1 -1
- package/dist/integrations/gitlab/version-control.js +37 -3
- package/dist/integrations/gitlab/version-control.js.map +1 -1
- package/dist/integrations/incidentio/api.d.ts +9 -0
- package/dist/integrations/incidentio/api.d.ts.map +1 -1
- package/dist/integrations/incidentio/api.js +3 -0
- package/dist/integrations/incidentio/api.js.map +1 -1
- package/dist/integrations/incidentio/intake.d.ts +1 -1
- package/dist/integrations/incidentio/intake.d.ts.map +1 -1
- package/dist/integrations/incidentio/intake.js.map +1 -1
- package/dist/integrations/incidentio/integration.d.ts.map +1 -1
- package/dist/integrations/incidentio/integration.js +1 -0
- package/dist/integrations/incidentio/integration.js.map +1 -1
- package/dist/integrations/jira/api.d.ts +7 -0
- package/dist/integrations/jira/api.d.ts.map +1 -1
- package/dist/integrations/jira/api.js +4 -0
- package/dist/integrations/jira/api.js.map +1 -1
- package/dist/integrations/linear/agent-tools.d.ts.map +1 -1
- package/dist/integrations/linear/agent-tools.js.map +1 -1
- package/dist/integrations/platform/api-client.d.ts +6 -0
- package/dist/integrations/platform/api-client.d.ts.map +1 -1
- package/dist/integrations/platform/api-client.js +6 -1
- package/dist/integrations/platform/api-client.js.map +1 -1
- package/dist/integrations/platform/connect/routes.d.ts +1 -1
- package/dist/integrations/platform/connect/routes.d.ts.map +1 -1
- package/dist/integrations/platform/connect/routes.js.map +1 -1
- package/dist/integrations/platform/github/integration.d.ts.map +1 -1
- package/dist/integrations/platform/github/integration.js +27 -1
- package/dist/integrations/platform/github/integration.js.map +1 -1
- package/dist/integrations/platform/incidentio/integration.d.ts +1 -1
- package/dist/integrations/platform/incidentio/integration.d.ts.map +1 -1
- package/dist/integrations/platform/incidentio/integration.js +1 -0
- package/dist/integrations/platform/incidentio/integration.js.map +1 -1
- package/dist/integrations/platform/jira/integration.d.ts +1 -1
- package/dist/integrations/platform/jira/integration.d.ts.map +1 -1
- package/dist/integrations/platform/jira/integration.js +1 -0
- package/dist/integrations/platform/jira/integration.js.map +1 -1
- package/dist/integrations/platform/linear/event-worker.d.ts +1 -1
- package/dist/integrations/platform/linear/event-worker.d.ts.map +1 -1
- package/dist/integrations/platform/linear/event-worker.js.map +1 -1
- package/dist/integrations/slack/integration.d.ts.map +1 -1
- package/dist/integrations/slack/integration.js +33 -6
- package/dist/integrations/slack/integration.js.map +1 -1
- package/dist/integrations/slack/slack.d.ts +16 -5
- package/dist/integrations/slack/slack.d.ts.map +1 -1
- package/dist/integrations/slack/slack.js +137 -64
- package/dist/integrations/slack/slack.js.map +1 -1
- package/dist/integrations/subscription-session.d.ts +14 -0
- package/dist/integrations/subscription-session.d.ts.map +1 -1
- package/dist/integrations/subscription-session.js +30 -16
- package/dist/integrations/subscription-session.js.map +1 -1
- package/dist/mcp/disconnect.d.ts +10 -0
- package/dist/mcp/disconnect.d.ts.map +1 -0
- package/dist/mcp/disconnect.js +44 -0
- package/dist/mcp/disconnect.js.map +1 -0
- package/dist/mcp/routes.d.ts +46 -0
- package/dist/mcp/routes.d.ts.map +1 -0
- package/dist/mcp/routes.js +243 -0
- package/dist/mcp/routes.js.map +1 -0
- package/dist/provider-display-name.d.ts +2 -0
- package/dist/provider-display-name.d.ts.map +1 -0
- package/dist/provider-display-name.js +16 -0
- package/dist/provider-display-name.js.map +1 -0
- package/dist/review-verdict.d.ts +11 -0
- package/dist/review-verdict.d.ts.map +1 -1
- package/dist/review-verdict.js +16 -1
- package/dist/review-verdict.js.map +1 -1
- package/dist/routes/attention-activity.d.ts.map +1 -1
- package/dist/routes/attention-activity.js +5 -2
- package/dist/routes/attention-activity.js.map +1 -1
- package/dist/routes/attention-providers.d.ts +0 -1
- package/dist/routes/attention-providers.d.ts.map +1 -1
- package/dist/routes/attention-providers.js +5 -6
- package/dist/routes/attention-providers.js.map +1 -1
- package/dist/routes/config.d.ts +44 -5
- package/dist/routes/config.d.ts.map +1 -1
- package/dist/routes/config.js +181 -58
- package/dist/routes/config.js.map +1 -1
- package/dist/routes/contracts.d.ts +4 -0
- package/dist/routes/contracts.d.ts.map +1 -1
- package/dist/routes/contracts.js +3 -1
- package/dist/routes/contracts.js.map +1 -1
- package/dist/routes/custom-provider-source.d.ts +8 -0
- package/dist/routes/custom-provider-source.d.ts.map +1 -1
- package/dist/routes/custom-provider-source.js +22 -1
- package/dist/routes/custom-provider-source.js.map +1 -1
- package/dist/routes/oauth.js +1 -1
- package/dist/routes/oauth.js.map +1 -1
- package/dist/routes/projects.d.ts +1 -1
- package/dist/routes/projects.d.ts.map +1 -1
- package/dist/routes/projects.js +1 -1
- package/dist/routes/projects.js.map +1 -1
- package/dist/routes/provider-credentials.d.ts +7 -2
- package/dist/routes/provider-credentials.d.ts.map +1 -1
- package/dist/routes/provider-credentials.js +30 -8
- package/dist/routes/provider-credentials.js.map +1 -1
- package/dist/routes/skills.d.ts +2 -4
- package/dist/routes/skills.d.ts.map +1 -1
- package/dist/routes/skills.js +19 -65
- package/dist/routes/skills.js.map +1 -1
- package/dist/routes/source-control-sessions.d.ts +9 -1
- package/dist/routes/source-control-sessions.d.ts.map +1 -1
- package/dist/routes/source-control-sessions.js +131 -7
- package/dist/routes/source-control-sessions.js.map +1 -1
- package/dist/routes/surface.d.ts +3 -2
- package/dist/routes/surface.d.ts.map +1 -1
- package/dist/routes/surface.js +7 -3
- package/dist/routes/surface.js.map +1 -1
- package/dist/routes/work-items.d.ts.map +1 -1
- package/dist/routes/work-items.js +60 -120
- package/dist/routes/work-items.js.map +1 -1
- package/dist/rules/binding-context.js +7 -7
- package/dist/rules/binding-context.js.map +1 -1
- package/dist/rules/dispatch-errors.d.ts.map +1 -1
- package/dist/rules/dispatch-errors.js +12 -0
- package/dist/rules/dispatch-errors.js.map +1 -1
- package/dist/rules/dispatcher.d.ts +8 -1
- package/dist/rules/dispatcher.d.ts.map +1 -1
- package/dist/rules/dispatcher.js +117 -12
- package/dist/rules/dispatcher.js.map +1 -1
- package/dist/rules/processor.d.ts.map +1 -1
- package/dist/rules/processor.js +2 -1
- package/dist/rules/processor.js.map +1 -1
- package/dist/rules/start-coordinator.d.ts +4 -0
- package/dist/rules/start-coordinator.d.ts.map +1 -1
- package/dist/rules/start-coordinator.js +36 -4
- package/dist/rules/start-coordinator.js.map +1 -1
- package/dist/rules/tools.d.ts.map +1 -1
- package/dist/rules/tools.js +82 -3
- package/dist/rules/tools.js.map +1 -1
- package/dist/rules/transition-service.d.ts +17 -0
- package/dist/rules/transition-service.d.ts.map +1 -1
- package/dist/rules/transition-service.js +25 -0
- package/dist/rules/transition-service.js.map +1 -1
- package/dist/rules/types.d.ts +2 -4
- package/dist/rules/types.d.ts.map +1 -1
- package/dist/rules/types.js +1 -4
- package/dist/rules/types.js.map +1 -1
- package/dist/sandbox/local-factory-sandbox.d.ts.map +1 -1
- package/dist/sandbox/local-factory-sandbox.js +1 -0
- package/dist/sandbox/local-factory-sandbox.js.map +1 -1
- package/dist/sandbox/session-retirement.d.ts +27 -3
- package/dist/sandbox/session-retirement.d.ts.map +1 -1
- package/dist/sandbox/session-retirement.js +158 -20
- package/dist/sandbox/session-retirement.js.map +1 -1
- package/dist/secret-encryption.d.ts.map +1 -1
- package/dist/secret-encryption.js +65 -25
- package/dist/secret-encryption.js.map +1 -1
- package/dist/session/channel-reply-processor.d.ts +36 -0
- package/dist/session/channel-reply-processor.d.ts.map +1 -0
- package/dist/session/channel-reply-processor.js +46 -0
- package/dist/session/channel-reply-processor.js.map +1 -0
- package/dist/session/channel-session-link-processor.d.ts +39 -0
- package/dist/session/channel-session-link-processor.d.ts.map +1 -0
- package/dist/session/channel-session-link-processor.js +100 -0
- package/dist/session/channel-session-link-processor.js.map +1 -0
- package/dist/session/credential-policy.d.ts +1 -1
- package/dist/session/credential-policy.d.ts.map +1 -1
- package/dist/session/credential-policy.js +7 -6
- package/dist/session/credential-policy.js.map +1 -1
- package/dist/session/default-model-hydration.d.ts +1 -0
- package/dist/session/default-model-hydration.d.ts.map +1 -1
- package/dist/session/default-model-hydration.js +3 -2
- package/dist/session/default-model-hydration.js.map +1 -1
- package/dist/session/environment-state-processor.js +1 -1
- package/dist/session/environment-state-processor.js.map +1 -1
- package/dist/session/factory-session.d.ts +19 -1
- package/dist/session/factory-session.d.ts.map +1 -1
- package/dist/session/factory-session.js +5 -2
- package/dist/session/factory-session.js.map +1 -1
- package/dist/session/filesystem-capture.d.ts +3 -3
- package/dist/session/filesystem-capture.d.ts.map +1 -1
- package/dist/session/filesystem-capture.js +16 -17
- package/dist/session/filesystem-capture.js.map +1 -1
- package/dist/session/first-exec-capture.d.ts +1 -1
- package/dist/session/first-exec-capture.d.ts.map +1 -1
- package/dist/session/first-exec-capture.js +1 -1
- package/dist/session/first-exec-capture.js.map +1 -1
- package/dist/session/first-message-capture.d.ts +1 -1
- package/dist/session/first-message-capture.d.ts.map +1 -1
- package/dist/session/first-message-capture.js +1 -1
- package/dist/session/first-message-capture.js.map +1 -1
- package/dist/session/live-sessions.d.ts +1 -1
- package/dist/session/live-sessions.js +1 -1
- package/dist/session/live-sessions.js.map +1 -1
- package/dist/session/memory-settings-hydration.d.ts +1 -1
- package/dist/session/memory-settings-hydration.d.ts.map +1 -1
- package/dist/session/memory-settings-hydration.js +1 -1
- package/dist/session/memory-settings-hydration.js.map +1 -1
- package/dist/session/reply-credential-processor.d.ts +15 -0
- package/dist/session/reply-credential-processor.d.ts.map +1 -0
- package/dist/session/reply-credential-processor.js +38 -0
- package/dist/session/reply-credential-processor.js.map +1 -0
- package/dist/session/resource-migration.d.ts +87 -0
- package/dist/session/resource-migration.d.ts.map +1 -0
- package/dist/session/resource-migration.js +163 -0
- package/dist/session/resource-migration.js.map +1 -0
- package/dist/session/run-audit.d.ts +24 -0
- package/dist/session/run-audit.d.ts.map +1 -1
- package/dist/session/run-audit.js +93 -7
- package/dist/session/run-audit.js.map +1 -1
- package/dist/session/session-threads.d.ts +49 -0
- package/dist/session/session-threads.d.ts.map +1 -0
- package/dist/session/session-threads.js +70 -0
- package/dist/session/session-threads.js.map +1 -0
- package/dist/session/source-control-tools.d.ts +19 -1
- package/dist/session/source-control-tools.d.ts.map +1 -1
- package/dist/session/source-control-tools.js +78 -11
- package/dist/session/source-control-tools.js.map +1 -1
- package/dist/session/thinking-default.d.ts.map +1 -1
- package/dist/session/thinking-default.js +3 -4
- package/dist/session/thinking-default.js.map +1 -1
- package/dist/session/thread-session-stamp.d.ts +21 -0
- package/dist/session/thread-session-stamp.d.ts.map +1 -0
- package/dist/session/thread-session-stamp.js +20 -0
- package/dist/session/thread-session-stamp.js.map +1 -0
- package/dist/session/thread-title-mirror.d.ts +1 -1
- package/dist/session/thread-title-mirror.d.ts.map +1 -1
- package/dist/session/thread-title-mirror.js +1 -1
- package/dist/session/thread-title-mirror.js.map +1 -1
- package/dist/session/work-item-repository.d.ts +1 -1
- package/dist/session/work-item-repository.d.ts.map +1 -1
- package/dist/session/work-item-repository.js.map +1 -1
- package/dist/skills/service.d.ts +6 -8
- package/dist/skills/service.d.ts.map +1 -1
- package/dist/skills/service.js +3 -3
- package/dist/skills/service.js.map +1 -1
- package/dist/storage/domains/audit/agent-audit.d.ts.map +1 -1
- package/dist/storage/domains/audit/agent-audit.js +2 -2
- package/dist/storage/domains/audit/agent-audit.js.map +1 -1
- package/dist/storage/domains/audit/domain.js +1 -1
- package/dist/storage/domains/filesystem/base.d.ts +4 -0
- package/dist/storage/domains/filesystem/base.d.ts.map +1 -1
- package/dist/storage/domains/filesystem/base.js +5 -0
- package/dist/storage/domains/filesystem/base.js.map +1 -1
- package/dist/storage/domains/integrations/base.d.ts +8 -0
- package/dist/storage/domains/integrations/base.d.ts.map +1 -1
- package/dist/storage/domains/integrations/base.js +7 -0
- package/dist/storage/domains/integrations/base.js.map +1 -1
- package/dist/storage/domains/projects/base.d.ts +6 -0
- package/dist/storage/domains/projects/base.d.ts.map +1 -1
- package/dist/storage/domains/projects/base.js +12 -0
- package/dist/storage/domains/projects/base.js.map +1 -1
- package/dist/storage/domains/source-control/base.d.ts +28 -0
- package/dist/storage/domains/source-control/base.d.ts.map +1 -1
- package/dist/storage/domains/source-control/base.js +48 -0
- package/dist/storage/domains/source-control/base.js.map +1 -1
- package/dist/storage/domains/source-control/inmemory.d.ts +8 -0
- package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
- package/dist/storage/domains/source-control/inmemory.js +13 -1
- package/dist/storage/domains/source-control/inmemory.js.map +1 -1
- package/dist/storage/domains/work-items/base.d.ts +79 -7
- package/dist/storage/domains/work-items/base.d.ts.map +1 -1
- package/dist/storage/domains/work-items/base.js +172 -30
- package/dist/storage/domains/work-items/base.js.map +1 -1
- package/dist/timing.d.ts +23 -0
- package/dist/timing.d.ts.map +1 -1
- package/dist/timing.js +85 -6
- package/dist/timing.js.map +1 -1
- package/dist/tools/annotations.d.ts +7 -0
- package/dist/tools/annotations.d.ts.map +1 -0
- package/dist/tools/annotations.js +102 -0
- package/dist/tools/annotations.js.map +1 -0
- package/dist/tools/approval-tools.d.ts +15 -0
- package/dist/tools/approval-tools.d.ts.map +1 -0
- package/dist/tools/approval-tools.js +79 -0
- package/dist/tools/approval-tools.js.map +1 -0
- package/dist/tools/deliver.d.ts +71 -0
- package/dist/tools/deliver.d.ts.map +1 -0
- package/dist/tools/deliver.js +129 -0
- package/dist/tools/deliver.js.map +1 -0
- package/dist/tools/index.d.ts +30 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +36 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/messaging-tools.d.ts +28 -0
- package/dist/tools/messaging-tools.d.ts.map +1 -0
- package/dist/tools/messaging-tools.js +396 -0
- package/dist/tools/messaging-tools.js.map +1 -0
- package/dist/tools/permissions.d.ts +31 -0
- package/dist/tools/permissions.d.ts.map +1 -0
- package/dist/tools/permissions.js +48 -0
- package/dist/tools/permissions.js.map +1 -0
- package/dist/tools/read-tools.d.ts +30 -0
- package/dist/tools/read-tools.d.ts.map +1 -0
- package/dist/tools/read-tools.js +293 -0
- package/dist/tools/read-tools.js.map +1 -0
- package/dist/tools/reports.d.ts +174 -0
- package/dist/tools/reports.d.ts.map +1 -0
- package/dist/tools/reports.js +380 -0
- package/dist/tools/reports.js.map +1 -0
- package/dist/tools/run-watch.d.ts +46 -0
- package/dist/tools/run-watch.d.ts.map +1 -0
- package/dist/tools/run-watch.js +102 -0
- package/dist/tools/run-watch.js.map +1 -0
- package/dist/tools/scope.d.ts +54 -0
- package/dist/tools/scope.d.ts.map +1 -0
- package/dist/tools/scope.js +87 -0
- package/dist/tools/scope.js.map +1 -0
- package/dist/tools/session-tools.d.ts +43 -0
- package/dist/tools/session-tools.d.ts.map +1 -0
- package/dist/tools/session-tools.js +410 -0
- package/dist/tools/session-tools.js.map +1 -0
- package/dist/tools/sessions-state-processor.d.ts +41 -0
- package/dist/tools/sessions-state-processor.d.ts.map +1 -0
- package/dist/tools/sessions-state-processor.js +190 -0
- package/dist/tools/sessions-state-processor.js.map +1 -0
- package/dist/tools/side-sessions.d.ts +53 -0
- package/dist/tools/side-sessions.d.ts.map +1 -0
- package/dist/tools/side-sessions.js +83 -0
- package/dist/tools/side-sessions.js.map +1 -0
- package/dist/tools/work-item-tools.d.ts +28 -0
- package/dist/tools/work-item-tools.d.ts.map +1 -0
- package/dist/tools/work-item-tools.js +342 -0
- package/dist/tools/work-item-tools.js.map +1 -0
- package/dist/work-items/create-work-item.d.ts +55 -0
- package/dist/work-items/create-work-item.d.ts.map +1 -0
- package/dist/work-items/create-work-item.js +101 -0
- package/dist/work-items/create-work-item.js.map +1 -0
- package/dist/work-items/decision-actions.d.ts +42 -0
- package/dist/work-items/decision-actions.d.ts.map +1 -0
- package/dist/work-items/decision-actions.js +70 -0
- package/dist/work-items/decision-actions.js.map +1 -0
- package/dist/workspace.d.ts +1 -1
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +15 -7
- package/dist/workspace.js.map +1 -1
- package/factory-skills/factory-gitlab-rereview/SKILL.md +1 -1
- package/factory-skills/factory-gitlab-review/SKILL.md +1 -1
- package/factory-skills/factory-plan/SKILL.md +4 -4
- package/factory-skills/factory-rereview/SKILL.md +3 -1
- package/factory-skills/factory-review/SKILL.md +3 -1
- package/factory-skills/factory-self-review/SKILL.md +46 -0
- package/package.json +15 -15
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rules.js","names":["#targets","#ingestProject","#trusted","#evaluate","#trackOpenMergeRequest"],"sources":["../../../src/integrations/gitlab/rules.ts"],"sourcesContent":["import {\n boardEventHandlers,\n boardForWorkItem,\n eventBoardId,\n rulePhaseField,\n withBoardClosure,\n} from '../../boards/index.js';\nimport type { BoardRegistry } from '../../boards/index.js';\nimport { hasExplicitRuleOverride, selectEventRule } from '../../rules/event-rules.js';\nimport type {\n FactoryGitLabEventName,\n FactoryGitLabRuleContext,\n FactoryRuleActor,\n FactoryRuleDecision,\n} from '../../rules/types.js';\nimport { assertFactoryDecisionTarget, validateFactoryRuleDecisions } from '../../rules/validation.js';\nimport type { IntakeStorage } from '../../storage/domains/intake/base.js';\nimport type { FactoryProjectsStorage } from '../../storage/domains/projects/base.js';\nimport type {\n ExternalRepositoryProjectTarget,\n SourceControlStorageHandle,\n} from '../../storage/domains/source-control/base.js';\nimport { WorkItemUpdateConflictError } from '../../storage/domains/work-items/base.js';\nimport type { WorkItemRow, WorkItemsStorage } from '../../storage/domains/work-items/base.js';\nimport type { IntegrationContext } from '../base.js';\nimport { recordPullRequestHead } from '../pull-request-head.js';\nimport type { GitLabEventRules } from './default-rules.js';\nimport { encodeIssueReference, encodeSourceId, GITLAB_TRUSTED_ACCESS_LEVEL } from './integration.js';\nimport type { ParsedGitLabWebhook } from './webhook.js';\n\nconst RULE_TIMEOUT_MS = 5_000;\n\ntype IngressStatus = 'ignored' | 'committed' | 'replayed' | 'missing';\n\nfunction object(value: unknown): Record<string, unknown> | undefined {\n return value && typeof value === 'object' && !Array.isArray(value) ? (value as Record<string, unknown>) : undefined;\n}\n\nfunction string(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\nfunction number(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\n/** GitLab sends `2024-01-02T03:04:05Z` or `2024-01-02 03:04:05 UTC`. */\nfunction timestamp(value: unknown): number | undefined {\n const text = string(value);\n if (!text) return undefined;\n const parsed = Date.parse(text.replace(' UTC', 'Z').replace(' ', 'T'));\n return Number.isNaN(parsed) ? undefined : parsed;\n}\n\nfunction boolean(value: unknown): boolean | undefined {\n return typeof value === 'boolean' ? value : undefined;\n}\n\nfunction usernames(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n return value.flatMap(entry => {\n const username = string(object(entry)?.username);\n return username ? [username] : [];\n });\n}\n\nfunction labelNames(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n return value.flatMap(entry => {\n if (typeof entry === 'string') return entry ? [entry] : [];\n const title = string(object(entry)?.title) ?? string(object(entry)?.name);\n return title ? [title] : [];\n });\n}\n\nfunction labelColors(value: unknown): Record<string, string> {\n if (!Array.isArray(value)) return {};\n return Object.fromEntries(\n value.flatMap(entry => {\n const label = object(entry);\n const name = string(label?.title) ?? string(label?.name);\n const color = string(label?.color);\n return name && color ? [[name, color]] : [];\n }),\n );\n}\n\nfunction eventName(parsed: ParsedGitLabWebhook): FactoryGitLabEventName | undefined {\n const attributes = object(parsed.payload.object_attributes);\n const action = string(attributes?.action)?.toLowerCase();\n if (parsed.event === 'Issue Hook') {\n if (action === 'open' || action === 'reopen') return 'issueOpened';\n if (action === 'close') return 'issueClosed';\n return 'issueUpdated';\n }\n if (parsed.event === 'Note Hook') {\n const noteableType = string(attributes?.noteable_type);\n if (noteableType === 'Issue') return 'issueCommented';\n if (noteableType === 'MergeRequest') return 'mergeRequestCommented';\n return;\n }\n if (parsed.event !== 'Merge Request Hook') return;\n if (action === 'open' || action === 'reopen') return 'mergeRequestOpened';\n if (action === 'merge') return 'mergeRequestMerged';\n if (action === 'close') return 'mergeRequestClosed';\n return 'mergeRequestUpdated';\n}\n\nfunction normalizeHost(host: string): string {\n return host.trim().toLowerCase().replace(/\\.$/, '');\n}\n\nfunction hostFromPayload(parsed: ParsedGitLabWebhook): string | undefined {\n const declaredHost = parsed.instanceHost ? normalizeHost(parsed.instanceHost) : undefined;\n const projectUrl = string(object(parsed.payload.project)?.web_url);\n if (!projectUrl) return declaredHost;\n try {\n const payloadHost = normalizeHost(new URL(projectUrl).host);\n if (declaredHost && declaredHost !== payloadHost) return undefined;\n return payloadHost;\n } catch {\n return undefined;\n }\n}\n\nfunction mergeRequestSourceKey(host: string, projectId: number, mergeRequestIid: number): string {\n return `gitlab-pr:${Buffer.from(JSON.stringify({ version: 1, host, projectId, mergeRequestIid }), 'utf8').toString('base64url')}`;\n}\n\nasync function withRuleTimeout<T>(promise: Promise<T>): Promise<T> {\n let timeout: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n promise,\n new Promise<never>((_, reject) => {\n timeout = setTimeout(() => reject(new Error('FACTORY_RULE_TIMEOUT')), RULE_TIMEOUT_MS);\n }),\n ]);\n } finally {\n if (timeout) clearTimeout(timeout);\n }\n}\n\nconst MAX_STALE_RULE_ATTEMPTS = 3;\n\nexport interface GitLabRulesIntegration {\n readonly rules: GitLabEventRules;\n resolveActiveConnectionForHost?(storedConnectionId: string, host: string): Promise<string>;\n getProjectMemberAccessLevel(connectionId: string, projectId: string, username: string): Promise<number | undefined>;\n getWorkItemAuthorUsername(\n connectionId: string,\n projectId: string,\n kind: 'issue' | 'merge_request',\n iid: number,\n ): Promise<string | undefined>;\n}\n\ninterface GitLabRulesOptions {\n gitlab: GitLabRulesIntegration;\n sourceControl: SourceControlStorageHandle;\n projects: FactoryProjectsStorage;\n storage: WorkItemsStorage;\n intake: IntakeStorage;\n configVersion: string;\n boards: BoardRegistry;\n}\n\ninterface ProjectTarget {\n target: ExternalRepositoryProjectTarget;\n connectionIds: string[];\n}\n\nexport class GitLabRules {\n constructor(private readonly options: GitLabRulesOptions) {}\n\n async ingest(parsed: ParsedGitLabWebhook): Promise<{ status: IngressStatus }> {\n const event = eventName(parsed);\n const project = object(parsed.payload.project);\n const projectId = number(project?.id);\n const projectPath = string(project?.path_with_namespace);\n const host = hostFromPayload(parsed);\n const username = string(parsed.payload.user_username) ?? string(object(parsed.payload.user)?.username);\n if (!event || !projectId || !projectPath || !host || !username) return { status: 'ignored' };\n\n const targets = await this.#targets(String(projectId), host);\n if (targets.length === 0) return { status: 'ignored' };\n const results: Array<{ status: IngressStatus }> = [];\n for (const target of targets) {\n results.push(await this.#ingestProject(parsed, event, projectId, projectPath, host, username, target));\n }\n if (results.some(result => result.status === 'committed')) return { status: 'committed' };\n if (results.some(result => result.status === 'replayed')) return { status: 'replayed' };\n return results[0] ?? { status: 'ignored' };\n }\n\n async #targets(repositoryExternalId: string, host: string): Promise<ProjectTarget[]> {\n const grouped = new Map<string, ProjectTarget>();\n const keys = (await this.options.sourceControl.projectRepositories.listConfiguredExternalKeys()).filter(\n key => key.repositoryExternalId === repositoryExternalId,\n );\n for (const key of keys) {\n const targets = await this.options.sourceControl.projectRepositories.listByExternalRepository({\n installationExternalId: key.installationExternalId,\n repositoryExternalId,\n });\n for (const target of targets) {\n const installation = await this.options.sourceControl.installations.findByExternalId({\n orgId: target.orgId,\n externalId: key.installationExternalId,\n });\n const installationHost = string(installation?.providerMetadata.host);\n if (!installationHost || normalizeHost(installationHost) !== normalizeHost(host)) continue;\n const id = `${target.orgId}:${target.factoryProjectId}`;\n const current = grouped.get(id);\n if (current) {\n if (!current.connectionIds.includes(key.installationExternalId)) {\n current.connectionIds.push(key.installationExternalId);\n }\n } else {\n grouped.set(id, { target, connectionIds: [key.installationExternalId] });\n }\n }\n }\n return [...grouped.values()];\n }\n\n async #ingestProject(\n parsed: ParsedGitLabWebhook,\n event: FactoryGitLabEventName,\n projectId: number,\n projectPath: string,\n host: string,\n username: string,\n target: ProjectTarget,\n ): Promise<{ status: IngressStatus }> {\n const factoryProject = await this.options.projects.get({\n orgId: target.target.orgId,\n id: target.target.factoryProjectId,\n });\n if (!factoryProject) return { status: 'missing' };\n\n const sourceId = encodeSourceId({ host, projectId: String(projectId) });\n const config = await this.options.intake.getConfig({\n orgId: target.target.orgId,\n integrationIds: ['gitlab'],\n });\n if (!config.gitlab?.enabled || !config.gitlab.sourceIds?.includes(sourceId)) return { status: 'ignored' };\n const binding = (\n await this.options.intake.listBindings({ orgId: target.target.orgId, integrationId: 'gitlab' })\n ).find(\n candidate => candidate.sourceId === sourceId && candidate.factoryProjectId === target.target.factoryProjectId,\n );\n if (!binding) return { status: 'ignored' };\n const board = this.options.boards.get(binding.board ?? 'work');\n if (!board) return { status: 'ignored' };\n\n const connectionIds = [\n ...new Set(\n await Promise.all(\n target.connectionIds.map(\n connectionId => this.options.gitlab.resolveActiveConnectionForHost?.(connectionId, host) ?? connectionId,\n ),\n ),\n ),\n ];\n\n const attributes = object(parsed.payload.object_attributes);\n const issue = parsed.event === 'Note Hook' ? object(parsed.payload.issue) : attributes;\n const mergeRequest = parsed.event === 'Note Hook' ? object(parsed.payload.merge_request) : attributes;\n const issueIid = number(issue?.iid);\n const mergeRequestIid = number(mergeRequest?.iid);\n const issueSourceKey = issueIid\n ? encodeIssueReference({ host, projectId: String(projectId), issueIid })\n : undefined;\n const mergeRequestKey = mergeRequestIid ? mergeRequestSourceKey(host, projectId, mergeRequestIid) : undefined;\n const items = await this.options.storage.list({\n orgId: target.target.orgId,\n factoryProjectId: target.target.factoryProjectId,\n });\n const issueItem = issueSourceKey\n ? items.find(\n item => item.externalSource?.integrationId === 'gitlab' && item.externalSource.externalId === issueSourceKey,\n )\n : undefined;\n const reviewItem = mergeRequestKey\n ? items.find(\n item => item.externalSource?.integrationId === 'gitlab' && item.externalSource.externalId === mergeRequestKey,\n )\n : undefined;\n const headBranch = string(mergeRequest?.source_branch) ?? '';\n const authoringItem = headBranch\n ? items.find(\n item =>\n item.externalSource?.type !== 'pull-request' &&\n Object.values(item.sessions).some(session => session.branch === headBranch),\n )\n : undefined;\n const actorTrusted = await this.#trusted(connectionIds, String(projectId), username);\n const eventUserId = number(object(parsed.payload.user)?.id) ?? number(parsed.payload.user_id);\n const resolveAuthor = async (\n item: Record<string, unknown> | undefined,\n kind: 'issue' | 'merge_request',\n iid: number | undefined,\n ): Promise<string | undefined> => {\n if (!item || !iid) return undefined;\n const embedded = string(object(item.author)?.username);\n if (embedded) return embedded;\n if (eventUserId && eventUserId === number(item.author_id)) return username;\n for (const connectionId of connectionIds) {\n try {\n const resolved = await this.options.gitlab.getWorkItemAuthorUsername(\n connectionId,\n String(projectId),\n kind,\n iid,\n );\n if (resolved) return resolved;\n } catch {\n // Another active credential may cover this project; otherwise leave author trust unset.\n }\n }\n return undefined;\n };\n const [issueAuthor, mergeRequestAuthor] = await Promise.all([\n resolveAuthor(issue, 'issue', issueIid),\n resolveAuthor(mergeRequest, 'merge_request', mergeRequestIid),\n ]);\n const authorTrusted = async (author: string | undefined): Promise<boolean> => {\n if (!author) return false;\n return author === username ? actorTrusted : this.#trusted(connectionIds, String(projectId), author);\n };\n const [issueAuthorTrusted, mergeRequestAuthorTrusted] = await Promise.all([\n authorTrusted(issueAuthor),\n authorTrusted(mergeRequestAuthor),\n ]);\n const actor: FactoryRuleActor = {\n type: 'gitlab',\n username,\n trusted: actorTrusted,\n factoryAuthored: false,\n };\n\n const candidates: Array<WorkItemRow | undefined> =\n event === 'mergeRequestMerged'\n ? [...new Map([reviewItem, authoringItem].filter(Boolean).map(item => [item!.id, item!])).values()]\n : event === 'mergeRequestCommented'\n ? [authoringItem ?? reviewItem]\n : event === 'mergeRequestOpened'\n ? [authoringItem, ...(authoringItem ? [authoringItem] : [])]\n : event.startsWith('mergeRequest')\n ? [reviewItem ?? authoringItem]\n : [issueItem];\n const results = [];\n for (const [index, item] of candidates.entries()) {\n results.push(\n await this.#evaluate({\n parsed,\n event,\n projectId,\n projectPath,\n host,\n actor,\n orgId: target.target.orgId,\n factoryProjectId: target.target.factoryProjectId,\n factoryProject,\n board: { board: board.id, initialPhase: board.initialPhase },\n issue,\n issueAuthor,\n issueAuthorTrusted,\n issueIid,\n issueSourceKey,\n mergeRequest,\n mergeRequestAuthor,\n mergeRequestAuthorTrusted,\n mergeRequestIid,\n mergeRequestKey,\n factoryAuthored: Boolean(authoringItem),\n item,\n ingressIdentity: `gitlab:${projectId}:${parsed.deliveryId}${index ? `:${item?.id ?? index}` : ''}`,\n // A merge request opening concerns two cards: its own Review card,\n // which the integration handler files against the authoring item,\n // and that authoring item, which its board may react to. The second\n // evaluation runs only the board handler, under an id-suffixed\n // identity so it is not dropped as a replay of the first.\n ...(event === 'mergeRequestOpened' ? { mergeRequestOpenedScope: index ? ('board' as const) : ('intake' as const) } : {}),\n }),\n );\n }\n // The authoring Work item records which merge request it has out, so the\n // Work transition policy keeps an agent from closing the work while it is\n // open. Sessions carry no repository, so a branch shared by two Work cards\n // is ambiguous and records nothing.\n const branchOwners = headBranch\n ? items.filter(\n item =>\n item.externalSource?.type !== 'pull-request' &&\n Object.values(item.sessions).some(session => session.branch === headBranch),\n )\n : [];\n if (authoringItem && mergeRequestIid && branchOwners.length === 1) {\n await this.#trackOpenMergeRequest({\n orgId: authoringItem.orgId,\n id: authoringItem.id,\n event,\n mergeRequestIid,\n eventAt: timestamp(mergeRequest?.updated_at),\n opened: string(mergeRequest?.state) === 'opened' && (actorTrusted || mergeRequestAuthorTrusted),\n headSha: string(object(mergeRequest?.last_commit)?.id)?.toLowerCase(),\n });\n }\n // A push moves the head a recorded review verdict was given on. Recording\n // the new head lets the verdict badge show that it is outdated, while the\n // verdict itself stays so the next pass still runs as a re-review.\n const headSha = string(object(mergeRequest?.last_commit)?.id)?.toLowerCase();\n const headAt = timestamp(mergeRequest?.updated_at);\n if (event === 'mergeRequestUpdated' && headSha && reviewItem) {\n const parent = items.find(item => item.id === reviewItem.parentWorkItemId);\n // The authoring Work item mirrors the verdict of the merge request it has\n // out; a push to an older one must not mark its successor's verdict.\n await recordPullRequestHead(this.options.storage, {\n orgId: reviewItem.orgId,\n id: reviewItem.id,\n headSha,\n headAt,\n });\n if (parent) {\n await recordPullRequestHead(this.options.storage, {\n orgId: parent.orgId,\n id: parent.id,\n headSha,\n headAt,\n ownedPullRequestNumber: mergeRequestIid,\n });\n }\n }\n if (results.some(result => result.status === 'committed')) return { status: 'committed' };\n if (results.some(result => result.status === 'replayed')) return { status: 'replayed' };\n return results[0] ?? { status: 'ignored' };\n }\n\n /**\n * Records or clears the merge request a Work card has out. Each merge\n * request's last applied event time is kept on the card, so an opening that\n * is older than a close already applied (a replay, or a close delivered\n * first) is ignored, while a genuine reopen is newer and records it again.\n */\n async #trackOpenMergeRequest(input: {\n orgId: string;\n id: string;\n event: string;\n mergeRequestIid: number;\n eventAt: number | undefined;\n opened: boolean;\n headSha: string | undefined;\n }): Promise<void> {\n const settling = input.event === 'mergeRequestMerged' || input.event === 'mergeRequestClosed';\n if (input.event !== 'mergeRequestOpened' && !settling) return;\n const key = String(input.mergeRequestIid);\n for (let attempt = 0; attempt < 3; attempt++) {\n const item = await this.options.storage.get({ orgId: input.orgId, id: input.id });\n if (!item) return;\n const events = object(item.metadata?.mergeRequestEvents) ?? {};\n const last = object(events[key]);\n const lastAt = number(last?.at);\n if (input.eventAt !== undefined && lastAt !== undefined && lastAt >= input.eventAt) return;\n // Without a timestamp, only an opening that no close has settled is trusted.\n if (input.eventAt === undefined && !settling && last?.open === false) return;\n if (!settling && !input.opened) return;\n const recorded = item.metadata?.openPullRequestNumber;\n const patch: Record<string, unknown> = {\n mergeRequestEvents: { ...events, [key]: { at: input.eventAt ?? lastAt ?? null, open: !settling } },\n };\n if (!settling) {\n // A head recorded for an earlier merge request must not mark this one's\n // verdict outdated. A replayed opening of the merge request already\n // recorded keeps the head its pushes left.\n if (recorded !== input.mergeRequestIid) {\n patch.pullRequestHeadSha = input.headSha ?? null;\n patch.pullRequestHeadAt = input.eventAt ?? null;\n }\n patch.openPullRequestNumber = input.mergeRequestIid;\n } else if (recorded === input.mergeRequestIid) {\n patch.openPullRequestNumber = null;\n if (input.event === 'mergeRequestMerged' && typeof item.metadata?.reviewVerdict === 'string') {\n patch.reviewVerdict = null;\n }\n }\n try {\n await this.options.storage.update({\n orgId: item.orgId,\n id: item.id,\n userId: 'factory-rule-dispatcher',\n expectedRevision: item.revision,\n patch: { metadata: patch },\n });\n return;\n } catch (error) {\n if (!(error instanceof WorkItemUpdateConflictError)) throw error;\n }\n }\n }\n\n async #trusted(connectionIds: string[], projectId: string, username: string): Promise<boolean> {\n for (const connectionId of connectionIds) {\n try {\n const level = await this.options.gitlab.getProjectMemberAccessLevel(connectionId, projectId, username);\n if (level !== undefined) return level >= GITLAB_TRUSTED_ACCESS_LEVEL;\n } catch {\n // Try another active credential covering the same canonical project; otherwise fail closed.\n }\n }\n return false;\n }\n\n async #evaluate(\n input: {\n parsed: ParsedGitLabWebhook;\n event: FactoryGitLabEventName;\n projectId: number;\n projectPath: string;\n host: string;\n actor: FactoryRuleActor;\n orgId: string;\n factoryProjectId: string;\n factoryProject: { createdAt: Date };\n board: { board: string; initialPhase: string };\n issue: Record<string, unknown> | undefined;\n issueAuthor: string | undefined;\n issueAuthorTrusted: boolean;\n issueIid: number | undefined;\n issueSourceKey: string | undefined;\n mergeRequest: Record<string, unknown> | undefined;\n mergeRequestAuthor: string | undefined;\n mergeRequestAuthorTrusted: boolean;\n mergeRequestIid: number | undefined;\n mergeRequestKey: string | undefined;\n factoryAuthored: boolean;\n item: WorkItemRow | undefined;\n ingressIdentity: string;\n mergeRequestOpenedScope?: 'intake' | 'board';\n },\n attempt = 1,\n ): Promise<{ status: IngressStatus }> {\n const note = object(input.parsed.payload.object_attributes);\n const issueAuthor = input.issueAuthor;\n const mergeRequestAuthor = input.mergeRequestAuthor;\n const mergeRequestState = string(input.mergeRequest?.state);\n const context: FactoryGitLabRuleContext = {\n tenant: { orgId: input.orgId, projectId: input.factoryProjectId },\n actor: input.actor,\n ingress: { type: 'gitlab', id: input.ingressIdentity },\n cause: `gitlab.${input.event}`,\n causalChain: [],\n configVersion: this.options.configVersion,\n ...(input.item\n ? {\n item: {\n id: input.item.id,\n source:\n input.item.externalSource?.type === 'pull-request'\n ? ('gitlab-pr' as const)\n : input.item.externalSource?.integrationId === 'gitlab'\n ? ('gitlab-issue' as const)\n : ('manual' as const),\n sourceKey: input.item.externalSource?.externalId ?? null,\n parentWorkItemId: input.item.parentWorkItemId,\n title: input.item.title,\n url: input.item.externalSource?.url ?? null,\n stages: input.item.stages,\n acceptedAt: input.item.acceptedAt,\n metadata: input.item.metadata,\n },\n board: boardForWorkItem(input.item),\n itemRevision: input.item.revision,\n ...withBoardClosure(this.options.boards, input.item),\n ...rulePhaseField(this.options.boards, input.item),\n }\n : {}),\n intake: input.board,\n event: input.event,\n deliveryId: input.parsed.deliveryId,\n factory: { createdAt: input.factoryProject.createdAt.toISOString() },\n repository: { id: input.projectId, fullName: input.projectPath, host: input.host },\n ...(input.issueIid && input.issueSourceKey && string(input.issue?.title)\n ? {\n issue: {\n number: input.issueIid,\n sourceKey: input.issueSourceKey,\n title: string(input.issue?.title)!,\n url:\n string(input.issue?.url) ??\n string(input.issue?.web_url) ??\n `https://${input.host}/${input.projectPath}/-/issues/${input.issueIid}`,\n ...(string(input.issue?.created_at) ? { createdAt: string(input.issue?.created_at) } : {}),\n ...(string(input.issue?.updated_at) ? { updatedAt: string(input.issue?.updated_at) } : {}),\n assignees: usernames(input.parsed.payload.assignees ?? input.issue?.assignees),\n labels: labelNames(input.issue?.labels ?? input.parsed.payload.labels),\n labelColors: labelColors(input.issue?.labels ?? input.parsed.payload.labels),\n state: string(input.issue?.state) === 'closed' ? ('closed' as const) : ('open' as const),\n ...(issueAuthor ? { author: issueAuthor } : {}),\n authorTrusted: input.issueAuthorTrusted,\n },\n }\n : {}),\n ...(input.parsed.event === 'Note Hook' && number(note?.id)\n ? {\n note: {\n id: number(note?.id)!,\n ...(string(note?.note) ? { body: string(note?.note) } : {}),\n ...(string(note?.url) ? { url: string(note?.url) } : {}),\n ...(string(object(input.parsed.payload.user)?.username)\n ? { author: string(object(input.parsed.payload.user)?.username) }\n : {}),\n ...(string(note?.created_at) ? { createdAt: string(note?.created_at) } : {}),\n ...(string(note?.updated_at) ? { updatedAt: string(note?.updated_at) } : {}),\n },\n }\n : {}),\n ...(input.mergeRequestIid && input.mergeRequestKey && string(input.mergeRequest?.title)\n ? {\n mergeRequest: {\n number: input.mergeRequestIid,\n sourceKey: input.mergeRequestKey,\n title: string(input.mergeRequest?.title)!,\n url:\n string(input.mergeRequest?.url) ??\n string(input.mergeRequest?.web_url) ??\n `https://${input.host}/${input.projectPath}/-/merge_requests/${input.mergeRequestIid}`,\n ...(string(input.mergeRequest?.created_at) ? { createdAt: string(input.mergeRequest?.created_at) } : {}),\n state:\n mergeRequestState === 'closed' || mergeRequestState === 'merged'\n ? ('closed' as const)\n : ('open' as const),\n draft: boolean(input.mergeRequest?.draft) ?? boolean(input.mergeRequest?.work_in_progress) ?? false,\n merged: input.event === 'mergeRequestMerged' || mergeRequestState === 'merged',\n assignees: usernames(input.parsed.payload.assignees ?? input.mergeRequest?.assignees),\n reviewers: usernames(input.parsed.payload.reviewers ?? input.mergeRequest?.reviewers),\n labels: labelNames(input.mergeRequest?.labels ?? input.parsed.payload.labels),\n labelColors: labelColors(input.mergeRequest?.labels ?? input.parsed.payload.labels),\n ...(mergeRequestAuthor ? { author: mergeRequestAuthor } : {}),\n authorTrusted: input.mergeRequestAuthorTrusted,\n factoryAuthored: input.factoryAuthored,\n headBranch: string(input.mergeRequest?.source_branch) ?? '',\n baseBranch: string(input.mergeRequest?.target_branch) ?? '',\n },\n }\n : {}),\n };\n const boardRules = boardEventHandlers(\n this.options.boards,\n input.mergeRequestOpenedScope === 'intake' ? undefined : eventBoardId(context),\n 'gitlab',\n );\n const rule =\n input.mergeRequestOpenedScope === 'board'\n ? hasExplicitRuleOverride(this.options.gitlab.rules, input.event)\n ? undefined\n : boardRules?.mergeRequestOpened\n : selectEventRule(this.options.gitlab.rules, boardRules, input.event);\n // Without a board handler the authoring card has nothing to answer; skip\n // recording an empty evaluation for it.\n if (input.mergeRequestOpenedScope === 'board' && !rule) return { status: 'ignored' };\n let decision: FactoryRuleDecision | void;\n let decisions: Record<string, unknown>[] = [];\n let outcome: { status: 'accepted' | 'rejected'; code?: string; reason?: string } = { status: 'accepted' };\n try {\n decision = rule ? await withRuleTimeout(Promise.resolve(rule(Object.freeze(context)))) : undefined;\n if (decision?.type === 'reject') {\n outcome = { status: 'rejected', code: decision.code, reason: decision.reason };\n } else if (decision) {\n decisions = validateFactoryRuleDecisions([decision]).map(entry => {\n assertFactoryDecisionTarget(\n entry,\n this.options.boards,\n input.item ? boardForWorkItem(input.item) : undefined,\n );\n return { ...entry };\n });\n }\n } catch (error) {\n const timedOut = error instanceof Error && error.message === 'FACTORY_RULE_TIMEOUT';\n outcome = {\n status: 'rejected',\n code: timedOut ? 'timeout' : 'rule_error',\n reason: timedOut\n ? 'Factory rule evaluation timed out.'\n : error instanceof Error\n ? error.message.slice(0, 2_000)\n : 'Factory GitLab rule failed.',\n };\n }\n\n const committed = await this.options.storage.commitRuleEvaluation({\n orgId: input.orgId,\n factoryProjectId: input.factoryProjectId,\n workItemId: input.item?.id ?? null,\n ingress: { identity: input.ingressIdentity, triggerType: `gitlab.${input.event}` },\n configVersion: this.options.configVersion,\n expectedRevision: input.item?.revision ?? null,\n actor: { ...input.actor },\n outcome,\n decisions,\n causalChain: [],\n now: new Date(),\n });\n if (committed.status === 'stale') {\n // Nothing was persisted: re-read the item and evaluate again at its fresh revision.\n if (attempt >= MAX_STALE_RULE_ATTEMPTS) {\n throw new Error(`Factory GitLab rule evaluation for ${input.ingressIdentity} kept losing revision races.`);\n }\n const fresh = input.item ? await this.options.storage.get({ orgId: input.orgId, id: input.item.id }) : null;\n if (input.item && !fresh) return { status: 'missing' };\n return this.#evaluate({ ...input, item: fresh ?? undefined }, attempt + 1);\n }\n return { status: committed.status };\n }\n}\n\nexport function attachGitLabRules(\n gitlab: GitLabRulesIntegration,\n context: IntegrationContext,\n): ((event: ParsedGitLabWebhook) => Promise<unknown>) | undefined {\n if (!context.runtime) return undefined;\n const rules = new GitLabRules({\n gitlab,\n sourceControl: context.storage.sourceControl,\n projects: context.storage.projects,\n storage: context.runtime.workItems,\n intake: context.storage.intake,\n configVersion: context.runtime.configVersion,\n boards: context.runtime.boards,\n });\n return event => rules.ingest(event);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"rules.js","names":["#targets","#ingestProject","#trusted","#evaluate","#trackOpenMergeRequest"],"sources":["../../../src/integrations/gitlab/rules.ts"],"sourcesContent":["import {\n boardEventHandlers,\n boardForWorkItem,\n eventBoardId,\n rulePhaseField,\n withBoardClosure,\n} from '../../boards/index.js';\nimport type { BoardRegistry } from '../../boards/index.js';\nimport { hasExplicitRuleOverride, selectEventRule } from '../../rules/event-rules.js';\nimport type {\n FactoryGitLabEventName,\n FactoryGitLabRuleContext,\n FactoryRuleActor,\n FactoryRuleDecision,\n} from '../../rules/types.js';\nimport { assertFactoryDecisionTarget, validateFactoryRuleDecisions } from '../../rules/validation.js';\nimport type { IntakeStorage } from '../../storage/domains/intake/base.js';\nimport type { FactoryProjectsStorage } from '../../storage/domains/projects/base.js';\nimport type {\n ExternalRepositoryProjectTarget,\n SourceControlStorageHandle,\n} from '../../storage/domains/source-control/base.js';\nimport { WorkItemUpdateConflictError } from '../../storage/domains/work-items/base.js';\nimport type { WorkItemRow, WorkItemsStorage } from '../../storage/domains/work-items/base.js';\nimport type { IntegrationContext } from '../base.js';\nimport { recordPullRequestHead } from '../pull-request-head.js';\nimport type { GitLabEventRules } from './default-rules.js';\nimport { encodeIssueReference, encodeSourceId, GITLAB_TRUSTED_ACCESS_LEVEL } from './integration.js';\nimport type { ParsedGitLabWebhook } from './webhook.js';\n\nconst RULE_TIMEOUT_MS = 5_000;\n\ntype IngressStatus = 'ignored' | 'committed' | 'replayed' | 'missing';\n\nfunction object(value: unknown): Record<string, unknown> | undefined {\n return value && typeof value === 'object' && !Array.isArray(value) ? (value as Record<string, unknown>) : undefined;\n}\n\nfunction string(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\nfunction number(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\n/** GitLab sends `2024-01-02T03:04:05Z`, `2024-01-02 03:04:05 UTC` or `2024-01-02 03:04:05 +0000`. */\nfunction timestamp(value: unknown): number | undefined {\n const text = string(value);\n if (!text) return undefined;\n const spaced = /^(\\d{4}-\\d{2}-\\d{2}) (\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?) (?:UTC|([+-]\\d{2}):?(\\d{2}))$/.exec(text);\n const parsed = Date.parse(\n spaced ? `${spaced[1]}T${spaced[2]}${spaced[3] ? `${spaced[3]}:${spaced[4]}` : 'Z'}` : text,\n );\n return Number.isNaN(parsed) ? undefined : parsed;\n}\n\nfunction boolean(value: unknown): boolean | undefined {\n return typeof value === 'boolean' ? value : undefined;\n}\n\nfunction usernames(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n return value.flatMap(entry => {\n const username = string(object(entry)?.username);\n return username ? [username] : [];\n });\n}\n\nfunction labelNames(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n return value.flatMap(entry => {\n if (typeof entry === 'string') return entry ? [entry] : [];\n const title = string(object(entry)?.title) ?? string(object(entry)?.name);\n return title ? [title] : [];\n });\n}\n\nfunction labelColors(value: unknown): Record<string, string> {\n if (!Array.isArray(value)) return {};\n return Object.fromEntries(\n value.flatMap(entry => {\n const label = object(entry);\n const name = string(label?.title) ?? string(label?.name);\n const color = string(label?.color);\n return name && color ? [[name, color]] : [];\n }),\n );\n}\n\nfunction eventName(parsed: ParsedGitLabWebhook): FactoryGitLabEventName | undefined {\n const attributes = object(parsed.payload.object_attributes);\n const action = string(attributes?.action)?.toLowerCase();\n if (parsed.event === 'Issue Hook') {\n if (action === 'open' || action === 'reopen') return 'issueOpened';\n if (action === 'close') return 'issueClosed';\n return 'issueUpdated';\n }\n if (parsed.event === 'Note Hook') {\n const noteableType = string(attributes?.noteable_type);\n if (noteableType === 'Issue') return 'issueCommented';\n if (noteableType === 'MergeRequest') return 'mergeRequestCommented';\n return;\n }\n if (parsed.event !== 'Merge Request Hook') return;\n if (action === 'open' || action === 'reopen') return 'mergeRequestOpened';\n if (action === 'merge') return 'mergeRequestMerged';\n if (action === 'close') return 'mergeRequestClosed';\n return 'mergeRequestUpdated';\n}\n\nfunction normalizeHost(host: string): string {\n return host.trim().toLowerCase().replace(/\\.$/, '');\n}\n\nfunction hostFromPayload(parsed: ParsedGitLabWebhook): string | undefined {\n const declaredHost = parsed.instanceHost ? normalizeHost(parsed.instanceHost) : undefined;\n const projectUrl = string(object(parsed.payload.project)?.web_url);\n if (!projectUrl) return declaredHost;\n try {\n const payloadHost = normalizeHost(new URL(projectUrl).host);\n if (declaredHost && declaredHost !== payloadHost) return undefined;\n return payloadHost;\n } catch {\n return undefined;\n }\n}\n\nfunction mergeRequestSourceKey(host: string, projectId: number, mergeRequestIid: number): string {\n return `gitlab-pr:${Buffer.from(JSON.stringify({ version: 1, host, projectId, mergeRequestIid }), 'utf8').toString('base64url')}`;\n}\n\nasync function withRuleTimeout<T>(promise: Promise<T>): Promise<T> {\n let timeout: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n promise,\n new Promise<never>((_, reject) => {\n timeout = setTimeout(() => reject(new Error('FACTORY_RULE_TIMEOUT')), RULE_TIMEOUT_MS);\n }),\n ]);\n } finally {\n if (timeout) clearTimeout(timeout);\n }\n}\n\nconst MAX_STALE_RULE_ATTEMPTS = 3;\n\nexport interface GitLabRulesIntegration {\n readonly rules: GitLabEventRules;\n resolveActiveConnectionForHost?(storedConnectionId: string, host: string): Promise<string>;\n getProjectMemberAccessLevel(connectionId: string, projectId: string, username: string): Promise<number | undefined>;\n getWorkItemAuthorUsername(\n connectionId: string,\n projectId: string,\n kind: 'issue' | 'merge_request',\n iid: number,\n ): Promise<string | undefined>;\n}\n\ninterface GitLabRulesOptions {\n gitlab: GitLabRulesIntegration;\n sourceControl: SourceControlStorageHandle;\n projects: FactoryProjectsStorage;\n storage: WorkItemsStorage;\n intake: IntakeStorage;\n configVersion: string;\n boards: BoardRegistry;\n}\n\ninterface ProjectTarget {\n target: ExternalRepositoryProjectTarget;\n connectionIds: string[];\n}\n\nexport class GitLabRules {\n constructor(private readonly options: GitLabRulesOptions) {}\n\n async ingest(parsed: ParsedGitLabWebhook): Promise<{ status: IngressStatus }> {\n const event = eventName(parsed);\n const project = object(parsed.payload.project);\n const projectId = number(project?.id);\n const projectPath = string(project?.path_with_namespace);\n const host = hostFromPayload(parsed);\n const username = string(parsed.payload.user_username) ?? string(object(parsed.payload.user)?.username);\n if (!event || !projectId || !projectPath || !host || !username) return { status: 'ignored' };\n\n const targets = await this.#targets(String(projectId), host);\n if (targets.length === 0) return { status: 'ignored' };\n const results: Array<{ status: IngressStatus }> = [];\n for (const target of targets) {\n results.push(await this.#ingestProject(parsed, event, projectId, projectPath, host, username, target));\n }\n if (results.some(result => result.status === 'committed')) return { status: 'committed' };\n if (results.some(result => result.status === 'replayed')) return { status: 'replayed' };\n return results[0] ?? { status: 'ignored' };\n }\n\n async #targets(repositoryExternalId: string, host: string): Promise<ProjectTarget[]> {\n const grouped = new Map<string, ProjectTarget>();\n const keys = (await this.options.sourceControl.projectRepositories.listConfiguredExternalKeys()).filter(\n key => key.repositoryExternalId === repositoryExternalId,\n );\n for (const key of keys) {\n const targets = await this.options.sourceControl.projectRepositories.listByExternalRepository({\n installationExternalId: key.installationExternalId,\n repositoryExternalId,\n });\n for (const target of targets) {\n const installation = await this.options.sourceControl.installations.findByExternalId({\n orgId: target.orgId,\n externalId: key.installationExternalId,\n });\n const installationHost = string(installation?.providerMetadata.host);\n if (!installationHost || normalizeHost(installationHost) !== normalizeHost(host)) continue;\n const id = `${target.orgId}:${target.factoryProjectId}`;\n const current = grouped.get(id);\n if (current) {\n if (!current.connectionIds.includes(key.installationExternalId)) {\n current.connectionIds.push(key.installationExternalId);\n }\n } else {\n grouped.set(id, { target, connectionIds: [key.installationExternalId] });\n }\n }\n }\n return [...grouped.values()];\n }\n\n async #ingestProject(\n parsed: ParsedGitLabWebhook,\n event: FactoryGitLabEventName,\n projectId: number,\n projectPath: string,\n host: string,\n username: string,\n target: ProjectTarget,\n ): Promise<{ status: IngressStatus }> {\n const factoryProject = await this.options.projects.get({\n orgId: target.target.orgId,\n id: target.target.factoryProjectId,\n });\n if (!factoryProject) return { status: 'missing' };\n\n const sourceId = encodeSourceId({ host, projectId: String(projectId) });\n const config = await this.options.intake.getConfig({\n orgId: target.target.orgId,\n integrationIds: ['gitlab'],\n });\n if (!config.gitlab?.enabled || !config.gitlab.sourceIds?.includes(sourceId)) return { status: 'ignored' };\n const binding = (\n await this.options.intake.listBindings({ orgId: target.target.orgId, integrationId: 'gitlab' })\n ).find(\n candidate => candidate.sourceId === sourceId && candidate.factoryProjectId === target.target.factoryProjectId,\n );\n if (!binding) return { status: 'ignored' };\n const board = this.options.boards.get(binding.board ?? 'work');\n if (!board) return { status: 'ignored' };\n\n const connectionIds = [\n ...new Set(\n await Promise.all(\n target.connectionIds.map(\n connectionId => this.options.gitlab.resolveActiveConnectionForHost?.(connectionId, host) ?? connectionId,\n ),\n ),\n ),\n ];\n\n const attributes = object(parsed.payload.object_attributes);\n const issue = parsed.event === 'Note Hook' ? object(parsed.payload.issue) : attributes;\n const mergeRequest = parsed.event === 'Note Hook' ? object(parsed.payload.merge_request) : attributes;\n const issueIid = number(issue?.iid);\n const mergeRequestIid = number(mergeRequest?.iid);\n const issueSourceKey = issueIid\n ? encodeIssueReference({ host, projectId: String(projectId), issueIid })\n : undefined;\n const mergeRequestKey = mergeRequestIid ? mergeRequestSourceKey(host, projectId, mergeRequestIid) : undefined;\n const items = await this.options.storage.list({\n orgId: target.target.orgId,\n factoryProjectId: target.target.factoryProjectId,\n });\n const issueItem = issueSourceKey\n ? items.find(\n item => item.externalSource?.integrationId === 'gitlab' && item.externalSource.externalId === issueSourceKey,\n )\n : undefined;\n const reviewItem = mergeRequestKey\n ? items.find(\n item => item.externalSource?.integrationId === 'gitlab' && item.externalSource.externalId === mergeRequestKey,\n )\n : undefined;\n const headBranch = string(mergeRequest?.source_branch) ?? '';\n const authoringItem = headBranch\n ? items.find(\n item =>\n item.externalSource?.type !== 'pull-request' &&\n Object.values(item.sessions).some(session => session.branch === headBranch),\n )\n : undefined;\n const actorTrusted = await this.#trusted(connectionIds, String(projectId), username);\n const eventUserId = number(object(parsed.payload.user)?.id) ?? number(parsed.payload.user_id);\n const resolveAuthor = async (\n item: Record<string, unknown> | undefined,\n kind: 'issue' | 'merge_request',\n iid: number | undefined,\n ): Promise<string | undefined> => {\n if (!item || !iid) return undefined;\n const embedded = string(object(item.author)?.username);\n if (embedded) return embedded;\n if (eventUserId && eventUserId === number(item.author_id)) return username;\n for (const connectionId of connectionIds) {\n try {\n const resolved = await this.options.gitlab.getWorkItemAuthorUsername(\n connectionId,\n String(projectId),\n kind,\n iid,\n );\n if (resolved) return resolved;\n } catch {\n // Another active credential may cover this project; otherwise leave author trust unset.\n }\n }\n return undefined;\n };\n const [issueAuthor, mergeRequestAuthor] = await Promise.all([\n resolveAuthor(issue, 'issue', issueIid),\n resolveAuthor(mergeRequest, 'merge_request', mergeRequestIid),\n ]);\n const authorTrusted = async (author: string | undefined): Promise<boolean> => {\n if (!author) return false;\n return author === username ? actorTrusted : this.#trusted(connectionIds, String(projectId), author);\n };\n const [issueAuthorTrusted, mergeRequestAuthorTrusted] = await Promise.all([\n authorTrusted(issueAuthor),\n authorTrusted(mergeRequestAuthor),\n ]);\n const actor: FactoryRuleActor = {\n type: 'gitlab',\n username,\n trusted: actorTrusted,\n factoryAuthored: false,\n };\n\n // A push moves the head a recorded review verdict was given on. Recording\n // the new head lets the verdict badge show that it is outdated, while the\n // verdict itself stays so the next pass still runs as a re-review. It is\n // recorded before the push is evaluated: the dispatcher can apply the\n // re-review transition as soon as it commits, and the pass it starts names\n // the head it covers, which must be the pushed one.\n const lastCommit = object(mergeRequest?.last_commit);\n const headSha = string(lastCommit?.id)?.toLowerCase();\n // Without an update time the commit time still orders the push; dropping\n // the write would leave the pass naming the head the last verdict covered.\n const headAt = timestamp(mergeRequest?.updated_at) ?? timestamp(lastCommit?.timestamp);\n let pushedReviewItem = reviewItem;\n if (event === 'mergeRequestUpdated' && headSha && reviewItem) {\n const parent = items.find(item => item.id === reviewItem.parentWorkItemId);\n // The authoring Work item mirrors the verdict of the merge request it has\n // out; a push to an older one must not mark its successor's verdict.\n await recordPullRequestHead(this.options.storage, {\n orgId: reviewItem.orgId,\n id: reviewItem.id,\n headSha,\n headAt,\n });\n if (parent) {\n await recordPullRequestHead(this.options.storage, {\n orgId: parent.orgId,\n id: parent.id,\n headSha,\n headAt,\n ownedPullRequestNumber: mergeRequestIid,\n });\n }\n pushedReviewItem = (await this.options.storage.get({ orgId: reviewItem.orgId, id: reviewItem.id })) ?? reviewItem;\n }\n const candidates: Array<WorkItemRow | undefined> =\n event === 'mergeRequestMerged'\n ? [...new Map([reviewItem, authoringItem].filter(Boolean).map(item => [item!.id, item!])).values()]\n : event === 'mergeRequestCommented'\n ? [authoringItem ?? reviewItem]\n : event === 'mergeRequestOpened'\n ? [authoringItem, ...(authoringItem ? [authoringItem] : [])]\n : event.startsWith('mergeRequest')\n ? [pushedReviewItem ?? authoringItem]\n : [issueItem];\n const results = [];\n for (const [index, item] of candidates.entries()) {\n results.push(\n await this.#evaluate({\n parsed,\n event,\n projectId,\n projectPath,\n host,\n actor,\n orgId: target.target.orgId,\n factoryProjectId: target.target.factoryProjectId,\n factoryProject,\n board: { board: board.id, initialPhase: board.initialPhase },\n issue,\n issueAuthor,\n issueAuthorTrusted,\n issueIid,\n issueSourceKey,\n mergeRequest,\n mergeRequestAuthor,\n mergeRequestAuthorTrusted,\n mergeRequestIid,\n mergeRequestKey,\n factoryAuthored: Boolean(authoringItem),\n item,\n ingressIdentity: `gitlab:${projectId}:${parsed.deliveryId}${index ? `:${item?.id ?? index}` : ''}`,\n // A merge request opening concerns two cards: its own Review card,\n // which the integration handler files against the authoring item,\n // and that authoring item, which its board may react to. The second\n // evaluation runs only the board handler, under an id-suffixed\n // identity so it is not dropped as a replay of the first.\n ...(event === 'mergeRequestOpened' ? { mergeRequestOpenedScope: index ? ('board' as const) : ('intake' as const) } : {}),\n }),\n );\n }\n // The authoring Work item records which merge request it has out, so the\n // Work transition policy keeps an agent from closing the work while it is\n // open. Sessions carry no repository, so a branch shared by two Work cards\n // is ambiguous and records nothing.\n const branchOwners = headBranch\n ? items.filter(\n item =>\n item.externalSource?.type !== 'pull-request' &&\n Object.values(item.sessions).some(session => session.branch === headBranch),\n )\n : [];\n if (authoringItem && mergeRequestIid && branchOwners.length === 1) {\n await this.#trackOpenMergeRequest({\n orgId: authoringItem.orgId,\n id: authoringItem.id,\n event,\n mergeRequestIid,\n eventAt: timestamp(mergeRequest?.updated_at),\n opened: string(mergeRequest?.state) === 'opened' && (actorTrusted || mergeRequestAuthorTrusted),\n headSha: string(object(mergeRequest?.last_commit)?.id)?.toLowerCase(),\n });\n }\n if (results.some(result => result.status === 'committed')) return { status: 'committed' };\n if (results.some(result => result.status === 'replayed')) return { status: 'replayed' };\n return results[0] ?? { status: 'ignored' };\n }\n\n /**\n * Records or clears the merge request a Work card has out. Each merge\n * request's last applied event time is kept on the card, so an opening that\n * is older than a close already applied (a replay, or a close delivered\n * first) is ignored, while a genuine reopen is newer and records it again.\n */\n async #trackOpenMergeRequest(input: {\n orgId: string;\n id: string;\n event: string;\n mergeRequestIid: number;\n eventAt: number | undefined;\n opened: boolean;\n headSha: string | undefined;\n }): Promise<void> {\n const settling = input.event === 'mergeRequestMerged' || input.event === 'mergeRequestClosed';\n if (input.event !== 'mergeRequestOpened' && !settling) return;\n const key = String(input.mergeRequestIid);\n for (let attempt = 0; attempt < 3; attempt++) {\n const item = await this.options.storage.get({ orgId: input.orgId, id: input.id });\n if (!item) return;\n const events = object(item.metadata?.mergeRequestEvents) ?? {};\n const last = object(events[key]);\n const lastAt = number(last?.at);\n if (input.eventAt !== undefined && lastAt !== undefined && lastAt >= input.eventAt) return;\n // Without a timestamp, only an opening that no close has settled is trusted.\n if (input.eventAt === undefined && !settling && last?.open === false) return;\n if (!settling && !input.opened) return;\n const recorded = item.metadata?.openPullRequestNumber;\n const patch: Record<string, unknown> = {\n mergeRequestEvents: { ...events, [key]: { at: input.eventAt ?? lastAt ?? null, open: !settling } },\n };\n if (!settling) {\n // A head recorded for an earlier merge request must not mark this one's\n // verdict outdated. A replayed opening of the merge request already\n // recorded keeps the head its pushes left.\n if (recorded !== input.mergeRequestIid) {\n patch.pullRequestHeadSha = input.headSha ?? null;\n patch.pullRequestHeadAt = input.eventAt ?? null;\n }\n patch.openPullRequestNumber = input.mergeRequestIid;\n } else if (recorded === input.mergeRequestIid) {\n patch.openPullRequestNumber = null;\n if (input.event === 'mergeRequestMerged' && typeof item.metadata?.reviewVerdict === 'string') {\n patch.reviewVerdict = null;\n }\n }\n try {\n await this.options.storage.update({\n orgId: item.orgId,\n id: item.id,\n userId: 'factory-rule-dispatcher',\n expectedRevision: item.revision,\n patch: { metadata: patch },\n });\n return;\n } catch (error) {\n if (!(error instanceof WorkItemUpdateConflictError)) throw error;\n }\n }\n }\n\n async #trusted(connectionIds: string[], projectId: string, username: string): Promise<boolean> {\n for (const connectionId of connectionIds) {\n try {\n const level = await this.options.gitlab.getProjectMemberAccessLevel(connectionId, projectId, username);\n if (level !== undefined) return level >= GITLAB_TRUSTED_ACCESS_LEVEL;\n } catch {\n // Try another active credential covering the same canonical project; otherwise fail closed.\n }\n }\n return false;\n }\n\n async #evaluate(\n input: {\n parsed: ParsedGitLabWebhook;\n event: FactoryGitLabEventName;\n projectId: number;\n projectPath: string;\n host: string;\n actor: FactoryRuleActor;\n orgId: string;\n factoryProjectId: string;\n factoryProject: { createdAt: Date };\n board: { board: string; initialPhase: string };\n issue: Record<string, unknown> | undefined;\n issueAuthor: string | undefined;\n issueAuthorTrusted: boolean;\n issueIid: number | undefined;\n issueSourceKey: string | undefined;\n mergeRequest: Record<string, unknown> | undefined;\n mergeRequestAuthor: string | undefined;\n mergeRequestAuthorTrusted: boolean;\n mergeRequestIid: number | undefined;\n mergeRequestKey: string | undefined;\n factoryAuthored: boolean;\n item: WorkItemRow | undefined;\n ingressIdentity: string;\n mergeRequestOpenedScope?: 'intake' | 'board';\n },\n attempt = 1,\n ): Promise<{ status: IngressStatus }> {\n const note = object(input.parsed.payload.object_attributes);\n const issueAuthor = input.issueAuthor;\n const mergeRequestAuthor = input.mergeRequestAuthor;\n const mergeRequestState = string(input.mergeRequest?.state);\n const context: FactoryGitLabRuleContext = {\n tenant: { orgId: input.orgId, projectId: input.factoryProjectId },\n actor: input.actor,\n ingress: { type: 'gitlab', id: input.ingressIdentity },\n cause: `gitlab.${input.event}`,\n causalChain: [],\n configVersion: this.options.configVersion,\n ...(input.item\n ? {\n item: {\n id: input.item.id,\n source:\n input.item.externalSource?.type === 'pull-request'\n ? ('gitlab-pr' as const)\n : input.item.externalSource?.integrationId === 'gitlab'\n ? ('gitlab-issue' as const)\n : ('manual' as const),\n sourceKey: input.item.externalSource?.externalId ?? null,\n parentWorkItemId: input.item.parentWorkItemId,\n title: input.item.title,\n url: input.item.externalSource?.url ?? null,\n stages: input.item.stages,\n acceptedAt: input.item.acceptedAt,\n metadata: input.item.metadata,\n },\n board: boardForWorkItem(input.item),\n itemRevision: input.item.revision,\n ...withBoardClosure(this.options.boards, input.item),\n ...rulePhaseField(this.options.boards, input.item),\n }\n : {}),\n intake: input.board,\n event: input.event,\n deliveryId: input.parsed.deliveryId,\n factory: { createdAt: input.factoryProject.createdAt.toISOString() },\n repository: { id: input.projectId, fullName: input.projectPath, host: input.host },\n ...(input.issueIid && input.issueSourceKey && string(input.issue?.title)\n ? {\n issue: {\n number: input.issueIid,\n sourceKey: input.issueSourceKey,\n title: string(input.issue?.title)!,\n url:\n string(input.issue?.url) ??\n string(input.issue?.web_url) ??\n `https://${input.host}/${input.projectPath}/-/issues/${input.issueIid}`,\n ...(string(input.issue?.created_at) ? { createdAt: string(input.issue?.created_at) } : {}),\n ...(string(input.issue?.updated_at) ? { updatedAt: string(input.issue?.updated_at) } : {}),\n assignees: usernames(input.parsed.payload.assignees ?? input.issue?.assignees),\n labels: labelNames(input.issue?.labels ?? input.parsed.payload.labels),\n labelColors: labelColors(input.issue?.labels ?? input.parsed.payload.labels),\n state: string(input.issue?.state) === 'closed' ? ('closed' as const) : ('open' as const),\n ...(issueAuthor ? { author: issueAuthor } : {}),\n authorTrusted: input.issueAuthorTrusted,\n },\n }\n : {}),\n ...(input.parsed.event === 'Note Hook' && number(note?.id)\n ? {\n note: {\n id: number(note?.id)!,\n ...(string(note?.note) ? { body: string(note?.note) } : {}),\n ...(string(note?.url) ? { url: string(note?.url) } : {}),\n ...(string(object(input.parsed.payload.user)?.username)\n ? { author: string(object(input.parsed.payload.user)?.username) }\n : {}),\n ...(string(note?.created_at) ? { createdAt: string(note?.created_at) } : {}),\n ...(string(note?.updated_at) ? { updatedAt: string(note?.updated_at) } : {}),\n },\n }\n : {}),\n ...(input.mergeRequestIid && input.mergeRequestKey && string(input.mergeRequest?.title)\n ? {\n mergeRequest: {\n number: input.mergeRequestIid,\n sourceKey: input.mergeRequestKey,\n title: string(input.mergeRequest?.title)!,\n url:\n string(input.mergeRequest?.url) ??\n string(input.mergeRequest?.web_url) ??\n `https://${input.host}/${input.projectPath}/-/merge_requests/${input.mergeRequestIid}`,\n ...(string(input.mergeRequest?.created_at) ? { createdAt: string(input.mergeRequest?.created_at) } : {}),\n state:\n mergeRequestState === 'closed' || mergeRequestState === 'merged'\n ? ('closed' as const)\n : ('open' as const),\n draft: boolean(input.mergeRequest?.draft) ?? boolean(input.mergeRequest?.work_in_progress) ?? false,\n merged: input.event === 'mergeRequestMerged' || mergeRequestState === 'merged',\n assignees: usernames(input.parsed.payload.assignees ?? input.mergeRequest?.assignees),\n reviewers: usernames(input.parsed.payload.reviewers ?? input.mergeRequest?.reviewers),\n labels: labelNames(input.mergeRequest?.labels ?? input.parsed.payload.labels),\n labelColors: labelColors(input.mergeRequest?.labels ?? input.parsed.payload.labels),\n ...(mergeRequestAuthor ? { author: mergeRequestAuthor } : {}),\n authorTrusted: input.mergeRequestAuthorTrusted,\n factoryAuthored: input.factoryAuthored,\n headBranch: string(input.mergeRequest?.source_branch) ?? '',\n baseBranch: string(input.mergeRequest?.target_branch) ?? '',\n },\n }\n : {}),\n };\n const boardRules = boardEventHandlers(\n this.options.boards,\n input.mergeRequestOpenedScope === 'intake' ? undefined : eventBoardId(context),\n 'gitlab',\n );\n const rule =\n input.mergeRequestOpenedScope === 'board'\n ? hasExplicitRuleOverride(this.options.gitlab.rules, input.event)\n ? undefined\n : boardRules?.mergeRequestOpened\n : selectEventRule(this.options.gitlab.rules, boardRules, input.event);\n // Without a board handler the authoring card has nothing to answer; skip\n // recording an empty evaluation for it.\n if (input.mergeRequestOpenedScope === 'board' && !rule) return { status: 'ignored' };\n let decision: FactoryRuleDecision | void;\n let decisions: Record<string, unknown>[] = [];\n let outcome: { status: 'accepted' | 'rejected'; code?: string; reason?: string } = { status: 'accepted' };\n try {\n decision = rule ? await withRuleTimeout(Promise.resolve(rule(Object.freeze(context)))) : undefined;\n if (decision?.type === 'reject') {\n outcome = { status: 'rejected', code: decision.code, reason: decision.reason };\n } else if (decision) {\n decisions = validateFactoryRuleDecisions([decision]).map(entry => {\n assertFactoryDecisionTarget(\n entry,\n this.options.boards,\n input.item ? boardForWorkItem(input.item) : undefined,\n );\n return { ...entry };\n });\n }\n } catch (error) {\n const timedOut = error instanceof Error && error.message === 'FACTORY_RULE_TIMEOUT';\n outcome = {\n status: 'rejected',\n code: timedOut ? 'timeout' : 'rule_error',\n reason: timedOut\n ? 'Factory rule evaluation timed out.'\n : error instanceof Error\n ? error.message.slice(0, 2_000)\n : 'Factory GitLab rule failed.',\n };\n }\n\n const committed = await this.options.storage.commitRuleEvaluation({\n orgId: input.orgId,\n factoryProjectId: input.factoryProjectId,\n workItemId: input.item?.id ?? null,\n ingress: { identity: input.ingressIdentity, triggerType: `gitlab.${input.event}` },\n configVersion: this.options.configVersion,\n expectedRevision: input.item?.revision ?? null,\n actor: { ...input.actor },\n outcome,\n decisions,\n causalChain: [],\n now: new Date(),\n });\n if (committed.status === 'stale') {\n // Nothing was persisted: re-read the item and evaluate again at its fresh revision.\n if (attempt >= MAX_STALE_RULE_ATTEMPTS) {\n throw new Error(`Factory GitLab rule evaluation for ${input.ingressIdentity} kept losing revision races.`);\n }\n const fresh = input.item ? await this.options.storage.get({ orgId: input.orgId, id: input.item.id }) : null;\n if (input.item && !fresh) return { status: 'missing' };\n return this.#evaluate({ ...input, item: fresh ?? undefined }, attempt + 1);\n }\n return { status: committed.status };\n }\n}\n\nexport function attachGitLabRules(\n gitlab: GitLabRulesIntegration,\n context: IntegrationContext,\n): ((event: ParsedGitLabWebhook) => Promise<unknown>) | undefined {\n if (!context.runtime) return undefined;\n const rules = new GitLabRules({\n gitlab,\n sourceControl: context.storage.sourceControl,\n projects: context.storage.projects,\n storage: context.runtime.workItems,\n intake: context.storage.intake,\n configVersion: context.runtime.configVersion,\n boards: context.runtime.boards,\n });\n return event => rules.ingest(event);\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-subscriptions.d.ts","sourceRoot":"","sources":["../../../src/integrations/gitlab/session-subscriptions.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAWnE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAG9D,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,oBAAoB,CAAC;AAiCzE,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAMhH;
|
|
1
|
+
{"version":3,"file":"session-subscriptions.d.ts","sourceRoot":"","sources":["../../../src/integrations/gitlab/session-subscriptions.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAWnE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAG9D,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,oBAAoB,CAAC;AAiCzE,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAMhH;AAyHD,wBAAsB,qCAAqC,CACzD,cAAc,EAAE,cAAc,EAC9B,YAAY,EAAE,MAAM,GAAG,MAAM,EAC7B,MAAM,EAAE,8BAA8B,EACtC,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAmB7B;AAED,wBAAsB,yCAAyC,CAC7D,cAAc,EAAE,cAAc,EAC9B,YAAY,EAAE,MAAM,GAAG,MAAM,EAC7B,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,MAAM,CAAC,CAKjB;AAED,wBAAgB,6BAA6B,CAAC,cAAc,EAAE,cAAc,EAAE,MAAM,EAAE,qBAAqB;;;;;;;;;;EAyB1G;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,GAAG,MAAM,GAAG,SAAS,CAKrB"}
|
|
@@ -51,7 +51,7 @@ async function resolveSessionTarget(requestContext, gitlab) {
|
|
|
51
51
|
if (!context || !context.threadId || !context.getState().factoryProjectId || !orgId || !userId) throw new Error("GitLab subscriptions require an authenticated repository session with an active thread.");
|
|
52
52
|
const storage = gitlab.sourceControlStorage;
|
|
53
53
|
if (!storage) throw new Error("GitLab source control is unavailable.");
|
|
54
|
-
const projectRepositoryId = context.getState().projectRepositoryId ?? (await storage.sessions.getBySessionId(context.
|
|
54
|
+
const projectRepositoryId = context.getState().projectRepositoryId ?? (await storage.sessions.getBySessionId(context.sessionId))?.projectRepositoryId;
|
|
55
55
|
if (!projectRepositoryId) return void 0;
|
|
56
56
|
const projectRepository = await storage.projectRepositories.get({
|
|
57
57
|
orgId,
|
|
@@ -117,7 +117,6 @@ function subscriptionInput(target, iid) {
|
|
|
117
117
|
ownerId: target.context.session.ownerId,
|
|
118
118
|
resourceId: target.connection.factoryProjectId,
|
|
119
119
|
threadId: target.context.threadId,
|
|
120
|
-
sessionScope: target.context.scope,
|
|
121
120
|
source: "explicit-tool",
|
|
122
121
|
subscribedByUserId: target.userId
|
|
123
122
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-subscriptions.js","names":[],"sources":["../../../src/integrations/gitlab/session-subscriptions.ts"],"sourcesContent":["import type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport type { RequestContext } from '@mastra/core/request-context';\nimport { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\n\nimport { getFactoryAuthOrgId, getFactoryAuthUserFromContext, getFactoryAuthUserId } from '../../auth.js';\nimport type {\n ProjectRepository,\n ProjectSourceControlConnection,\n SourceControlInstallation,\n SourceControlRepository,\n} from '../../storage/domains/source-control/base.js';\nimport type { GitLabIntegrationBase } from './integration.js';\nimport { normalizeGitLabHost } from './integration.js';\nimport { subscribeToMergeRequest, unsubscribeFromMergeRequest } from './subscriptions.js';\nimport type { GitLabSignalSubscriptionSource } from './subscriptions.js';\n\ntype RepositorySessionState = { factoryProjectId?: string; projectRepositoryId?: string };\n\nconst MERGE_REQUEST_URL = /^https?:\\/\\/([^/\\s]+)\\/(.+?)\\/-\\/merge_requests\\/(\\d+)\\/?$/i;\n\nfunction sessionUserId(requestContext: RequestContext): string | undefined {\n return getFactoryAuthUserId(getFactoryAuthUserFromContext(requestContext));\n}\n\nfunction sessionOrgId(requestContext: RequestContext): string | undefined {\n return getFactoryAuthOrgId(getFactoryAuthUserFromContext(requestContext));\n}\n\nconst mergeRequestInputSchema = z.object({\n mergeRequest: z.union([z.number().int().positive(), z.string().min(1)]),\n});\n\ninterface SessionTarget {\n context: AgentControllerRequestContext<RepositorySessionState>;\n projectRepository: ProjectRepository;\n connection: ProjectSourceControlConnection;\n installation: SourceControlInstallation;\n repository: SourceControlRepository;\n host: string;\n orgId: string;\n userId: string;\n}\n\nfunction normalizePath(value: string): string {\n return value.replace(/^\\/+|\\/+$/g, '').toLowerCase();\n}\n\nexport function parseMergeRequestUrl(url: string): { host: string; projectPath: string; iid: number } | undefined {\n const match = url.trim().match(MERGE_REQUEST_URL);\n if (!match) return undefined;\n const iid = Number(match[3]);\n if (!Number.isSafeInteger(iid) || iid < 1) return undefined;\n return { host: normalizeGitLabHost(match[1]!), projectPath: match[2]!.replace(/\\/+$/, ''), iid };\n}\n\nfunction parseMergeRequest(value: number | string, target: SessionTarget): number {\n if (typeof value === 'number') return value;\n if (/^\\d+$/.test(value)) return Number(value);\n const parsed = parseMergeRequestUrl(value);\n if (\n !parsed ||\n parsed.host !== target.host ||\n normalizePath(parsed.projectPath) !== normalizePath(target.repository.slug)\n ) {\n throw new Error(`Merge request must belong to ${target.host}/${target.repository.slug}.`);\n }\n return parsed.iid;\n}\n\n/**\n * Whether the current request comes from a session that merge-request\n * subscriptions can ever apply to: an authenticated org user on a factory\n * session with an active thread. Mirrors the GitHub gate; whether the\n * session's repository is GitLab-linked is only known after a storage read,\n * so the entry points verify that and fail loudly for the explicit tools.\n */\nfunction isRepositorySession(requestContext: RequestContext): boolean {\n const context = requestContext.get('controller') as AgentControllerRequestContext<RepositorySessionState> | undefined;\n return Boolean(\n context?.threadId &&\n context.getState().factoryProjectId &&\n sessionOrgId(requestContext) &&\n sessionUserId(requestContext),\n );\n}\n\nasync function resolveSessionTarget(\n requestContext: RequestContext,\n gitlab: GitLabIntegrationBase,\n): Promise<SessionTarget | undefined> {\n const context = requestContext.get('controller') as AgentControllerRequestContext<RepositorySessionState> | undefined;\n const orgId = sessionOrgId(requestContext);\n const userId = sessionUserId(requestContext);\n if (!context || !context.threadId || !context.getState().factoryProjectId || !orgId || !userId) {\n throw new Error('GitLab subscriptions require an authenticated repository session with an active thread.');\n }\n const storage = gitlab.sourceControlStorage;\n if (!storage) throw new Error('GitLab source control is unavailable.');\n\n // Merge requests stay bound to the link the session is filed under: the\n // controller state names it for dispatched sessions, the session row for\n // the rest. A session filed under no link cannot subscribe.\n const projectRepositoryId =\n context.getState().projectRepositoryId ??\n (await storage.sessions.getBySessionId(context.resourceId))?.projectRepositoryId;\n if (!projectRepositoryId) return undefined;\n\n // The handle is scoped to GitLab rows, so a GitHub-linked repository is\n // simply absent here rather than misidentified.\n const projectRepository = await storage.projectRepositories.get({ orgId, id: projectRepositoryId });\n if (!projectRepository) return undefined;\n const connection = await storage.connections.get({ orgId, id: projectRepository.connectionId });\n if (!connection || connection.integrationId !== 'gitlab') return undefined;\n const repository = await storage.repositories.get({ orgId, id: projectRepository.repositoryId });\n if (!repository) throw new Error('Repository not found for this organization.');\n const installation = await storage.installations.get({ orgId, id: connection.installationId });\n if (!installation) throw new Error('Source-control installation not found for this organization.');\n const host = installation.providerMetadata.host;\n if (typeof host !== 'string' || !host) throw new Error('GitLab installation is missing its instance host.');\n return {\n context,\n projectRepository,\n connection,\n installation,\n repository,\n host: normalizeGitLabHost(host),\n orgId,\n userId,\n };\n}\n\nasync function requireGitLabSessionTarget(\n requestContext: RequestContext,\n gitlab: GitLabIntegrationBase,\n): Promise<SessionTarget> {\n const target = await resolveSessionTarget(requestContext, gitlab);\n if (!target) throw new Error('The active session is not backed by a GitLab repository.');\n return target;\n}\n\nasync function verifyMergeRequest(target: SessionTarget, iid: number, gitlab: GitLabIntegrationBase): Promise<void> {\n const repositoryTarget = await gitlab.versionControl.getRepositoryTarget({\n orgId: target.orgId,\n repositoryId: target.repository.id,\n });\n const mergeRequest = await gitlab.versionControl.getPullRequest({ ...repositoryTarget, pullRequestId: String(iid) });\n if (!mergeRequest) throw new Error(`Merge request !${iid} was not found in ${target.repository.slug}.`);\n const parsed = parseMergeRequestUrl(mergeRequest.url);\n if (\n parsed &&\n (parsed.host !== target.host || normalizePath(parsed.projectPath) !== normalizePath(target.repository.slug))\n ) {\n throw new Error('Merge request project does not match the active project repository.');\n }\n}\n\nfunction subscriptionInput(target: SessionTarget, iid: number) {\n return {\n orgId: target.orgId,\n host: target.host,\n projectId: target.repository.externalId,\n projectPath: target.repository.slug,\n projectRepositoryId: target.projectRepository.id,\n installationExternalId: target.installation.externalId,\n changeRequestId: String(iid),\n sessionId: target.context.session.id,\n ownerId: target.context.session.ownerId,\n resourceId: target.connection.factoryProjectId,\n threadId: target.context.threadId!,\n sessionScope: target.context.scope,\n source: 'explicit-tool' as GitLabSignalSubscriptionSource,\n subscribedByUserId: target.userId,\n };\n}\n\nexport async function subscribeCurrentSessionToMergeRequest(\n requestContext: RequestContext,\n mergeRequest: number | string,\n source: GitLabSignalSubscriptionSource,\n gitlab: GitLabIntegrationBase,\n): Promise<number | undefined> {\n // The auto path observes every successful change-request creation in every\n // session, including GitHub-project sessions where a GitLab subscription can\n // never apply. Skip silently there; only the explicit tool should surface\n // \"this session cannot subscribe\" as an error.\n if (source === 'auto-create-change-request') {\n if (!isRepositorySession(requestContext)) return undefined;\n const target = await resolveSessionTarget(requestContext, gitlab);\n if (!target) return undefined;\n const iid = parseMergeRequest(mergeRequest, target);\n await verifyMergeRequest(target, iid, gitlab);\n await subscribeToMergeRequest({ ...subscriptionInput(target, iid), source }, gitlab.integrationStorage);\n return iid;\n }\n const target = await requireGitLabSessionTarget(requestContext, gitlab);\n const iid = parseMergeRequest(mergeRequest, target);\n await verifyMergeRequest(target, iid, gitlab);\n await subscribeToMergeRequest({ ...subscriptionInput(target, iid), source }, gitlab.integrationStorage);\n return iid;\n}\n\nexport async function unsubscribeCurrentSessionFromMergeRequest(\n requestContext: RequestContext,\n mergeRequest: number | string,\n gitlab: GitLabIntegrationBase,\n): Promise<number> {\n const target = await requireGitLabSessionTarget(requestContext, gitlab);\n const iid = parseMergeRequest(mergeRequest, target);\n await unsubscribeFromMergeRequest(subscriptionInput(target, iid), gitlab.integrationStorage);\n return iid;\n}\n\nexport function createGitLabSubscriptionTools(requestContext: RequestContext, gitlab: GitLabIntegrationBase) {\n if (!isRepositorySession(requestContext)) return {};\n\n return {\n gitlab_subscribe_mr: createTool({\n id: 'gitlab_subscribe_mr',\n description:\n 'Subscribe this thread to GitLab merge request activity. You usually do not need this tool: successful source_control_create_change_request calls subscribe automatically. Use it for an existing MR or to recover when automatic subscription did not occur. Closed or merged MRs are unsubscribed automatically. Accepts an MR IID or canonical URL for the active project.',\n inputSchema: mergeRequestInputSchema,\n execute: async ({ mergeRequest }) => {\n const iid = await subscribeCurrentSessionToMergeRequest(requestContext, mergeRequest, 'explicit-tool', gitlab);\n return { subscribed: true, mergeRequestIid: iid };\n },\n }),\n gitlab_unsubscribe_mr: createTool({\n id: 'gitlab_unsubscribe_mr',\n description:\n 'Manually unsubscribe this thread from GitLab merge request activity. You usually do not need this tool because closed or merged MRs are unsubscribed automatically. Use it to stop notifications before then. Accepts an MR IID or canonical URL for the active project.',\n inputSchema: mergeRequestInputSchema,\n execute: async ({ mergeRequest }) => {\n const iid = await unsubscribeCurrentSessionFromMergeRequest(requestContext, mergeRequest, gitlab);\n return { subscribed: false, mergeRequestIid: iid };\n },\n }),\n };\n}\n\n/**\n * The merge request URL a successful provider-neutral change-request creation\n * reported, when that URL is a GitLab one. Anything else is not ours to observe.\n */\nexport function parseCreatedMergeRequest(context: {\n toolName: string;\n input: unknown;\n output?: unknown;\n error?: unknown;\n}): string | undefined {\n if (context.toolName !== 'source_control_create_change_request' || context.error) return undefined;\n const url = (context.output as { url?: unknown } | undefined)?.url;\n if (typeof url !== 'string' || !parseMergeRequestUrl(url)) return undefined;\n return url.trim().replace(/\\/$/, '');\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"session-subscriptions.js","names":[],"sources":["../../../src/integrations/gitlab/session-subscriptions.ts"],"sourcesContent":["import type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport type { RequestContext } from '@mastra/core/request-context';\nimport { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\n\nimport { getFactoryAuthOrgId, getFactoryAuthUserFromContext, getFactoryAuthUserId } from '../../auth.js';\nimport type {\n ProjectRepository,\n ProjectSourceControlConnection,\n SourceControlInstallation,\n SourceControlRepository,\n} from '../../storage/domains/source-control/base.js';\nimport type { GitLabIntegrationBase } from './integration.js';\nimport { normalizeGitLabHost } from './integration.js';\nimport { subscribeToMergeRequest, unsubscribeFromMergeRequest } from './subscriptions.js';\nimport type { GitLabSignalSubscriptionSource } from './subscriptions.js';\n\ntype RepositorySessionState = { factoryProjectId?: string; projectRepositoryId?: string };\n\nconst MERGE_REQUEST_URL = /^https?:\\/\\/([^/\\s]+)\\/(.+?)\\/-\\/merge_requests\\/(\\d+)\\/?$/i;\n\nfunction sessionUserId(requestContext: RequestContext): string | undefined {\n return getFactoryAuthUserId(getFactoryAuthUserFromContext(requestContext));\n}\n\nfunction sessionOrgId(requestContext: RequestContext): string | undefined {\n return getFactoryAuthOrgId(getFactoryAuthUserFromContext(requestContext));\n}\n\nconst mergeRequestInputSchema = z.object({\n mergeRequest: z.union([z.number().int().positive(), z.string().min(1)]),\n});\n\ninterface SessionTarget {\n context: AgentControllerRequestContext<RepositorySessionState>;\n projectRepository: ProjectRepository;\n connection: ProjectSourceControlConnection;\n installation: SourceControlInstallation;\n repository: SourceControlRepository;\n host: string;\n orgId: string;\n userId: string;\n}\n\nfunction normalizePath(value: string): string {\n return value.replace(/^\\/+|\\/+$/g, '').toLowerCase();\n}\n\nexport function parseMergeRequestUrl(url: string): { host: string; projectPath: string; iid: number } | undefined {\n const match = url.trim().match(MERGE_REQUEST_URL);\n if (!match) return undefined;\n const iid = Number(match[3]);\n if (!Number.isSafeInteger(iid) || iid < 1) return undefined;\n return { host: normalizeGitLabHost(match[1]!), projectPath: match[2]!.replace(/\\/+$/, ''), iid };\n}\n\nfunction parseMergeRequest(value: number | string, target: SessionTarget): number {\n if (typeof value === 'number') return value;\n if (/^\\d+$/.test(value)) return Number(value);\n const parsed = parseMergeRequestUrl(value);\n if (\n !parsed ||\n parsed.host !== target.host ||\n normalizePath(parsed.projectPath) !== normalizePath(target.repository.slug)\n ) {\n throw new Error(`Merge request must belong to ${target.host}/${target.repository.slug}.`);\n }\n return parsed.iid;\n}\n\n/**\n * Whether the current request comes from a session that merge-request\n * subscriptions can ever apply to: an authenticated org user on a factory\n * session with an active thread. Mirrors the GitHub gate; whether the\n * session's repository is GitLab-linked is only known after a storage read,\n * so the entry points verify that and fail loudly for the explicit tools.\n */\nfunction isRepositorySession(requestContext: RequestContext): boolean {\n const context = requestContext.get('controller') as AgentControllerRequestContext<RepositorySessionState> | undefined;\n return Boolean(\n context?.threadId &&\n context.getState().factoryProjectId &&\n sessionOrgId(requestContext) &&\n sessionUserId(requestContext),\n );\n}\n\nasync function resolveSessionTarget(\n requestContext: RequestContext,\n gitlab: GitLabIntegrationBase,\n): Promise<SessionTarget | undefined> {\n const context = requestContext.get('controller') as AgentControllerRequestContext<RepositorySessionState> | undefined;\n const orgId = sessionOrgId(requestContext);\n const userId = sessionUserId(requestContext);\n if (!context || !context.threadId || !context.getState().factoryProjectId || !orgId || !userId) {\n throw new Error('GitLab subscriptions require an authenticated repository session with an active thread.');\n }\n const storage = gitlab.sourceControlStorage;\n if (!storage) throw new Error('GitLab source control is unavailable.');\n\n // Merge requests stay bound to the link the session is filed under: the\n // controller state names it for dispatched sessions, the session row for\n // the rest. A session filed under no link cannot subscribe.\n const projectRepositoryId =\n context.getState().projectRepositoryId ??\n (await storage.sessions.getBySessionId(context.sessionId))?.projectRepositoryId;\n if (!projectRepositoryId) return undefined;\n\n // The handle is scoped to GitLab rows, so a GitHub-linked repository is\n // simply absent here rather than misidentified.\n const projectRepository = await storage.projectRepositories.get({ orgId, id: projectRepositoryId });\n if (!projectRepository) return undefined;\n const connection = await storage.connections.get({ orgId, id: projectRepository.connectionId });\n if (!connection || connection.integrationId !== 'gitlab') return undefined;\n const repository = await storage.repositories.get({ orgId, id: projectRepository.repositoryId });\n if (!repository) throw new Error('Repository not found for this organization.');\n const installation = await storage.installations.get({ orgId, id: connection.installationId });\n if (!installation) throw new Error('Source-control installation not found for this organization.');\n const host = installation.providerMetadata.host;\n if (typeof host !== 'string' || !host) throw new Error('GitLab installation is missing its instance host.');\n return {\n context,\n projectRepository,\n connection,\n installation,\n repository,\n host: normalizeGitLabHost(host),\n orgId,\n userId,\n };\n}\n\nasync function requireGitLabSessionTarget(\n requestContext: RequestContext,\n gitlab: GitLabIntegrationBase,\n): Promise<SessionTarget> {\n const target = await resolveSessionTarget(requestContext, gitlab);\n if (!target) throw new Error('The active session is not backed by a GitLab repository.');\n return target;\n}\n\nasync function verifyMergeRequest(target: SessionTarget, iid: number, gitlab: GitLabIntegrationBase): Promise<void> {\n const repositoryTarget = await gitlab.versionControl.getRepositoryTarget({\n orgId: target.orgId,\n repositoryId: target.repository.id,\n });\n const mergeRequest = await gitlab.versionControl.getPullRequest({ ...repositoryTarget, pullRequestId: String(iid) });\n if (!mergeRequest) throw new Error(`Merge request !${iid} was not found in ${target.repository.slug}.`);\n const parsed = parseMergeRequestUrl(mergeRequest.url);\n if (\n parsed &&\n (parsed.host !== target.host || normalizePath(parsed.projectPath) !== normalizePath(target.repository.slug))\n ) {\n throw new Error('Merge request project does not match the active project repository.');\n }\n}\n\nfunction subscriptionInput(target: SessionTarget, iid: number) {\n return {\n orgId: target.orgId,\n host: target.host,\n projectId: target.repository.externalId,\n projectPath: target.repository.slug,\n projectRepositoryId: target.projectRepository.id,\n installationExternalId: target.installation.externalId,\n changeRequestId: String(iid),\n sessionId: target.context.session.id,\n ownerId: target.context.session.ownerId,\n resourceId: target.connection.factoryProjectId,\n threadId: target.context.threadId!,\n source: 'explicit-tool' as GitLabSignalSubscriptionSource,\n subscribedByUserId: target.userId,\n };\n}\n\nexport async function subscribeCurrentSessionToMergeRequest(\n requestContext: RequestContext,\n mergeRequest: number | string,\n source: GitLabSignalSubscriptionSource,\n gitlab: GitLabIntegrationBase,\n): Promise<number | undefined> {\n // The auto path observes every successful change-request creation in every\n // session, including GitHub-project sessions where a GitLab subscription can\n // never apply. Skip silently there; only the explicit tool should surface\n // \"this session cannot subscribe\" as an error.\n if (source === 'auto-create-change-request') {\n if (!isRepositorySession(requestContext)) return undefined;\n const target = await resolveSessionTarget(requestContext, gitlab);\n if (!target) return undefined;\n const iid = parseMergeRequest(mergeRequest, target);\n await verifyMergeRequest(target, iid, gitlab);\n await subscribeToMergeRequest({ ...subscriptionInput(target, iid), source }, gitlab.integrationStorage);\n return iid;\n }\n const target = await requireGitLabSessionTarget(requestContext, gitlab);\n const iid = parseMergeRequest(mergeRequest, target);\n await verifyMergeRequest(target, iid, gitlab);\n await subscribeToMergeRequest({ ...subscriptionInput(target, iid), source }, gitlab.integrationStorage);\n return iid;\n}\n\nexport async function unsubscribeCurrentSessionFromMergeRequest(\n requestContext: RequestContext,\n mergeRequest: number | string,\n gitlab: GitLabIntegrationBase,\n): Promise<number> {\n const target = await requireGitLabSessionTarget(requestContext, gitlab);\n const iid = parseMergeRequest(mergeRequest, target);\n await unsubscribeFromMergeRequest(subscriptionInput(target, iid), gitlab.integrationStorage);\n return iid;\n}\n\nexport function createGitLabSubscriptionTools(requestContext: RequestContext, gitlab: GitLabIntegrationBase) {\n if (!isRepositorySession(requestContext)) return {};\n\n return {\n gitlab_subscribe_mr: createTool({\n id: 'gitlab_subscribe_mr',\n description:\n 'Subscribe this thread to GitLab merge request activity. You usually do not need this tool: successful source_control_create_change_request calls subscribe automatically. Use it for an existing MR or to recover when automatic subscription did not occur. Closed or merged MRs are unsubscribed automatically. Accepts an MR IID or canonical URL for the active project.',\n inputSchema: mergeRequestInputSchema,\n execute: async ({ mergeRequest }) => {\n const iid = await subscribeCurrentSessionToMergeRequest(requestContext, mergeRequest, 'explicit-tool', gitlab);\n return { subscribed: true, mergeRequestIid: iid };\n },\n }),\n gitlab_unsubscribe_mr: createTool({\n id: 'gitlab_unsubscribe_mr',\n description:\n 'Manually unsubscribe this thread from GitLab merge request activity. You usually do not need this tool because closed or merged MRs are unsubscribed automatically. Use it to stop notifications before then. Accepts an MR IID or canonical URL for the active project.',\n inputSchema: mergeRequestInputSchema,\n execute: async ({ mergeRequest }) => {\n const iid = await unsubscribeCurrentSessionFromMergeRequest(requestContext, mergeRequest, gitlab);\n return { subscribed: false, mergeRequestIid: iid };\n },\n }),\n };\n}\n\n/**\n * The merge request URL a successful provider-neutral change-request creation\n * reported, when that URL is a GitLab one. Anything else is not ours to observe.\n */\nexport function parseCreatedMergeRequest(context: {\n toolName: string;\n input: unknown;\n output?: unknown;\n error?: unknown;\n}): string | undefined {\n if (context.toolName !== 'source_control_create_change_request' || context.error) return undefined;\n const url = (context.output as { url?: unknown } | undefined)?.url;\n if (typeof url !== 'string' || !parseMergeRequestUrl(url)) return undefined;\n return url.trim().replace(/\\/$/, '');\n}\n"],"mappings":"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"}
|
|
@@ -31,16 +31,19 @@ export interface SubscribeToMergeRequestInput {
|
|
|
31
31
|
ownerId: string;
|
|
32
32
|
resourceId: string;
|
|
33
33
|
threadId: string;
|
|
34
|
-
sessionScope?: string;
|
|
35
34
|
source: GitLabSignalSubscriptionSource;
|
|
36
35
|
subscribedByUserId?: string;
|
|
37
36
|
}
|
|
38
|
-
|
|
37
|
+
/** A thread's subscriptions, found by the session that owns it or (for older callers) by its resource. */
|
|
38
|
+
export type GitLabThreadSubscriptionTarget = {
|
|
39
|
+
orgId: string;
|
|
40
|
+
sessionId: string;
|
|
41
|
+
threadId: string;
|
|
42
|
+
} | {
|
|
39
43
|
orgId: string;
|
|
40
44
|
resourceId: string;
|
|
41
45
|
threadId: string;
|
|
42
|
-
|
|
43
|
-
}
|
|
46
|
+
};
|
|
44
47
|
export interface MergeRequestSubscriptionTarget {
|
|
45
48
|
orgId: string;
|
|
46
49
|
host: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subscriptions.d.ts","sourceRoot":"","sources":["../../../src/integrations/gitlab/subscriptions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,MAAM,4CAA4C,CAAC;AAEpH,MAAM,MAAM,8BAA8B,GAAG,4BAA4B,GAAG,eAAe,CAAC;AAC5F,MAAM,MAAM,8BAA8B,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE1E,MAAM,WAAW,4BAA4B;IAC3C,uCAAuC;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,SAAS,EAAE,MAAM,CAAC;IAClB,mEAAmE;IACnE,WAAW,EAAE,MAAM,CAAC;IACpB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,+EAA+E;IAC/E,sBAAsB,EAAE,MAAM,CAAC;IAC/B,yBAAyB;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,8BAA8B,CAAC;IACvC,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED,MAAM,MAAM,2BAA2B,GAAG,uBAAuB,CAAC,4BAA4B,CAAC,CAAC;AAChG,MAAM,MAAM,yBAAyB,GAAG,wBAAwB,CAC9D,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACvB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACvB,4BAA4B,CAC7B,CAAC;AAEF,MAAM,WAAW,4BAA4B;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,
|
|
1
|
+
{"version":3,"file":"subscriptions.d.ts","sourceRoot":"","sources":["../../../src/integrations/gitlab/subscriptions.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,7 +9,7 @@ function mergeRequestUrl(host, projectPath, changeRequestId) {
|
|
|
9
9
|
return `https://${normalizeSubscriptionHost(host)}/${projectPath.replace(/^\/+|\/+$/g, "")}/-/merge_requests/${changeRequestId}`;
|
|
10
10
|
}
|
|
11
11
|
function sameSession(row, input) {
|
|
12
|
-
return row.orgId === input.orgId && row.sessionId === input.sessionId && row.resourceId === input.resourceId && row.threadId === input.threadId
|
|
12
|
+
return row.orgId === input.orgId && row.sessionId === input.sessionId && row.resourceId === input.resourceId && row.threadId === input.threadId;
|
|
13
13
|
}
|
|
14
14
|
async function subscribeToMergeRequest(input, storage) {
|
|
15
15
|
const targetKey = mergeRequestTargetKey(input);
|
|
@@ -27,7 +27,6 @@ async function subscribeToMergeRequest(input, storage) {
|
|
|
27
27
|
sessionId: input.sessionId,
|
|
28
28
|
resourceId: input.resourceId,
|
|
29
29
|
threadId: input.threadId,
|
|
30
|
-
sessionScope: input.sessionScope ?? "",
|
|
31
30
|
status: "open",
|
|
32
31
|
data: {
|
|
33
32
|
host: normalizeSubscriptionHost(input.host),
|
|
@@ -47,9 +46,10 @@ async function unsubscribeFromMergeRequest(input, storage) {
|
|
|
47
46
|
await Promise.all(rows.filter((row) => sameSession(row, input)).map((row) => storage.subscriptions.delete(row.id)));
|
|
48
47
|
}
|
|
49
48
|
async function listMergeRequestSubscriptionsForThread(input, storage) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
if ("sessionId" in input) return (await storage.subscriptions.listBySession(input.sessionId)).filter((row) => row.orgId === input.orgId && row.threadId === input.threadId);
|
|
50
|
+
const matching = (await storage.subscriptions.listByThread(input.resourceId, input.threadId)).filter((row) => row.orgId === input.orgId && row.resourceId === input.resourceId && row.threadId === input.threadId);
|
|
51
|
+
if (matching.length > 0) return matching;
|
|
52
|
+
return (await storage.subscriptions.listBySession(input.resourceId)).filter((row) => row.orgId === input.orgId && row.threadId === input.threadId);
|
|
53
53
|
}
|
|
54
54
|
async function listMergeRequestSubscriptions(input, storage) {
|
|
55
55
|
return (await storage.subscriptions.listByTarget(mergeRequestTargetKey(input))).filter((row) => row.orgId === input.orgId && row.status === "open");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subscriptions.js","names":[],"sources":["../../../src/integrations/gitlab/subscriptions.ts"],"sourcesContent":["import type { IntegrationStorageHandle, IntegrationSubscription } from '../../storage/domains/integrations/base.js';\n\nexport type GitLabSignalSubscriptionSource = 'auto-create-change-request' | 'explicit-tool';\nexport type GitLabSignalSubscriptionStatus = 'open' | 'closed' | 'merged';\n\nexport interface GitLabSignalSubscriptionData {\n /** Normalized GitLab instance host. */\n host: string;\n /** Immutable numeric GitLab project id, as stored on the repository row. */\n projectId: string;\n /** Project `path_with_namespace`, used for links and summaries. */\n projectPath: string;\n projectRepositoryId: string;\n /** The stored connection id of the installation the link was created under. */\n installationExternalId: string;\n /** Merge request IID. */\n changeRequestId: string;\n ownerId: string;\n source: GitLabSignalSubscriptionSource;\n subscribedByUserId: string | null;\n}\n\nexport type GitLabSignalSubscriptionRow = IntegrationSubscription<GitLabSignalSubscriptionData>;\nexport type GitLabSubscriptionStorage = IntegrationStorageHandle<\n Record<string, unknown>,\n Record<string, unknown>,\n GitLabSignalSubscriptionData\n>;\n\nexport interface SubscribeToMergeRequestInput {\n orgId: string;\n host: string;\n projectId: string;\n projectPath: string;\n projectRepositoryId: string;\n installationExternalId: string;\n changeRequestId: string;\n sessionId: string;\n ownerId: string;\n resourceId: string;\n threadId: string;\n
|
|
1
|
+
{"version":3,"file":"subscriptions.js","names":[],"sources":["../../../src/integrations/gitlab/subscriptions.ts"],"sourcesContent":["import type { IntegrationStorageHandle, IntegrationSubscription } from '../../storage/domains/integrations/base.js';\n\nexport type GitLabSignalSubscriptionSource = 'auto-create-change-request' | 'explicit-tool';\nexport type GitLabSignalSubscriptionStatus = 'open' | 'closed' | 'merged';\n\nexport interface GitLabSignalSubscriptionData {\n /** Normalized GitLab instance host. */\n host: string;\n /** Immutable numeric GitLab project id, as stored on the repository row. */\n projectId: string;\n /** Project `path_with_namespace`, used for links and summaries. */\n projectPath: string;\n projectRepositoryId: string;\n /** The stored connection id of the installation the link was created under. */\n installationExternalId: string;\n /** Merge request IID. */\n changeRequestId: string;\n ownerId: string;\n source: GitLabSignalSubscriptionSource;\n subscribedByUserId: string | null;\n}\n\nexport type GitLabSignalSubscriptionRow = IntegrationSubscription<GitLabSignalSubscriptionData>;\nexport type GitLabSubscriptionStorage = IntegrationStorageHandle<\n Record<string, unknown>,\n Record<string, unknown>,\n GitLabSignalSubscriptionData\n>;\n\nexport interface SubscribeToMergeRequestInput {\n orgId: string;\n host: string;\n projectId: string;\n projectPath: string;\n projectRepositoryId: string;\n installationExternalId: string;\n changeRequestId: string;\n sessionId: string;\n ownerId: string;\n resourceId: string;\n threadId: string;\n source: GitLabSignalSubscriptionSource;\n subscribedByUserId?: string;\n}\n\n/** A thread's subscriptions, found by the session that owns it or (for older callers) by its resource. */\nexport type GitLabThreadSubscriptionTarget =\n | { orgId: string; sessionId: string; threadId: string }\n | { orgId: string; resourceId: string; threadId: string };\n\nexport interface MergeRequestSubscriptionTarget {\n orgId: string;\n host: string;\n projectId: string;\n changeRequestId: string;\n}\n\nexport type GitLabWebhookMergeRequestTarget = Omit<MergeRequestSubscriptionTarget, 'orgId'>;\n\nexport function normalizeSubscriptionHost(host: string): string {\n return host.trim().toLowerCase().replace(/\\.$/, '');\n}\n\nexport function mergeRequestTargetKey(input: GitLabWebhookMergeRequestTarget): string {\n return `change-request:gitlab:${normalizeSubscriptionHost(input.host)}:${input.projectId}:${input.changeRequestId}`;\n}\n\nexport function mergeRequestUrl(host: string, projectPath: string, changeRequestId: string | number): string {\n return `https://${normalizeSubscriptionHost(host)}/${projectPath.replace(/^\\/+|\\/+$/g, '')}/-/merge_requests/${changeRequestId}`;\n}\n\nfunction sameSession(row: GitLabSignalSubscriptionRow, input: SubscribeToMergeRequestInput): boolean {\n return (\n row.orgId === input.orgId &&\n row.sessionId === input.sessionId &&\n row.resourceId === input.resourceId &&\n row.threadId === input.threadId\n );\n}\n\nexport async function subscribeToMergeRequest(\n input: SubscribeToMergeRequestInput,\n storage: GitLabSubscriptionStorage,\n): Promise<GitLabSignalSubscriptionRow> {\n const targetKey = mergeRequestTargetKey(input);\n const existing = (await storage.subscriptions.listByTarget(targetKey)).find(row => sameSession(row, input));\n if (existing) {\n if (existing.status !== 'open') await storage.subscriptions.updateStatus(existing.id, 'open');\n return { ...existing, status: 'open' };\n }\n\n return storage.subscriptions.create({\n orgId: input.orgId,\n targetKey,\n sessionId: input.sessionId,\n resourceId: input.resourceId,\n threadId: input.threadId,\n status: 'open',\n data: {\n host: normalizeSubscriptionHost(input.host),\n projectId: input.projectId,\n projectPath: input.projectPath,\n projectRepositoryId: input.projectRepositoryId,\n installationExternalId: input.installationExternalId,\n changeRequestId: input.changeRequestId,\n ownerId: input.ownerId,\n source: input.source,\n subscribedByUserId: input.subscribedByUserId ?? null,\n },\n });\n}\n\nexport async function unsubscribeFromMergeRequest(\n input: SubscribeToMergeRequestInput,\n storage: GitLabSubscriptionStorage,\n): Promise<void> {\n const rows = await storage.subscriptions.listByTarget(mergeRequestTargetKey(input));\n await Promise.all(rows.filter(row => sameSession(row, input)).map(row => storage.subscriptions.delete(row.id)));\n}\n\nexport async function listMergeRequestSubscriptionsForThread(\n input: GitLabThreadSubscriptionTarget,\n storage: GitLabSubscriptionStorage,\n): Promise<GitLabSignalSubscriptionRow[]> {\n if ('sessionId' in input) {\n const owned = await storage.subscriptions.listBySession(input.sessionId);\n return owned.filter(row => row.orgId === input.orgId && row.threadId === input.threadId);\n }\n const rows = await storage.subscriptions.listByThread(input.resourceId, input.threadId);\n const matching = rows.filter(\n row =>\n row.orgId === input.orgId &&\n row.resourceId === input.resourceId &&\n row.threadId === input.threadId,\n );\n if (matching.length > 0) return matching;\n // Same as GitHub: a user session addresses itself by its own id while its\n // rows name the owning Factory project, so fall back to the session's rows.\n const owned = await storage.subscriptions.listBySession(input.resourceId);\n return owned.filter(row => row.orgId === input.orgId && row.threadId === input.threadId);\n}\n\nexport async function listMergeRequestSubscriptions(\n input: MergeRequestSubscriptionTarget,\n storage: GitLabSubscriptionStorage,\n): Promise<GitLabSignalSubscriptionRow[]> {\n const rows = await storage.subscriptions.listByTarget(mergeRequestTargetKey(input));\n return rows.filter(row => row.orgId === input.orgId && row.status === 'open');\n}\n\nexport async function listMergeRequestSubscriptionsForWebhook(\n input: GitLabWebhookMergeRequestTarget,\n options: { includeTerminal?: boolean } | undefined,\n storage: GitLabSubscriptionStorage,\n): Promise<GitLabSignalSubscriptionRow[]> {\n const rows = await storage.subscriptions.listByTarget(mergeRequestTargetKey(input));\n return options?.includeTerminal ? rows : rows.filter(row => row.status === 'open');\n}\n\nexport function retireMergeRequestSubscription(\n id: string,\n status: GitLabSignalSubscriptionStatus,\n storage: GitLabSubscriptionStorage,\n): Promise<void> {\n return storage.subscriptions.updateStatus(id, status);\n}\n\n/**\n * Retire every open subscription for a merge request that reached a terminal\n * state outside the webhook path (the reconcile sweep), so the thread's MR chip\n * and the workspace row do not stay `open` on a deployment GitLab cannot reach.\n */\nexport async function retireMergeRequestSubscriptions(\n input: GitLabWebhookMergeRequestTarget & { merged: boolean },\n storage: GitLabSubscriptionStorage,\n): Promise<void> {\n const rows = await storage.subscriptions.listByTarget(mergeRequestTargetKey(input));\n await Promise.all(\n rows\n .filter(row => row.status === 'open')\n .map(row => storage.subscriptions.updateStatus(row.id, input.merged ? 'merged' : 'closed')),\n );\n}\n"],"mappings":";AA2DA,SAAgB,0BAA0B,MAAsB;CAC9D,OAAO,KAAK,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,QAAQ,OAAO,EAAE;AACpD;AAEA,SAAgB,sBAAsB,OAAgD;CACpF,OAAO,yBAAyB,0BAA0B,MAAM,IAAI,EAAE,GAAG,MAAM,UAAU,GAAG,MAAM;AACpG;AAEA,SAAgB,gBAAgB,MAAc,aAAqB,iBAA0C;CAC3G,OAAO,WAAW,0BAA0B,IAAI,EAAE,GAAG,YAAY,QAAQ,cAAc,EAAE,EAAE,oBAAoB;AACjH;AAEA,SAAS,YAAY,KAAkC,OAA8C;CACnG,OACE,IAAI,UAAU,MAAM,SACpB,IAAI,cAAc,MAAM,aACxB,IAAI,eAAe,MAAM,cACzB,IAAI,aAAa,MAAM;AAE3B;AAEA,eAAsB,wBACpB,OACA,SACsC;CACtC,MAAM,YAAY,sBAAsB,KAAK;CAC7C,MAAM,YAAY,MAAM,QAAQ,cAAc,aAAa,SAAS,EAAA,CAAG,MAAK,QAAO,YAAY,KAAK,KAAK,CAAC;CAC1G,IAAI,UAAU;EACZ,IAAI,SAAS,WAAW,QAAQ,MAAM,QAAQ,cAAc,aAAa,SAAS,IAAI,MAAM;EAC5F,OAAO;GAAE,GAAG;GAAU,QAAQ;EAAO;CACvC;CAEA,OAAO,QAAQ,cAAc,OAAO;EAClC,OAAO,MAAM;EACb;EACA,WAAW,MAAM;EACjB,YAAY,MAAM;EAClB,UAAU,MAAM;EAChB,QAAQ;EACR,MAAM;GACJ,MAAM,0BAA0B,MAAM,IAAI;GAC1C,WAAW,MAAM;GACjB,aAAa,MAAM;GACnB,qBAAqB,MAAM;GAC3B,wBAAwB,MAAM;GAC9B,iBAAiB,MAAM;GACvB,SAAS,MAAM;GACf,QAAQ,MAAM;GACd,oBAAoB,MAAM,sBAAsB;EAClD;CACF,CAAC;AACH;AAEA,eAAsB,4BACpB,OACA,SACe;CACf,MAAM,OAAO,MAAM,QAAQ,cAAc,aAAa,sBAAsB,KAAK,CAAC;CAClF,MAAM,QAAQ,IAAI,KAAK,QAAO,QAAO,YAAY,KAAK,KAAK,CAAC,CAAC,CAAC,KAAI,QAAO,QAAQ,cAAc,OAAO,IAAI,EAAE,CAAC,CAAC;AAChH;AAEA,eAAsB,uCACpB,OACA,SACwC;CACxC,IAAI,eAAe,OAEjB,QAAO,MADa,QAAQ,cAAc,cAAc,MAAM,SAAS,EAAA,CAC1D,QAAO,QAAO,IAAI,UAAU,MAAM,SAAS,IAAI,aAAa,MAAM,QAAQ;CAGzF,MAAM,YAAW,MADE,QAAQ,cAAc,aAAa,MAAM,YAAY,MAAM,QAAQ,EAAA,CAChE,QACpB,QACE,IAAI,UAAU,MAAM,SACpB,IAAI,eAAe,MAAM,cACzB,IAAI,aAAa,MAAM,QAC3B;CACA,IAAI,SAAS,SAAS,GAAG,OAAO;CAIhC,QAAO,MADa,QAAQ,cAAc,cAAc,MAAM,UAAU,EAAA,CAC3D,QAAO,QAAO,IAAI,UAAU,MAAM,SAAS,IAAI,aAAa,MAAM,QAAQ;AACzF;AAEA,eAAsB,8BACpB,OACA,SACwC;CAExC,QAAO,MADY,QAAQ,cAAc,aAAa,sBAAsB,KAAK,CAAC,EAAA,CACtE,QAAO,QAAO,IAAI,UAAU,MAAM,SAAS,IAAI,WAAW,MAAM;AAC9E;AAEA,eAAsB,wCACpB,OACA,SACA,SACwC;CACxC,MAAM,OAAO,MAAM,QAAQ,cAAc,aAAa,sBAAsB,KAAK,CAAC;CAClF,OAAO,SAAS,kBAAkB,OAAO,KAAK,QAAO,QAAO,IAAI,WAAW,MAAM;AACnF;AAEA,SAAgB,+BACd,IACA,QACA,SACe;CACf,OAAO,QAAQ,cAAc,aAAa,IAAI,MAAM;AACtD;;;;;;AAOA,eAAsB,gCACpB,OACA,SACe;CACf,MAAM,OAAO,MAAM,QAAQ,cAAc,aAAa,sBAAsB,KAAK,CAAC;CAClF,MAAM,QAAQ,IACZ,KACG,QAAO,QAAO,IAAI,WAAW,MAAM,CAAC,CACpC,KAAI,QAAO,QAAQ,cAAc,aAAa,IAAI,IAAI,MAAM,SAAS,WAAW,QAAQ,CAAC,CAC9F;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version-control.d.ts","sourceRoot":"","sources":["../../../src/integrations/gitlab/version-control.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAC;AAC9E,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"version-control.d.ts","sourceRoot":"","sources":["../../../src/integrations/gitlab/version-control.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAC;AAC9E,OAAO,KAAK,EAOV,cAAc,EACf,MAAM,uCAAuC,CAAC;AAQ/C,OAAO,KAAK,EACV,eAAe,EAQhB,MAAM,UAAU,CAAC;AAElB,MAAM,WAAW,2BAA2B;IAC1C,GAAG,EAAE,eAAe,CAAC;IACrB,UAAU,EAAE,qBAAqB,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qBAAqB,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;CAC/C;AAED,MAAM,WAAW,gCAAgC;IAC/C,oBAAoB,CAAC,UAAU,EAAE,qBAAqB,GAAG,OAAO,CAAC,2BAA2B,CAAC,CAAC;IAC9F,4BAA4B,CAAC,CAC3B,UAAU,EAAE,qBAAqB,EACjC,IAAI,EAAE,MAAM,GAAG,SAAS,GACvB,OAAO,CAAC,2BAA2B,CAAC,CAAC;CACzC;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,gCAAgC,GAAG,cAAc,CAohBhG"}
|
|
@@ -227,6 +227,30 @@ function buildGitLabVersionControl(deps) {
|
|
|
227
227
|
const reference = parseDiscussionNoteId(input.commentId);
|
|
228
228
|
await context.api.resolveMergeRequestDiscussion(input.sourceId, reference.mergeRequestIid, reference.discussionId, input.resolved);
|
|
229
229
|
};
|
|
230
|
+
const publishCommitCheck = async (input) => {
|
|
231
|
+
const context = await deps.contextForConnection(input.connection);
|
|
232
|
+
const body = {
|
|
233
|
+
state: GITLAB_COMMIT_STATUS_STATE[input.status],
|
|
234
|
+
name: input.name,
|
|
235
|
+
...input.ref !== void 0 ? { ref: input.ref } : {},
|
|
236
|
+
description: input.title.slice(0, GITLAB_COMMIT_STATUS_DESCRIPTION_LIMIT)
|
|
237
|
+
};
|
|
238
|
+
let status;
|
|
239
|
+
for (let attempt = 1;; attempt++) try {
|
|
240
|
+
status = await context.api.setCommitStatus(input.sourceId, input.headSha, body);
|
|
241
|
+
break;
|
|
242
|
+
} catch (error) {
|
|
243
|
+
if (attempt >= 3 || error?.status !== 409) throw error;
|
|
244
|
+
await new Promise((resolve) => setTimeout(resolve, 250 * attempt));
|
|
245
|
+
}
|
|
246
|
+
return {
|
|
247
|
+
id: String(status.id),
|
|
248
|
+
name: status.name,
|
|
249
|
+
headSha: status.sha,
|
|
250
|
+
status: input.status,
|
|
251
|
+
url: status.target_url
|
|
252
|
+
};
|
|
253
|
+
};
|
|
230
254
|
const listRequestedReviewers = async (input) => {
|
|
231
255
|
return toRequestedReviewers(await (await deps.contextForConnection(input.connection)).api.getMergeRequest(input.sourceId, requirePositiveId(input.pullRequestId, "merge request")));
|
|
232
256
|
};
|
|
@@ -354,9 +378,19 @@ function buildGitLabVersionControl(deps) {
|
|
|
354
378
|
deleteReviewComment,
|
|
355
379
|
listRequestedReviewers,
|
|
356
380
|
requestReviewers,
|
|
357
|
-
removeRequestedReviewers
|
|
358
|
-
|
|
359
|
-
}
|
|
381
|
+
removeRequestedReviewers,
|
|
382
|
+
publishCommitCheck
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
const GITLAB_COMMIT_STATUS_DESCRIPTION_LIMIT = 255;
|
|
386
|
+
const GITLAB_COMMIT_STATUS_STATE = {
|
|
387
|
+
"in-progress": "running",
|
|
388
|
+
success: "success",
|
|
389
|
+
failure: "failed",
|
|
390
|
+
neutral: "skipped",
|
|
391
|
+
cancelled: "canceled",
|
|
392
|
+
skipped: "skipped"
|
|
393
|
+
};
|
|
360
394
|
async function submitReviewAction(deps, input) {
|
|
361
395
|
const context = await deps.contextForConnection(input.connection);
|
|
362
396
|
const mergeRequestIid = requirePositiveId(input.pullRequestId, "merge request");
|