@shipfox/api-runners 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +2 -0
- package/.turbo/turbo-type$colon$emit.log +1 -0
- package/.turbo/turbo-type.log +1 -0
- package/CHANGELOG.md +187 -0
- package/LICENSE +21 -0
- package/dist/config.d.ts +30 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +208 -0
- package/dist/config.js.map +1 -0
- package/dist/core/demand.d.ts +33 -0
- package/dist/core/demand.d.ts.map +1 -0
- package/dist/core/demand.js +169 -0
- package/dist/core/demand.js.map +1 -0
- package/dist/core/entities/ephemeral-registration-token.d.ts +14 -0
- package/dist/core/entities/ephemeral-registration-token.d.ts.map +1 -0
- package/dist/core/entities/ephemeral-registration-token.js +3 -0
- package/dist/core/entities/ephemeral-registration-token.js.map +1 -0
- package/dist/core/entities/manual-registration-token.d.ts +12 -0
- package/dist/core/entities/manual-registration-token.d.ts.map +1 -0
- package/dist/core/entities/manual-registration-token.js +3 -0
- package/dist/core/entities/manual-registration-token.js.map +1 -0
- package/dist/core/entities/provisioned-runner.d.ts +24 -0
- package/dist/core/entities/provisioned-runner.d.ts.map +1 -0
- package/dist/core/entities/provisioned-runner.js +3 -0
- package/dist/core/entities/provisioned-runner.js.map +1 -0
- package/dist/core/entities/provisioner-token.d.ts +18 -0
- package/dist/core/entities/provisioner-token.d.ts.map +1 -0
- package/dist/core/entities/provisioner-token.js +3 -0
- package/dist/core/entities/provisioner-token.js.map +1 -0
- package/dist/core/entities/reservation.d.ts +10 -0
- package/dist/core/entities/reservation.d.ts.map +1 -0
- package/dist/core/entities/reservation.js +3 -0
- package/dist/core/entities/reservation.js.map +1 -0
- package/dist/core/entities/runner-session.d.ts +18 -0
- package/dist/core/entities/runner-session.d.ts.map +1 -0
- package/dist/core/entities/runner-session.js +3 -0
- package/dist/core/entities/runner-session.js.map +1 -0
- package/dist/core/ephemeral-registration-tokens.d.ts +31 -0
- package/dist/core/ephemeral-registration-tokens.d.ts.map +1 -0
- package/dist/core/ephemeral-registration-tokens.js +61 -0
- package/dist/core/ephemeral-registration-tokens.js.map +1 -0
- package/dist/core/errors.d.ts +62 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +92 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/index.d.ts +14 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +10 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/job-executions.d.ts +14 -0
- package/dist/core/job-executions.d.ts.map +1 -0
- package/dist/core/job-executions.js +37 -0
- package/dist/core/job-executions.js.map +1 -0
- package/dist/core/maintenance-policy.d.ts +2 -0
- package/dist/core/maintenance-policy.d.ts.map +1 -0
- package/dist/core/maintenance-policy.js +3 -0
- package/dist/core/maintenance-policy.js.map +1 -0
- package/dist/core/maintenance.d.ts +33 -0
- package/dist/core/maintenance.d.ts.map +1 -0
- package/dist/core/maintenance.js +55 -0
- package/dist/core/maintenance.js.map +1 -0
- package/dist/core/manual-registration-tokens.d.ts +17 -0
- package/dist/core/manual-registration-tokens.d.ts.map +1 -0
- package/dist/core/manual-registration-tokens.js +28 -0
- package/dist/core/manual-registration-tokens.js.map +1 -0
- package/dist/core/provisioned-runners.d.ts +67 -0
- package/dist/core/provisioned-runners.d.ts.map +1 -0
- package/dist/core/provisioned-runners.js +162 -0
- package/dist/core/provisioned-runners.js.map +1 -0
- package/dist/core/provisioner-tokens.d.ts +20 -0
- package/dist/core/provisioner-tokens.d.ts.map +1 -0
- package/dist/core/provisioner-tokens.js +36 -0
- package/dist/core/provisioner-tokens.js.map +1 -0
- package/dist/core/rate-limit.d.ts +35 -0
- package/dist/core/rate-limit.d.ts.map +1 -0
- package/dist/core/rate-limit.js +74 -0
- package/dist/core/rate-limit.js.map +1 -0
- package/dist/core/runner-sessions.d.ts +26 -0
- package/dist/core/runner-sessions.d.ts.map +1 -0
- package/dist/core/runner-sessions.js +41 -0
- package/dist/core/runner-sessions.js.map +1 -0
- package/dist/core/runner-tool-capabilities.d.ts +24 -0
- package/dist/core/runner-tool-capabilities.d.ts.map +1 -0
- package/dist/core/runner-tool-capabilities.js +43 -0
- package/dist/core/runner-tool-capabilities.js.map +1 -0
- package/dist/db/db.d.ts +4478 -0
- package/dist/db/db.d.ts.map +1 -0
- package/dist/db/db.js +36 -0
- package/dist/db/db.js.map +1 -0
- package/dist/db/ephemeral-registration-tokens.d.ts +47 -0
- package/dist/db/ephemeral-registration-tokens.d.ts.map +1 -0
- package/dist/db/ephemeral-registration-tokens.js +170 -0
- package/dist/db/ephemeral-registration-tokens.js.map +1 -0
- package/dist/db/index.d.ts +19 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +15 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/job-executions.d.ts +128 -0
- package/dist/db/job-executions.d.ts.map +1 -0
- package/dist/db/job-executions.js +354 -0
- package/dist/db/job-executions.js.map +1 -0
- package/dist/db/manual-registration-tokens.d.ts +16 -0
- package/dist/db/manual-registration-tokens.d.ts.map +1 -0
- package/dist/db/manual-registration-tokens.js +37 -0
- package/dist/db/manual-registration-tokens.js.map +1 -0
- package/dist/db/provisioned-runners.d.ts +79 -0
- package/dist/db/provisioned-runners.d.ts.map +1 -0
- package/dist/db/provisioned-runners.js +466 -0
- package/dist/db/provisioned-runners.js.map +1 -0
- package/dist/db/provisioner-tokens.d.ts +26 -0
- package/dist/db/provisioner-tokens.d.ts.map +1 -0
- package/dist/db/provisioner-tokens.js +54 -0
- package/dist/db/provisioner-tokens.js.map +1 -0
- package/dist/db/rate-limits.d.ts +18 -0
- package/dist/db/rate-limits.d.ts.map +1 -0
- package/dist/db/rate-limits.js +44 -0
- package/dist/db/rate-limits.js.map +1 -0
- package/dist/db/reservations.d.ts +48 -0
- package/dist/db/reservations.d.ts.map +1 -0
- package/dist/db/reservations.js +162 -0
- package/dist/db/reservations.js.map +1 -0
- package/dist/db/runner-sessions.d.ts +18 -0
- package/dist/db/runner-sessions.d.ts.map +1 -0
- package/dist/db/runner-sessions.js +38 -0
- package/dist/db/runner-sessions.js.map +1 -0
- package/dist/db/schema/common.d.ts +2 -0
- package/dist/db/schema/common.d.ts.map +1 -0
- package/dist/db/schema/common.js +4 -0
- package/dist/db/schema/common.js.map +1 -0
- package/dist/db/schema/ephemeral-registration-tokens.d.ts +199 -0
- package/dist/db/schema/ephemeral-registration-tokens.d.ts.map +1 -0
- package/dist/db/schema/ephemeral-registration-tokens.js +45 -0
- package/dist/db/schema/ephemeral-registration-tokens.js.map +1 -0
- package/dist/db/schema/manual-registration-tokens.d.ts +165 -0
- package/dist/db/schema/manual-registration-tokens.d.ts.map +1 -0
- package/dist/db/schema/manual-registration-tokens.js +40 -0
- package/dist/db/schema/manual-registration-tokens.js.map +1 -0
- package/dist/db/schema/outbox.d.ts +195 -0
- package/dist/db/schema/outbox.d.ts.map +1 -0
- package/dist/db/schema/outbox.js +5 -0
- package/dist/db/schema/outbox.js.map +1 -0
- package/dist/db/schema/pending-job-executions.d.ts +189 -0
- package/dist/db/schema/pending-job-executions.d.ts.map +1 -0
- package/dist/db/schema/pending-job-executions.js +21 -0
- package/dist/db/schema/pending-job-executions.js.map +1 -0
- package/dist/db/schema/provisioned-runners.d.ts +379 -0
- package/dist/db/schema/provisioned-runners.d.ts.map +1 -0
- package/dist/db/schema/provisioned-runners.js +83 -0
- package/dist/db/schema/provisioned-runners.js.map +1 -0
- package/dist/db/schema/provisioner-tokens.d.ts +216 -0
- package/dist/db/schema/provisioner-tokens.d.ts.map +1 -0
- package/dist/db/schema/provisioner-tokens.js +48 -0
- package/dist/db/schema/provisioner-tokens.js.map +1 -0
- package/dist/db/schema/rate-limits.d.ts +145 -0
- package/dist/db/schema/rate-limits.d.ts.map +1 -0
- package/dist/db/schema/rate-limits.js +25 -0
- package/dist/db/schema/rate-limits.js.map +1 -0
- package/dist/db/schema/reservations.d.ts +157 -0
- package/dist/db/schema/reservations.d.ts.map +1 -0
- package/dist/db/schema/reservations.js +34 -0
- package/dist/db/schema/reservations.js.map +1 -0
- package/dist/db/schema/runner-sessions.d.ts +298 -0
- package/dist/db/schema/runner-sessions.d.ts.map +1 -0
- package/dist/db/schema/runner-sessions.js +61 -0
- package/dist/db/schema/runner-sessions.js.map +1 -0
- package/dist/db/schema/running-job-executions.d.ts +334 -0
- package/dist/db/schema/running-job-executions.d.ts.map +1 -0
- package/dist/db/schema/running-job-executions.js +41 -0
- package/dist/db/schema/running-job-executions.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +56 -0
- package/dist/index.js.map +1 -0
- package/dist/metrics/index.d.ts +3 -0
- package/dist/metrics/index.d.ts.map +1 -0
- package/dist/metrics/index.js +4 -0
- package/dist/metrics/index.js.map +1 -0
- package/dist/metrics/instance.d.ts +34 -0
- package/dist/metrics/instance.d.ts.map +1 -0
- package/dist/metrics/instance.js +60 -0
- package/dist/metrics/instance.js.map +1 -0
- package/dist/metrics/service.d.ts +2 -0
- package/dist/metrics/service.d.ts.map +1 -0
- package/dist/metrics/service.js +21 -0
- package/dist/metrics/service.js.map +1 -0
- package/dist/presentation/auth/index.d.ts +3 -0
- package/dist/presentation/auth/index.d.ts.map +1 -0
- package/dist/presentation/auth/index.js +4 -0
- package/dist/presentation/auth/index.js.map +1 -0
- package/dist/presentation/auth/provisioner-token-auth.d.ts +3 -0
- package/dist/presentation/auth/provisioner-token-auth.d.ts.map +1 -0
- package/dist/presentation/auth/provisioner-token-auth.js +81 -0
- package/dist/presentation/auth/provisioner-token-auth.js.map +1 -0
- package/dist/presentation/auth/runner-registration-token-auth.d.ts +17 -0
- package/dist/presentation/auth/runner-registration-token-auth.d.ts.map +1 -0
- package/dist/presentation/auth/runner-registration-token-auth.js +77 -0
- package/dist/presentation/auth/runner-registration-token-auth.js.map +1 -0
- package/dist/presentation/dto/active-provisioner.d.ts +8 -0
- package/dist/presentation/dto/active-provisioner.d.ts.map +1 -0
- package/dist/presentation/dto/active-provisioner.js +10 -0
- package/dist/presentation/dto/active-provisioner.js.map +1 -0
- package/dist/presentation/dto/index.d.ts +7 -0
- package/dist/presentation/dto/index.d.ts.map +1 -0
- package/dist/presentation/dto/index.js +8 -0
- package/dist/presentation/dto/index.js.map +1 -0
- package/dist/presentation/dto/manual-registration-token.d.ts +12 -0
- package/dist/presentation/dto/manual-registration-token.d.ts.map +1 -0
- package/dist/presentation/dto/manual-registration-token.js +14 -0
- package/dist/presentation/dto/manual-registration-token.js.map +1 -0
- package/dist/presentation/dto/mint-registration-tokens.d.ts +5 -0
- package/dist/presentation/dto/mint-registration-tokens.d.ts.map +1 -0
- package/dist/presentation/dto/mint-registration-tokens.js +19 -0
- package/dist/presentation/dto/mint-registration-tokens.js.map +1 -0
- package/dist/presentation/dto/poll-demand.d.ts +4 -0
- package/dist/presentation/dto/poll-demand.d.ts.map +1 -0
- package/dist/presentation/dto/poll-demand.js +19 -0
- package/dist/presentation/dto/poll-demand.js.map +1 -0
- package/dist/presentation/dto/provisioned-runners.d.ts +38 -0
- package/dist/presentation/dto/provisioned-runners.d.ts.map +1 -0
- package/dist/presentation/dto/provisioned-runners.js +44 -0
- package/dist/presentation/dto/provisioned-runners.js.map +1 -0
- package/dist/presentation/dto/provisioner-token.d.ts +15 -0
- package/dist/presentation/dto/provisioner-token.d.ts.map +1 -0
- package/dist/presentation/dto/provisioner-token.js +17 -0
- package/dist/presentation/dto/provisioner-token.js.map +1 -0
- package/dist/presentation/index.d.ts +4 -0
- package/dist/presentation/index.d.ts.map +1 -0
- package/dist/presentation/index.js +5 -0
- package/dist/presentation/index.js.map +1 -0
- package/dist/presentation/routes/create-manual-registration-token.d.ts +2 -0
- package/dist/presentation/routes/create-manual-registration-token.d.ts.map +1 -0
- package/dist/presentation/routes/create-manual-registration-token.js +44 -0
- package/dist/presentation/routes/create-manual-registration-token.js.map +1 -0
- package/dist/presentation/routes/create-provisioner-token.d.ts +2 -0
- package/dist/presentation/routes/create-provisioner-token.d.ts.map +1 -0
- package/dist/presentation/routes/create-provisioner-token.js +42 -0
- package/dist/presentation/routes/create-provisioner-token.js.map +1 -0
- package/dist/presentation/routes/get-provisioner-me.d.ts +2 -0
- package/dist/presentation/routes/get-provisioner-me.d.ts.map +1 -0
- package/dist/presentation/routes/get-provisioner-me.js +22 -0
- package/dist/presentation/routes/get-provisioner-me.js.map +1 -0
- package/dist/presentation/routes/heartbeat.d.ts +2 -0
- package/dist/presentation/routes/heartbeat.d.ts.map +1 -0
- package/dist/presentation/routes/heartbeat.js +64 -0
- package/dist/presentation/routes/heartbeat.js.map +1 -0
- package/dist/presentation/routes/index.d.ts +4 -0
- package/dist/presentation/routes/index.d.ts.map +1 -0
- package/dist/presentation/routes/index.js +97 -0
- package/dist/presentation/routes/index.js.map +1 -0
- package/dist/presentation/routes/list-active-provisioners.d.ts +2 -0
- package/dist/presentation/routes/list-active-provisioners.d.ts.map +1 -0
- package/dist/presentation/routes/list-active-provisioners.js +32 -0
- package/dist/presentation/routes/list-active-provisioners.js.map +1 -0
- package/dist/presentation/routes/list-active-runners.d.ts +2 -0
- package/dist/presentation/routes/list-active-runners.d.ts.map +1 -0
- package/dist/presentation/routes/list-active-runners.js +32 -0
- package/dist/presentation/routes/list-active-runners.js.map +1 -0
- package/dist/presentation/routes/list-manual-registration-tokens.d.ts +2 -0
- package/dist/presentation/routes/list-manual-registration-tokens.d.ts.map +1 -0
- package/dist/presentation/routes/list-manual-registration-tokens.js +32 -0
- package/dist/presentation/routes/list-manual-registration-tokens.js.map +1 -0
- package/dist/presentation/routes/list-provisioner-tokens.d.ts +2 -0
- package/dist/presentation/routes/list-provisioner-tokens.d.ts.map +1 -0
- package/dist/presentation/routes/list-provisioner-tokens.js +32 -0
- package/dist/presentation/routes/list-provisioner-tokens.js.map +1 -0
- package/dist/presentation/routes/mint-registration-tokens.d.ts +2 -0
- package/dist/presentation/routes/mint-registration-tokens.d.ts.map +1 -0
- package/dist/presentation/routes/mint-registration-tokens.js +74 -0
- package/dist/presentation/routes/mint-registration-tokens.js.map +1 -0
- package/dist/presentation/routes/poll-demand.d.ts +2 -0
- package/dist/presentation/routes/poll-demand.d.ts.map +1 -0
- package/dist/presentation/routes/poll-demand.js +53 -0
- package/dist/presentation/routes/poll-demand.js.map +1 -0
- package/dist/presentation/routes/rate-limit.d.ts +4 -0
- package/dist/presentation/routes/rate-limit.d.ts.map +1 -0
- package/dist/presentation/routes/rate-limit.js +94 -0
- package/dist/presentation/routes/rate-limit.js.map +1 -0
- package/dist/presentation/routes/reconcile-provisioned-runners.d.ts +2 -0
- package/dist/presentation/routes/reconcile-provisioned-runners.d.ts.map +1 -0
- package/dist/presentation/routes/reconcile-provisioned-runners.js +27 -0
- package/dist/presentation/routes/reconcile-provisioned-runners.js.map +1 -0
- package/dist/presentation/routes/register.d.ts +2 -0
- package/dist/presentation/routes/register.d.ts.map +1 -0
- package/dist/presentation/routes/register.js +63 -0
- package/dist/presentation/routes/register.js.map +1 -0
- package/dist/presentation/routes/report-provisioned-runners.d.ts +2 -0
- package/dist/presentation/routes/report-provisioned-runners.d.ts.map +1 -0
- package/dist/presentation/routes/report-provisioned-runners.js +37 -0
- package/dist/presentation/routes/report-provisioned-runners.js.map +1 -0
- package/dist/presentation/routes/request-job.d.ts +2 -0
- package/dist/presentation/routes/request-job.d.ts.map +1 -0
- package/dist/presentation/routes/request-job.js +45 -0
- package/dist/presentation/routes/request-job.js.map +1 -0
- package/dist/presentation/routes/revoke-manual-registration-token.d.ts +2 -0
- package/dist/presentation/routes/revoke-manual-registration-token.d.ts.map +1 -0
- package/dist/presentation/routes/revoke-manual-registration-token.js +42 -0
- package/dist/presentation/routes/revoke-manual-registration-token.js.map +1 -0
- package/dist/presentation/routes/revoke-provisioner-token.d.ts +2 -0
- package/dist/presentation/routes/revoke-provisioner-token.d.ts.map +1 -0
- package/dist/presentation/routes/revoke-provisioner-token.js +44 -0
- package/dist/presentation/routes/revoke-provisioner-token.js.map +1 -0
- package/dist/presentation/routes/workspace-membership.d.ts +6 -0
- package/dist/presentation/routes/workspace-membership.d.ts.map +1 -0
- package/dist/presentation/routes/workspace-membership.js +6 -0
- package/dist/presentation/routes/workspace-membership.js.map +1 -0
- package/dist/presentation/subscribers/on-workflows-job-execution-timed-out.d.ts +3 -0
- package/dist/presentation/subscribers/on-workflows-job-execution-timed-out.d.ts.map +1 -0
- package/dist/presentation/subscribers/on-workflows-job-execution-timed-out.js +14 -0
- package/dist/presentation/subscribers/on-workflows-job-execution-timed-out.js.map +1 -0
- package/dist/temporal/activities/index.d.ts +9 -0
- package/dist/temporal/activities/index.d.ts.map +1 -0
- package/dist/temporal/activities/index.js +12 -0
- package/dist/temporal/activities/index.js.map +1 -0
- package/dist/temporal/activities/maintenance-activities.d.ts +31 -0
- package/dist/temporal/activities/maintenance-activities.d.ts.map +1 -0
- package/dist/temporal/activities/maintenance-activities.js +18 -0
- package/dist/temporal/activities/maintenance-activities.js.map +1 -0
- package/dist/temporal/constants.d.ts +2 -0
- package/dist/temporal/constants.d.ts.map +1 -0
- package/dist/temporal/constants.js +3 -0
- package/dist/temporal/constants.js.map +1 -0
- package/dist/temporal/workflows/index.d.ts +2 -0
- package/dist/temporal/workflows/index.d.ts.map +1 -0
- package/dist/temporal/workflows/index.js +3 -0
- package/dist/temporal/workflows/index.js.map +1 -0
- package/dist/temporal/workflows/stuck-job-detector.d.ts +2 -0
- package/dist/temporal/workflows/stuck-job-detector.d.ts.map +1 -0
- package/dist/temporal/workflows/stuck-job-detector.js +61 -0
- package/dist/temporal/workflows/stuck-job-detector.js.map +1 -0
- package/dist/tsconfig.test.tsbuildinfo +1 -0
- package/drizzle/0000_initial.sql +185 -0
- package/drizzle/meta/0000_snapshot.json +1582 -0
- package/drizzle/meta/_journal.json +13 -0
- package/drizzle.config.ts +7 -0
- package/package.json +82 -0
- package/src/config.test.ts +374 -0
- package/src/config.ts +330 -0
- package/src/core/demand.test.ts +301 -0
- package/src/core/demand.ts +239 -0
- package/src/core/entities/ephemeral-registration-token.ts +13 -0
- package/src/core/entities/manual-registration-token.ts +11 -0
- package/src/core/entities/provisioned-runner.ts +30 -0
- package/src/core/entities/provisioner-token.ts +16 -0
- package/src/core/entities/reservation.ts +9 -0
- package/src/core/entities/runner-session.ts +18 -0
- package/src/core/ephemeral-registration-tokens.test.ts +108 -0
- package/src/core/ephemeral-registration-tokens.ts +112 -0
- package/src/core/errors.ts +128 -0
- package/src/core/index.ts +56 -0
- package/src/core/job-executions.ts +47 -0
- package/src/core/maintenance-policy.ts +1 -0
- package/src/core/maintenance.test.ts +251 -0
- package/src/core/maintenance.ts +74 -0
- package/src/core/manual-registration-tokens.test.ts +44 -0
- package/src/core/manual-registration-tokens.ts +51 -0
- package/src/core/provisioned-runners.test.ts +101 -0
- package/src/core/provisioned-runners.ts +301 -0
- package/src/core/provisioner-tokens.test.ts +61 -0
- package/src/core/provisioner-tokens.ts +61 -0
- package/src/core/rate-limit.test.ts +158 -0
- package/src/core/rate-limit.ts +121 -0
- package/src/core/runner-sessions.test.ts +209 -0
- package/src/core/runner-sessions.ts +66 -0
- package/src/core/runner-tool-capabilities.test.ts +141 -0
- package/src/core/runner-tool-capabilities.ts +75 -0
- package/src/db/db.ts +38 -0
- package/src/db/ephemeral-registration-tokens.test.ts +332 -0
- package/src/db/ephemeral-registration-tokens.ts +348 -0
- package/src/db/index.ts +90 -0
- package/src/db/job-executions.test.ts +1500 -0
- package/src/db/job-executions.ts +647 -0
- package/src/db/manual-registration-tokens.test.ts +99 -0
- package/src/db/manual-registration-tokens.ts +87 -0
- package/src/db/provisioned-runners.test.ts +1812 -0
- package/src/db/provisioned-runners.ts +886 -0
- package/src/db/provisioner-tokens.test.ts +133 -0
- package/src/db/provisioner-tokens.ts +142 -0
- package/src/db/rate-limits.test.ts +74 -0
- package/src/db/rate-limits.ts +69 -0
- package/src/db/reservations.test.ts +399 -0
- package/src/db/reservations.ts +296 -0
- package/src/db/runner-sessions.test.ts +153 -0
- package/src/db/runner-sessions.ts +97 -0
- package/src/db/schema/common.ts +3 -0
- package/src/db/schema/ephemeral-registration-tokens.ts +58 -0
- package/src/db/schema/manual-registration-tokens.ts +40 -0
- package/src/db/schema/outbox.ts +4 -0
- package/src/db/schema/pending-job-executions.ts +25 -0
- package/src/db/schema/provisioned-runners.ts +89 -0
- package/src/db/schema/provisioner-tokens.ts +50 -0
- package/src/db/schema/rate-limits.ts +27 -0
- package/src/db/schema/reservations.ts +38 -0
- package/src/db/schema/runner-sessions.ts +77 -0
- package/src/db/schema/running-job-executions.ts +50 -0
- package/src/index.ts +63 -0
- package/src/metrics/index.ts +13 -0
- package/src/metrics/instance.ts +115 -0
- package/src/metrics/service.ts +22 -0
- package/src/presentation/auth/index.ts +6 -0
- package/src/presentation/auth/provisioner-token-auth.ts +76 -0
- package/src/presentation/auth/runner-registration-token-auth.ts +105 -0
- package/src/presentation/dto/active-provisioner.ts +15 -0
- package/src/presentation/dto/index.ts +13 -0
- package/src/presentation/dto/manual-registration-token.ts +23 -0
- package/src/presentation/dto/mint-registration-tokens.ts +35 -0
- package/src/presentation/dto/poll-demand.ts +20 -0
- package/src/presentation/dto/provisioned-runners.ts +88 -0
- package/src/presentation/dto/provisioner-token.ts +29 -0
- package/src/presentation/index.ts +6 -0
- package/src/presentation/routes/create-manual-registration-token.ts +43 -0
- package/src/presentation/routes/create-provisioner-token.ts +41 -0
- package/src/presentation/routes/get-provisioner-me.ts +22 -0
- package/src/presentation/routes/heartbeat.test.ts +325 -0
- package/src/presentation/routes/heartbeat.ts +80 -0
- package/src/presentation/routes/index.ts +86 -0
- package/src/presentation/routes/list-active-provisioners.ts +25 -0
- package/src/presentation/routes/list-active-runners.test.ts +366 -0
- package/src/presentation/routes/list-active-runners.ts +25 -0
- package/src/presentation/routes/list-manual-registration-tokens.ts +25 -0
- package/src/presentation/routes/list-provisioner-tokens.ts +25 -0
- package/src/presentation/routes/manual-registration-tokens.test.ts +213 -0
- package/src/presentation/routes/mint-registration-tokens.test.ts +453 -0
- package/src/presentation/routes/mint-registration-tokens.ts +82 -0
- package/src/presentation/routes/poll-demand-release.test.ts +80 -0
- package/src/presentation/routes/poll-demand.test.ts +323 -0
- package/src/presentation/routes/poll-demand.ts +56 -0
- package/src/presentation/routes/provisioner-me.test.ts +172 -0
- package/src/presentation/routes/provisioner-tokens.test.ts +219 -0
- package/src/presentation/routes/rate-limit.ts +115 -0
- package/src/presentation/routes/reconcile-provisioned-runners.test.ts +380 -0
- package/src/presentation/routes/reconcile-provisioned-runners.ts +30 -0
- package/src/presentation/routes/register.test.ts +453 -0
- package/src/presentation/routes/register.ts +75 -0
- package/src/presentation/routes/report-provisioned-runners.test.ts +249 -0
- package/src/presentation/routes/report-provisioned-runners.ts +40 -0
- package/src/presentation/routes/request-job.test.ts +222 -0
- package/src/presentation/routes/request-job.ts +47 -0
- package/src/presentation/routes/revoke-manual-registration-token.ts +35 -0
- package/src/presentation/routes/revoke-provisioner-token.ts +37 -0
- package/src/presentation/routes/workspace-membership.ts +9 -0
- package/src/presentation/subscribers/on-workflows-job-execution-timed-out.test.ts +147 -0
- package/src/presentation/subscribers/on-workflows-job-execution-timed-out.ts +17 -0
- package/src/temporal/activities/index.ts +17 -0
- package/src/temporal/activities/maintenance-activities.test.ts +104 -0
- package/src/temporal/activities/maintenance-activities.ts +41 -0
- package/src/temporal/constants.ts +1 -0
- package/src/temporal/workflows/index.ts +1 -0
- package/src/temporal/workflows/stuck-job-detector.test.ts +93 -0
- package/src/temporal/workflows/stuck-job-detector.ts +67 -0
- package/test/env.ts +11 -0
- package/test/factories/ephemeral-registration-token.ts +41 -0
- package/test/factories/manual-registration-token.ts +37 -0
- package/test/factories/pending-job.ts +44 -0
- package/test/factories/provisioned-runner.ts +78 -0
- package/test/factories/provisioner-token.ts +41 -0
- package/test/factories/reservation.ts +33 -0
- package/test/factories/runner-session.ts +32 -0
- package/test/globalSetup.ts +23 -0
- package/test/index.ts +16 -0
- package/test/setup.ts +17 -0
- package/tsconfig.build.json +9 -0
- package/tsconfig.build.tsbuildinfo +1 -0
- package/tsconfig.json +3 -0
- package/tsconfig.test.json +8 -0
- package/vitest.config.ts +14 -0
|
@@ -0,0 +1,647 @@
|
|
|
1
|
+
import {
|
|
2
|
+
RUNNER_JOB_CLAIMED,
|
|
3
|
+
RUNNER_JOB_LEASE_EXPIRED,
|
|
4
|
+
RUNNER_JOB_QUEUED,
|
|
5
|
+
type RunnersEventMap,
|
|
6
|
+
type RunnerToolCapabilitiesDto,
|
|
7
|
+
} from '@shipfox/api-runners-dto';
|
|
8
|
+
import {logger} from '@shipfox/node-opentelemetry';
|
|
9
|
+
import {writeOutboxEvent, writeOutboxEvents} from '@shipfox/node-outbox';
|
|
10
|
+
import {canonicalizeLabels} from '@shipfox/runner-labels';
|
|
11
|
+
import {
|
|
12
|
+
and,
|
|
13
|
+
arrayContained,
|
|
14
|
+
asc,
|
|
15
|
+
count,
|
|
16
|
+
desc,
|
|
17
|
+
eq,
|
|
18
|
+
gt,
|
|
19
|
+
inArray,
|
|
20
|
+
isNotNull,
|
|
21
|
+
isNull,
|
|
22
|
+
lt,
|
|
23
|
+
lte,
|
|
24
|
+
or,
|
|
25
|
+
sql,
|
|
26
|
+
} from 'drizzle-orm';
|
|
27
|
+
import {
|
|
28
|
+
EmptyRequiredLabelsError,
|
|
29
|
+
RunnerSessionExhaustedError,
|
|
30
|
+
RunningJobExecutionNotFoundError,
|
|
31
|
+
} from '#core/errors.js';
|
|
32
|
+
import {jobExecutionEnqueuedCount, jobExecutionLeaseExpiredCount} from '#metrics/instance.js';
|
|
33
|
+
import type {Tx} from './db.js';
|
|
34
|
+
import {db} from './db.js';
|
|
35
|
+
import {runnersOutbox} from './schema/outbox.js';
|
|
36
|
+
import {pendingJobExecutions} from './schema/pending-job-executions.js';
|
|
37
|
+
import {runnerSessions} from './schema/runner-sessions.js';
|
|
38
|
+
import {runningJobExecutions} from './schema/running-job-executions.js';
|
|
39
|
+
|
|
40
|
+
export interface EnqueueJobExecutionParams {
|
|
41
|
+
workspaceId: string;
|
|
42
|
+
workflowRunId: string;
|
|
43
|
+
workflowRunAttemptId: string;
|
|
44
|
+
jobId: string;
|
|
45
|
+
jobExecutionId: string;
|
|
46
|
+
projectId: string;
|
|
47
|
+
requiredLabels: string[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Idempotent while the job execution is still pending: a duplicate jobExecutionId already in
|
|
51
|
+
// `runners_pending_jobs` is a no-op. Temporal retries the enqueue activity
|
|
52
|
+
// at-least-once, so a unique-violation throw on a retry-after-lost-result
|
|
53
|
+
// would permanently fail a healthy job execution's workflow. The guard does not extend
|
|
54
|
+
// past the claim: once the job execution has moved to `runners_running_jobs`, a retry
|
|
55
|
+
// can reinsert an orphan pending row, which a later `claimPendingJobExecution` drops
|
|
56
|
+
// via the running-execution unique constraint (onConflictDoNothing) instead of failing.
|
|
57
|
+
export async function enqueueJobExecution(params: EnqueueJobExecutionParams): Promise<void> {
|
|
58
|
+
const requiredLabels = [...canonicalizeLabels(params.requiredLabels)];
|
|
59
|
+
if (requiredLabels.length === 0) throw new EmptyRequiredLabelsError();
|
|
60
|
+
|
|
61
|
+
const enqueued = await db().transaction(async (tx) => {
|
|
62
|
+
const [inserted] = await tx
|
|
63
|
+
.insert(pendingJobExecutions)
|
|
64
|
+
.values({
|
|
65
|
+
workspaceId: params.workspaceId,
|
|
66
|
+
workflowRunId: params.workflowRunId,
|
|
67
|
+
workflowRunAttemptId: params.workflowRunAttemptId,
|
|
68
|
+
jobId: params.jobId,
|
|
69
|
+
jobExecutionId: params.jobExecutionId,
|
|
70
|
+
projectId: params.projectId,
|
|
71
|
+
requiredLabels,
|
|
72
|
+
})
|
|
73
|
+
.onConflictDoNothing({target: pendingJobExecutions.jobExecutionId})
|
|
74
|
+
.returning({createdAt: pendingJobExecutions.createdAt});
|
|
75
|
+
|
|
76
|
+
// A retry that hits the conflict inserts nothing: the first enqueue already
|
|
77
|
+
// emitted the queued event (durably, in the outbox), so re-emitting would
|
|
78
|
+
// only add a redundant row the subscriber coalesces away. Skip it.
|
|
79
|
+
if (!inserted) return false;
|
|
80
|
+
|
|
81
|
+
await writeOutboxEvent<RunnersEventMap>(tx, runnersOutbox, {
|
|
82
|
+
type: RUNNER_JOB_QUEUED,
|
|
83
|
+
payload: {
|
|
84
|
+
workflowRunId: params.workflowRunId,
|
|
85
|
+
workflowRunAttemptId: params.workflowRunAttemptId,
|
|
86
|
+
jobId: params.jobId,
|
|
87
|
+
jobExecutionId: params.jobExecutionId,
|
|
88
|
+
queuedAt: inserted.createdAt.toISOString(),
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
return true;
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
if (enqueued) jobExecutionEnqueuedCount.add(1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface ClaimedJobExecution {
|
|
98
|
+
workflowRunId: string;
|
|
99
|
+
workflowRunAttemptId: string;
|
|
100
|
+
jobId: string;
|
|
101
|
+
jobExecutionId: string;
|
|
102
|
+
projectId: string;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface ActiveRunningJobExecution {
|
|
106
|
+
workflowRunId: string;
|
|
107
|
+
workflowRunAttemptId: string;
|
|
108
|
+
jobId: string;
|
|
109
|
+
jobExecutionId: string;
|
|
110
|
+
projectId: string;
|
|
111
|
+
runnerSessionId: string;
|
|
112
|
+
provisionerId: string | null;
|
|
113
|
+
provisionedRunnerId: string | null;
|
|
114
|
+
requiredLabels: string[];
|
|
115
|
+
runnerLabels: string[];
|
|
116
|
+
startedAt: Date;
|
|
117
|
+
lastHeartbeatAt: Date;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface ProvisionedRunnerBoundJobExecution {
|
|
121
|
+
workflowRunId: string;
|
|
122
|
+
workflowRunAttemptId: string;
|
|
123
|
+
jobId: string;
|
|
124
|
+
jobExecutionId: string;
|
|
125
|
+
provisionedRunnerId: string;
|
|
126
|
+
startedAt: Date;
|
|
127
|
+
lastHeartbeatAt: Date;
|
|
128
|
+
cancellationRequestedAt: Date | null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export async function claimPendingJobExecution(params: {
|
|
132
|
+
workspaceId: string;
|
|
133
|
+
runnerSessionId: string;
|
|
134
|
+
sessionLabels: string[];
|
|
135
|
+
maxClaims: number | null;
|
|
136
|
+
runnerSessionLivenessThrottleSeconds: number;
|
|
137
|
+
}): Promise<ClaimedJobExecution | null> {
|
|
138
|
+
await touchRunnerSessionLiveness({
|
|
139
|
+
workspaceId: params.workspaceId,
|
|
140
|
+
runnerSessionId: params.runnerSessionId,
|
|
141
|
+
throttleSeconds: params.runnerSessionLivenessThrottleSeconds,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
if (params.sessionLabels.length === 0) return null;
|
|
145
|
+
|
|
146
|
+
return await db().transaction(async (tx) => {
|
|
147
|
+
let provisionerId: string | null = null;
|
|
148
|
+
let provisionedRunnerId: string | null = null;
|
|
149
|
+
|
|
150
|
+
if (params.maxClaims !== null) {
|
|
151
|
+
const [session] = await tx
|
|
152
|
+
.select({
|
|
153
|
+
maxClaims: runnerSessions.maxClaims,
|
|
154
|
+
claimsUsed: runnerSessions.claimsUsed,
|
|
155
|
+
provisionerId: runnerSessions.provisionerId,
|
|
156
|
+
provisionedRunnerId: runnerSessions.provisionedRunnerId,
|
|
157
|
+
})
|
|
158
|
+
.from(runnerSessions)
|
|
159
|
+
.where(eq(runnerSessions.id, params.runnerSessionId))
|
|
160
|
+
.limit(1)
|
|
161
|
+
.for('update');
|
|
162
|
+
|
|
163
|
+
if (!session || session.maxClaims === null || session.claimsUsed >= session.maxClaims) {
|
|
164
|
+
throw new RunnerSessionExhaustedError(params.runnerSessionId);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Ephemeral sessions are the only capped sessions, and the DB check keeps
|
|
168
|
+
// their provisioned-runner link present as a pair.
|
|
169
|
+
provisionerId = session.provisionerId;
|
|
170
|
+
provisionedRunnerId = session.provisionedRunnerId;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// `id` is a uuidv7 (time-ordered), so it is a deterministic FIFO tiebreaker
|
|
174
|
+
// for rows sharing a created_at within a batch.
|
|
175
|
+
const [row] = await tx
|
|
176
|
+
.select()
|
|
177
|
+
.from(pendingJobExecutions)
|
|
178
|
+
.where(
|
|
179
|
+
and(
|
|
180
|
+
eq(pendingJobExecutions.workspaceId, params.workspaceId),
|
|
181
|
+
arrayContained(pendingJobExecutions.requiredLabels, params.sessionLabels),
|
|
182
|
+
),
|
|
183
|
+
)
|
|
184
|
+
.orderBy(asc(pendingJobExecutions.createdAt), asc(pendingJobExecutions.id))
|
|
185
|
+
.limit(1)
|
|
186
|
+
.for('update', {skipLocked: true});
|
|
187
|
+
|
|
188
|
+
if (!row) return null;
|
|
189
|
+
|
|
190
|
+
await tx.delete(pendingJobExecutions).where(eq(pendingJobExecutions.id, row.id));
|
|
191
|
+
|
|
192
|
+
// An enqueue retry that lands after a prior claim can leave an orphan pending
|
|
193
|
+
// row whose jobExecutionId is already in `runners_running_jobs`. Insert-or-skip
|
|
194
|
+
// on the jobExecutionId unique constraint: when the job execution is already running
|
|
195
|
+
// the insert touches no row, so we commit the orphan's deletion and return
|
|
196
|
+
// null rather than let the unique violation roll the claim back into a poison
|
|
197
|
+
// loop. The runner just re-polls for a real job execution.
|
|
198
|
+
const inserted = await tx
|
|
199
|
+
.insert(runningJobExecutions)
|
|
200
|
+
.values({
|
|
201
|
+
workspaceId: row.workspaceId,
|
|
202
|
+
workflowRunId: row.workflowRunId,
|
|
203
|
+
workflowRunAttemptId: row.workflowRunAttemptId,
|
|
204
|
+
jobId: row.jobId,
|
|
205
|
+
jobExecutionId: row.jobExecutionId,
|
|
206
|
+
projectId: row.projectId,
|
|
207
|
+
runnerSessionId: params.runnerSessionId,
|
|
208
|
+
provisionerId,
|
|
209
|
+
provisionedRunnerId,
|
|
210
|
+
requiredLabels: row.requiredLabels,
|
|
211
|
+
runnerLabels: params.sessionLabels,
|
|
212
|
+
})
|
|
213
|
+
.onConflictDoNothing({target: runningJobExecutions.jobExecutionId})
|
|
214
|
+
.returning({claimedAt: runningJobExecutions.startedAt});
|
|
215
|
+
|
|
216
|
+
const claimed = inserted[0];
|
|
217
|
+
if (!claimed) return null;
|
|
218
|
+
|
|
219
|
+
if (params.maxClaims !== null) {
|
|
220
|
+
await tx
|
|
221
|
+
.update(runnerSessions)
|
|
222
|
+
.set({claimsUsed: sql`${runnerSessions.claimsUsed} + 1`, updatedAt: sql`now()`})
|
|
223
|
+
.where(eq(runnerSessions.id, params.runnerSessionId));
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// The running-row insert is the runner claiming the job execution. Emit in the same tx; the
|
|
227
|
+
// payload carries the row's own claim instant so a consumer records the true time,
|
|
228
|
+
// not the outbox drain time.
|
|
229
|
+
await writeOutboxEvent<RunnersEventMap>(tx, runnersOutbox, {
|
|
230
|
+
type: RUNNER_JOB_CLAIMED,
|
|
231
|
+
payload: {
|
|
232
|
+
workflowRunId: row.workflowRunId,
|
|
233
|
+
workflowRunAttemptId: row.workflowRunAttemptId,
|
|
234
|
+
jobId: row.jobId,
|
|
235
|
+
jobExecutionId: row.jobExecutionId,
|
|
236
|
+
claimedAt: claimed.claimedAt.toISOString(),
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
workflowRunId: row.workflowRunId,
|
|
242
|
+
workflowRunAttemptId: row.workflowRunAttemptId,
|
|
243
|
+
jobId: row.jobId,
|
|
244
|
+
jobExecutionId: row.jobExecutionId,
|
|
245
|
+
projectId: row.projectId,
|
|
246
|
+
};
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async function touchRunnerSessionLiveness(params: {
|
|
251
|
+
workspaceId: string;
|
|
252
|
+
runnerSessionId: string;
|
|
253
|
+
throttleSeconds: number;
|
|
254
|
+
}): Promise<void> {
|
|
255
|
+
await db()
|
|
256
|
+
.update(runnerSessions)
|
|
257
|
+
.set({updatedAt: sql`now()`})
|
|
258
|
+
.where(
|
|
259
|
+
and(
|
|
260
|
+
eq(runnerSessions.id, params.runnerSessionId),
|
|
261
|
+
eq(runnerSessions.workspaceId, params.workspaceId),
|
|
262
|
+
sql`${runnerSessions.updatedAt} < now() - (${params.throttleSeconds} || ' seconds')::interval`,
|
|
263
|
+
),
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Releases a job execution's lease when the orchestration workflow finalizes it: deletes the
|
|
269
|
+
* running-job-execution row AND any lingering pending row for the same execution, in one tx.
|
|
270
|
+
* Idempotent (0-row no-op), no token scope (the workflow is authoritative), and
|
|
271
|
+
* emits no event — the workflow already owns the outcome. Sweeping the pending row
|
|
272
|
+
* too closes the at-least-once window where an enqueue retry left an orphan that a
|
|
273
|
+
* later claim would otherwise pick up for an already-finished job execution.
|
|
274
|
+
*/
|
|
275
|
+
export async function releaseJobExecution(params: {jobExecutionId: string}): Promise<void> {
|
|
276
|
+
await db().transaction(async (tx) => {
|
|
277
|
+
// Delete pending before running to match `claimPendingJobExecution`'s lock-acquisition
|
|
278
|
+
// order (it locks the pending row first, then the running row). A concurrent
|
|
279
|
+
// claim picking up an orphan pending row for this same job execution would otherwise
|
|
280
|
+
// deadlock against the reverse order here.
|
|
281
|
+
await tx
|
|
282
|
+
.delete(pendingJobExecutions)
|
|
283
|
+
.where(eq(pendingJobExecutions.jobExecutionId, params.jobExecutionId));
|
|
284
|
+
await tx
|
|
285
|
+
.delete(runningJobExecutions)
|
|
286
|
+
.where(eq(runningJobExecutions.jobExecutionId, params.jobExecutionId));
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Reaps stale leases (bounded by `limit`), emitting one
|
|
292
|
+
* `runners.job.lease_expired` event per reaped job execution.
|
|
293
|
+
*
|
|
294
|
+
* The cutoff is re-checked in the DELETE, not just the locking subquery, so a
|
|
295
|
+
* heartbeat landing mid-call spares the live row. Each reaped execution also sweeps its
|
|
296
|
+
* pending row: a failed best-effort `releaseJobExecution` would otherwise leave an orphan
|
|
297
|
+
* that a later claim re-runs as an already-finished job execution.
|
|
298
|
+
*
|
|
299
|
+
* Locks running-then-pending, the inverse of `claimPendingJobExecution` / `releaseJobExecution`.
|
|
300
|
+
* That pre-existing asymmetry opens a narrow deadlock window against a concurrent
|
|
301
|
+
* claim of the same orphan-pending job execution; Postgres breaks it and the cron retries.
|
|
302
|
+
*/
|
|
303
|
+
export async function expireStuckJobExecutions(params: {
|
|
304
|
+
thresholdSeconds: number;
|
|
305
|
+
noFirstHeartbeatGraceSeconds: number;
|
|
306
|
+
limit?: number;
|
|
307
|
+
}): Promise<
|
|
308
|
+
Array<{
|
|
309
|
+
workflowRunId: string;
|
|
310
|
+
workflowRunAttemptId: string;
|
|
311
|
+
jobId: string;
|
|
312
|
+
jobExecutionId: string;
|
|
313
|
+
}>
|
|
314
|
+
> {
|
|
315
|
+
const reaped = await db().transaction(async (tx) => {
|
|
316
|
+
const heartbeatCutoff = sql`now() - (${params.thresholdSeconds} || ' seconds')::interval`;
|
|
317
|
+
const firstHeartbeatCutoff = sql`now() - (${params.noFirstHeartbeatGraceSeconds} || ' seconds')::interval`;
|
|
318
|
+
const stalePredicate = or(
|
|
319
|
+
and(
|
|
320
|
+
isNull(runningJobExecutions.firstHeartbeatAt),
|
|
321
|
+
lte(runningJobExecutions.lastHeartbeatAt, runningJobExecutions.startedAt),
|
|
322
|
+
lt(runningJobExecutions.startedAt, firstHeartbeatCutoff),
|
|
323
|
+
),
|
|
324
|
+
and(
|
|
325
|
+
or(
|
|
326
|
+
isNotNull(runningJobExecutions.firstHeartbeatAt),
|
|
327
|
+
gt(runningJobExecutions.lastHeartbeatAt, runningJobExecutions.startedAt),
|
|
328
|
+
),
|
|
329
|
+
lt(runningJobExecutions.lastHeartbeatAt, heartbeatCutoff),
|
|
330
|
+
),
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
const staleIds = tx
|
|
334
|
+
.select({id: runningJobExecutions.id})
|
|
335
|
+
.from(runningJobExecutions)
|
|
336
|
+
.where(stalePredicate)
|
|
337
|
+
.orderBy(
|
|
338
|
+
asc(
|
|
339
|
+
sql`CASE WHEN ${runningJobExecutions.firstHeartbeatAt} IS NULL AND ${runningJobExecutions.lastHeartbeatAt} <= ${runningJobExecutions.startedAt} THEN ${runningJobExecutions.startedAt} ELSE ${runningJobExecutions.lastHeartbeatAt} END`,
|
|
340
|
+
),
|
|
341
|
+
)
|
|
342
|
+
.limit(params.limit ?? 100)
|
|
343
|
+
.for('update', {skipLocked: true});
|
|
344
|
+
|
|
345
|
+
const deleted = await tx
|
|
346
|
+
.delete(runningJobExecutions)
|
|
347
|
+
.where(and(inArray(runningJobExecutions.id, staleIds), stalePredicate))
|
|
348
|
+
.returning({
|
|
349
|
+
workflowRunId: runningJobExecutions.workflowRunId,
|
|
350
|
+
workflowRunAttemptId: runningJobExecutions.workflowRunAttemptId,
|
|
351
|
+
jobId: runningJobExecutions.jobId,
|
|
352
|
+
jobExecutionId: runningJobExecutions.jobExecutionId,
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
if (deleted.length === 0) return [];
|
|
356
|
+
|
|
357
|
+
await tx.delete(pendingJobExecutions).where(
|
|
358
|
+
inArray(
|
|
359
|
+
pendingJobExecutions.jobExecutionId,
|
|
360
|
+
deleted.map((row) => row.jobExecutionId),
|
|
361
|
+
),
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
await writeOutboxEvents<RunnersEventMap>(
|
|
365
|
+
tx,
|
|
366
|
+
runnersOutbox,
|
|
367
|
+
deleted.map((row) => ({
|
|
368
|
+
type: RUNNER_JOB_LEASE_EXPIRED,
|
|
369
|
+
payload: {
|
|
370
|
+
workflowRunId: row.workflowRunId,
|
|
371
|
+
workflowRunAttemptId: row.workflowRunAttemptId,
|
|
372
|
+
jobId: row.jobId,
|
|
373
|
+
jobExecutionId: row.jobExecutionId,
|
|
374
|
+
},
|
|
375
|
+
})),
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
return deleted;
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
if (reaped.length > 0) jobExecutionLeaseExpiredCount.add(reaped.length);
|
|
382
|
+
|
|
383
|
+
return reaped;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export async function getJobExecutionQueueDepth(): Promise<{
|
|
387
|
+
pendingJobExecutions: number;
|
|
388
|
+
runningJobExecutions: number;
|
|
389
|
+
}> {
|
|
390
|
+
const [pending] = await db().select({value: count()}).from(pendingJobExecutions);
|
|
391
|
+
const [running] = await db().select({value: count()}).from(runningJobExecutions);
|
|
392
|
+
return {
|
|
393
|
+
pendingJobExecutions: pending?.value ?? 0,
|
|
394
|
+
runningJobExecutions: running?.value ?? 0,
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
export async function listActiveRunningJobExecutions(params: {
|
|
399
|
+
workspaceId: string;
|
|
400
|
+
windowSeconds: number;
|
|
401
|
+
limit?: number;
|
|
402
|
+
}): Promise<ActiveRunningJobExecution[]> {
|
|
403
|
+
return await db()
|
|
404
|
+
.select({
|
|
405
|
+
jobId: runningJobExecutions.jobId,
|
|
406
|
+
jobExecutionId: runningJobExecutions.jobExecutionId,
|
|
407
|
+
workflowRunId: runningJobExecutions.workflowRunId,
|
|
408
|
+
workflowRunAttemptId: runningJobExecutions.workflowRunAttemptId,
|
|
409
|
+
projectId: runningJobExecutions.projectId,
|
|
410
|
+
runnerSessionId: runningJobExecutions.runnerSessionId,
|
|
411
|
+
provisionerId: runningJobExecutions.provisionerId,
|
|
412
|
+
provisionedRunnerId: runningJobExecutions.provisionedRunnerId,
|
|
413
|
+
requiredLabels: runningJobExecutions.requiredLabels,
|
|
414
|
+
runnerLabels: runningJobExecutions.runnerLabels,
|
|
415
|
+
startedAt: runningJobExecutions.startedAt,
|
|
416
|
+
lastHeartbeatAt: runningJobExecutions.lastHeartbeatAt,
|
|
417
|
+
})
|
|
418
|
+
.from(runningJobExecutions)
|
|
419
|
+
.where(
|
|
420
|
+
and(
|
|
421
|
+
eq(runningJobExecutions.workspaceId, params.workspaceId),
|
|
422
|
+
sql`${runningJobExecutions.lastHeartbeatAt} > now() - (${params.windowSeconds} || ' seconds')::interval`,
|
|
423
|
+
),
|
|
424
|
+
)
|
|
425
|
+
.orderBy(desc(runningJobExecutions.lastHeartbeatAt), desc(runningJobExecutions.id))
|
|
426
|
+
.limit(params.limit ?? 1000);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
export async function listRunningJobExecutionsByProvisionedRunnerTx(
|
|
430
|
+
tx: Tx,
|
|
431
|
+
params: {
|
|
432
|
+
workspaceId: string;
|
|
433
|
+
provisionerId: string;
|
|
434
|
+
provisionedRunnerIds: string[];
|
|
435
|
+
},
|
|
436
|
+
): Promise<ProvisionedRunnerBoundJobExecution[]> {
|
|
437
|
+
if (params.provisionedRunnerIds.length === 0) return [];
|
|
438
|
+
|
|
439
|
+
const duplicateRows = await tx
|
|
440
|
+
.select({
|
|
441
|
+
provisionedRunnerId: runningJobExecutions.provisionedRunnerId,
|
|
442
|
+
count: count(),
|
|
443
|
+
})
|
|
444
|
+
.from(runningJobExecutions)
|
|
445
|
+
.where(
|
|
446
|
+
and(
|
|
447
|
+
eq(runningJobExecutions.workspaceId, params.workspaceId),
|
|
448
|
+
eq(runningJobExecutions.provisionerId, params.provisionerId),
|
|
449
|
+
inArray(runningJobExecutions.provisionedRunnerId, params.provisionedRunnerIds),
|
|
450
|
+
),
|
|
451
|
+
)
|
|
452
|
+
.groupBy(runningJobExecutions.provisionedRunnerId)
|
|
453
|
+
.having(sql`count(*) > 1`);
|
|
454
|
+
|
|
455
|
+
const duplicateProvisionedRunnerIds = duplicateRows.flatMap((row) =>
|
|
456
|
+
row.provisionedRunnerId ? [row.provisionedRunnerId] : [],
|
|
457
|
+
);
|
|
458
|
+
if (duplicateProvisionedRunnerIds.length > 0) {
|
|
459
|
+
logger().warn(
|
|
460
|
+
{
|
|
461
|
+
workspaceId: params.workspaceId,
|
|
462
|
+
provisionerId: params.provisionerId,
|
|
463
|
+
provisionedRunnerIds: duplicateProvisionedRunnerIds,
|
|
464
|
+
},
|
|
465
|
+
'multiple running job executions are bound to the same provisioned runner',
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const result = await tx.execute<{
|
|
470
|
+
workflowRunId: string;
|
|
471
|
+
workflowRunAttemptId: string;
|
|
472
|
+
jobId: string;
|
|
473
|
+
jobExecutionId: string;
|
|
474
|
+
provisionedRunnerId: string;
|
|
475
|
+
startedAt: Date | string;
|
|
476
|
+
lastHeartbeatAt: Date | string;
|
|
477
|
+
cancellationRequestedAt: Date | string | null;
|
|
478
|
+
}>(sql`
|
|
479
|
+
SELECT DISTINCT ON (${runningJobExecutions.provisionedRunnerId})
|
|
480
|
+
${runningJobExecutions.workflowRunId} AS "workflowRunId",
|
|
481
|
+
${runningJobExecutions.workflowRunAttemptId} AS "workflowRunAttemptId",
|
|
482
|
+
${runningJobExecutions.jobId} AS "jobId",
|
|
483
|
+
${runningJobExecutions.jobExecutionId} AS "jobExecutionId",
|
|
484
|
+
${runningJobExecutions.provisionedRunnerId} AS "provisionedRunnerId",
|
|
485
|
+
${runningJobExecutions.startedAt} AS "startedAt",
|
|
486
|
+
${runningJobExecutions.lastHeartbeatAt} AS "lastHeartbeatAt",
|
|
487
|
+
${runningJobExecutions.cancellationRequestedAt} AS "cancellationRequestedAt"
|
|
488
|
+
FROM ${runningJobExecutions}
|
|
489
|
+
WHERE
|
|
490
|
+
${runningJobExecutions.workspaceId} = ${params.workspaceId}
|
|
491
|
+
AND ${runningJobExecutions.provisionerId} = ${params.provisionerId}
|
|
492
|
+
AND ${runningJobExecutions.provisionedRunnerId} IN (${sql.join(
|
|
493
|
+
params.provisionedRunnerIds.map((provisionedRunnerId) => sql`${provisionedRunnerId}`),
|
|
494
|
+
sql`, `,
|
|
495
|
+
)})
|
|
496
|
+
ORDER BY ${runningJobExecutions.provisionedRunnerId}, ${runningJobExecutions.startedAt} DESC, ${runningJobExecutions.jobExecutionId} DESC
|
|
497
|
+
`);
|
|
498
|
+
|
|
499
|
+
return result.rows.map((row) => ({
|
|
500
|
+
workflowRunId: row.workflowRunId,
|
|
501
|
+
workflowRunAttemptId: row.workflowRunAttemptId,
|
|
502
|
+
jobId: row.jobId,
|
|
503
|
+
jobExecutionId: row.jobExecutionId,
|
|
504
|
+
provisionedRunnerId: row.provisionedRunnerId,
|
|
505
|
+
startedAt: toDate(row.startedAt),
|
|
506
|
+
lastHeartbeatAt: toDate(row.lastHeartbeatAt),
|
|
507
|
+
cancellationRequestedAt: row.cancellationRequestedAt
|
|
508
|
+
? toDate(row.cancellationRequestedAt)
|
|
509
|
+
: null,
|
|
510
|
+
}));
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function toDate(value: Date | string): Date {
|
|
514
|
+
return value instanceof Date ? value : new Date(value);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
export async function isJobLeaseActive(params: {
|
|
518
|
+
jobId?: string;
|
|
519
|
+
jobExecutionId: string;
|
|
520
|
+
runnerSessionId: string;
|
|
521
|
+
}): Promise<boolean> {
|
|
522
|
+
const [row] = await db()
|
|
523
|
+
.select({id: runningJobExecutions.id})
|
|
524
|
+
.from(runningJobExecutions)
|
|
525
|
+
.where(
|
|
526
|
+
and(
|
|
527
|
+
eq(runningJobExecutions.jobExecutionId, params.jobExecutionId),
|
|
528
|
+
eq(runningJobExecutions.runnerSessionId, params.runnerSessionId),
|
|
529
|
+
params.jobId === undefined ? undefined : eq(runningJobExecutions.jobId, params.jobId),
|
|
530
|
+
),
|
|
531
|
+
)
|
|
532
|
+
.limit(1);
|
|
533
|
+
|
|
534
|
+
return row !== undefined;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
export async function recordHeartbeat(params: {
|
|
538
|
+
jobExecutionId: string;
|
|
539
|
+
runnerSessionId: string;
|
|
540
|
+
toolCapabilities?: RunnerToolCapabilitiesDto | null;
|
|
541
|
+
}): Promise<{
|
|
542
|
+
cancellationRequested: boolean;
|
|
543
|
+
previousToolCapabilities: RunnerToolCapabilitiesDto | null;
|
|
544
|
+
currentToolCapabilities: RunnerToolCapabilitiesDto | null;
|
|
545
|
+
runningJobExecution: {
|
|
546
|
+
workflowRunId: string;
|
|
547
|
+
workflowRunAttemptId: string;
|
|
548
|
+
jobId: string;
|
|
549
|
+
jobExecutionId: string;
|
|
550
|
+
projectId: string;
|
|
551
|
+
workspaceId: string;
|
|
552
|
+
runnerSessionId: string;
|
|
553
|
+
};
|
|
554
|
+
}> {
|
|
555
|
+
const result = await db().transaction(async (tx) => {
|
|
556
|
+
const updated = await tx
|
|
557
|
+
.update(runningJobExecutions)
|
|
558
|
+
.set({
|
|
559
|
+
firstHeartbeatAt: sql`COALESCE(${runningJobExecutions.firstHeartbeatAt}, now())`,
|
|
560
|
+
lastHeartbeatAt: sql`now()`,
|
|
561
|
+
})
|
|
562
|
+
.where(
|
|
563
|
+
and(
|
|
564
|
+
eq(runningJobExecutions.jobExecutionId, params.jobExecutionId),
|
|
565
|
+
eq(runningJobExecutions.runnerSessionId, params.runnerSessionId),
|
|
566
|
+
),
|
|
567
|
+
)
|
|
568
|
+
.returning({
|
|
569
|
+
cancellationRequestedAt: runningJobExecutions.cancellationRequestedAt,
|
|
570
|
+
workflowRunId: runningJobExecutions.workflowRunId,
|
|
571
|
+
workflowRunAttemptId: runningJobExecutions.workflowRunAttemptId,
|
|
572
|
+
jobId: runningJobExecutions.jobId,
|
|
573
|
+
jobExecutionId: runningJobExecutions.jobExecutionId,
|
|
574
|
+
projectId: runningJobExecutions.projectId,
|
|
575
|
+
workspaceId: runningJobExecutions.workspaceId,
|
|
576
|
+
runnerSessionId: runningJobExecutions.runnerSessionId,
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
const row = updated[0];
|
|
580
|
+
if (!row) throw new RunningJobExecutionNotFoundError(params.jobExecutionId);
|
|
581
|
+
|
|
582
|
+
const [previous] = await tx
|
|
583
|
+
.select({toolCapabilities: runnerSessions.toolCapabilities})
|
|
584
|
+
.from(runnerSessions)
|
|
585
|
+
.where(eq(runnerSessions.id, params.runnerSessionId))
|
|
586
|
+
.limit(1)
|
|
587
|
+
.for('update');
|
|
588
|
+
|
|
589
|
+
const [session] = await tx
|
|
590
|
+
.update(runnerSessions)
|
|
591
|
+
.set({
|
|
592
|
+
toolCapabilities: params.toolCapabilities ?? null,
|
|
593
|
+
toolCapabilitiesReportedAt: params.toolCapabilities ? sql`now()` : null,
|
|
594
|
+
updatedAt: sql`now()`,
|
|
595
|
+
})
|
|
596
|
+
.where(eq(runnerSessions.id, params.runnerSessionId))
|
|
597
|
+
.returning({toolCapabilities: runnerSessions.toolCapabilities});
|
|
598
|
+
|
|
599
|
+
return {
|
|
600
|
+
row,
|
|
601
|
+
previousToolCapabilities: previous?.toolCapabilities ?? null,
|
|
602
|
+
currentToolCapabilities: session?.toolCapabilities ?? null,
|
|
603
|
+
};
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
const row = result.row;
|
|
607
|
+
if (!row) throw new RunningJobExecutionNotFoundError(params.jobExecutionId);
|
|
608
|
+
return {
|
|
609
|
+
cancellationRequested: row.cancellationRequestedAt !== null,
|
|
610
|
+
previousToolCapabilities: result.previousToolCapabilities,
|
|
611
|
+
currentToolCapabilities: result.currentToolCapabilities,
|
|
612
|
+
runningJobExecution: {
|
|
613
|
+
workflowRunId: row.workflowRunId,
|
|
614
|
+
workflowRunAttemptId: row.workflowRunAttemptId,
|
|
615
|
+
jobId: row.jobId,
|
|
616
|
+
jobExecutionId: row.jobExecutionId,
|
|
617
|
+
projectId: row.projectId,
|
|
618
|
+
workspaceId: row.workspaceId,
|
|
619
|
+
runnerSessionId: row.runnerSessionId,
|
|
620
|
+
},
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
export async function requestJobExecutionCancellation(params: {
|
|
625
|
+
jobExecutionId: string;
|
|
626
|
+
}): Promise<void> {
|
|
627
|
+
await db()
|
|
628
|
+
.update(runningJobExecutions)
|
|
629
|
+
.set({
|
|
630
|
+
cancellationRequestedAt: sql`COALESCE(${runningJobExecutions.cancellationRequestedAt}, now())`,
|
|
631
|
+
})
|
|
632
|
+
.where(eq(runningJobExecutions.jobExecutionId, params.jobExecutionId));
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
export async function cancelRunnerJobs(params: {jobIds: string[]}): Promise<void> {
|
|
636
|
+
if (params.jobIds.length === 0) return;
|
|
637
|
+
|
|
638
|
+
await db().transaction(async (tx) => {
|
|
639
|
+
await tx.delete(pendingJobExecutions).where(inArray(pendingJobExecutions.jobId, params.jobIds));
|
|
640
|
+
await tx
|
|
641
|
+
.update(runningJobExecutions)
|
|
642
|
+
.set({
|
|
643
|
+
cancellationRequestedAt: sql`COALESCE(${runningJobExecutions.cancellationRequestedAt}, now())`,
|
|
644
|
+
})
|
|
645
|
+
.where(inArray(runningJobExecutions.jobId, params.jobIds));
|
|
646
|
+
});
|
|
647
|
+
}
|