@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,332 @@
|
|
|
1
|
+
import {extractDisplayPrefix, generateOpaqueToken, hashOpaqueToken} from '@shipfox/node-tokens';
|
|
2
|
+
import {and, eq, inArray} from 'drizzle-orm';
|
|
3
|
+
import {
|
|
4
|
+
ActiveEphemeralRegistrationTokensExistError,
|
|
5
|
+
type RegistrationTokenBatchExceedsReservationError,
|
|
6
|
+
} from '#core/errors.js';
|
|
7
|
+
import {db} from '#db/db.js';
|
|
8
|
+
import {
|
|
9
|
+
createEphemeralRegistrationTokensBatch,
|
|
10
|
+
deleteExpiredEphemeralRegistrationTokens,
|
|
11
|
+
resolveEphemeralRegistrationTokenByHash,
|
|
12
|
+
} from '#db/ephemeral-registration-tokens.js';
|
|
13
|
+
import {ephemeralRegistrationTokens} from '#db/schema/ephemeral-registration-tokens.js';
|
|
14
|
+
import {reservations} from '#db/schema/reservations.js';
|
|
15
|
+
import {ephemeralRegistrationTokenFactory} from '#test/index.js';
|
|
16
|
+
|
|
17
|
+
describe('createEphemeralRegistrationTokensBatch', () => {
|
|
18
|
+
let workspaceId: string;
|
|
19
|
+
let provisionerId: string;
|
|
20
|
+
let reservationId: string;
|
|
21
|
+
|
|
22
|
+
beforeEach(async () => {
|
|
23
|
+
workspaceId = crypto.randomUUID();
|
|
24
|
+
provisionerId = crypto.randomUUID();
|
|
25
|
+
reservationId = await createReservation({count: 3});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('persists all rows for the reservation in one batch', async () => {
|
|
29
|
+
const rawTokens = [
|
|
30
|
+
generateOpaqueToken('ephemeralRegistrationToken'),
|
|
31
|
+
generateOpaqueToken('ephemeralRegistrationToken'),
|
|
32
|
+
];
|
|
33
|
+
const expiresAt = new Date(Date.now() + 300_000);
|
|
34
|
+
|
|
35
|
+
const result = await createEphemeralRegistrationTokensBatch({
|
|
36
|
+
workspaceId,
|
|
37
|
+
provisionerId,
|
|
38
|
+
reservationId,
|
|
39
|
+
expiresAt,
|
|
40
|
+
rows: [
|
|
41
|
+
row('provisioned-runner-a', rawTokens[0] ?? ''),
|
|
42
|
+
row('provisioned-runner-b', rawTokens[1] ?? ''),
|
|
43
|
+
],
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
expect(result).toHaveLength(2);
|
|
47
|
+
expect(result.map((token) => token.provisionedRunnerId).sort()).toEqual([
|
|
48
|
+
'provisioned-runner-a',
|
|
49
|
+
'provisioned-runner-b',
|
|
50
|
+
]);
|
|
51
|
+
expect(result.every((token) => token.reservationId === reservationId)).toBe(true);
|
|
52
|
+
expect(result.every((token) => token.workspaceId === workspaceId)).toBe(true);
|
|
53
|
+
expect(result.every((token) => token.provisionerId === provisionerId)).toBe(true);
|
|
54
|
+
expect(
|
|
55
|
+
await resolveEphemeralRegistrationTokenByHash(hashOpaqueToken(rawTokens[0] ?? '')),
|
|
56
|
+
).toEqual(expect.objectContaining({provisionedRunnerId: 'provisioned-runner-a'}));
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('rejects the batch and inserts no rows when any provisioned runner already has an active token', async () => {
|
|
60
|
+
await ephemeralRegistrationTokenFactory.create({
|
|
61
|
+
workspaceId,
|
|
62
|
+
provisionerId,
|
|
63
|
+
provisionedRunnerId: 'provisioned-runner-a',
|
|
64
|
+
expiresAt: new Date(Date.now() + 60_000),
|
|
65
|
+
});
|
|
66
|
+
const rawToken = generateOpaqueToken('ephemeralRegistrationToken');
|
|
67
|
+
|
|
68
|
+
await expect(
|
|
69
|
+
createEphemeralRegistrationTokensBatch({
|
|
70
|
+
workspaceId,
|
|
71
|
+
provisionerId,
|
|
72
|
+
reservationId,
|
|
73
|
+
expiresAt: new Date(Date.now() + 300_000),
|
|
74
|
+
rows: [row('provisioned-runner-a', rawToken), row('provisioned-runner-b', rawToken)],
|
|
75
|
+
}),
|
|
76
|
+
).rejects.toBeInstanceOf(ActiveEphemeralRegistrationTokensExistError);
|
|
77
|
+
|
|
78
|
+
const rows = await db()
|
|
79
|
+
.select()
|
|
80
|
+
.from(ephemeralRegistrationTokens)
|
|
81
|
+
.where(
|
|
82
|
+
and(
|
|
83
|
+
eq(ephemeralRegistrationTokens.workspaceId, workspaceId),
|
|
84
|
+
eq(ephemeralRegistrationTokens.provisionerId, provisionerId),
|
|
85
|
+
),
|
|
86
|
+
);
|
|
87
|
+
expect(rows).toHaveLength(1);
|
|
88
|
+
expect(rows[0]?.provisionedRunnerId).toBe('provisioned-runner-a');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('allows sequential batches that exactly reach the reservation count', async () => {
|
|
92
|
+
const expiresAt = new Date(Date.now() + 300_000);
|
|
93
|
+
|
|
94
|
+
await createEphemeralRegistrationTokensBatch({
|
|
95
|
+
workspaceId,
|
|
96
|
+
provisionerId,
|
|
97
|
+
reservationId,
|
|
98
|
+
expiresAt,
|
|
99
|
+
rows: [
|
|
100
|
+
row('provisioned-runner-a', generateOpaqueToken('ephemeralRegistrationToken')),
|
|
101
|
+
row('provisioned-runner-b', generateOpaqueToken('ephemeralRegistrationToken')),
|
|
102
|
+
],
|
|
103
|
+
});
|
|
104
|
+
const result = await createEphemeralRegistrationTokensBatch({
|
|
105
|
+
workspaceId,
|
|
106
|
+
provisionerId,
|
|
107
|
+
reservationId,
|
|
108
|
+
expiresAt,
|
|
109
|
+
rows: [row('provisioned-runner-c', generateOpaqueToken('ephemeralRegistrationToken'))],
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
expect(result).toHaveLength(1);
|
|
113
|
+
expect(await countReservationTokens()).toBe(3);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('rejects sequential batches that cumulatively exceed the reservation count', async () => {
|
|
117
|
+
const expiresAt = new Date(Date.now() + 300_000);
|
|
118
|
+
await createEphemeralRegistrationTokensBatch({
|
|
119
|
+
workspaceId,
|
|
120
|
+
provisionerId,
|
|
121
|
+
reservationId,
|
|
122
|
+
expiresAt,
|
|
123
|
+
rows: [
|
|
124
|
+
row('provisioned-runner-a', generateOpaqueToken('ephemeralRegistrationToken')),
|
|
125
|
+
row('provisioned-runner-b', generateOpaqueToken('ephemeralRegistrationToken')),
|
|
126
|
+
],
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
await expect(
|
|
130
|
+
createEphemeralRegistrationTokensBatch({
|
|
131
|
+
workspaceId,
|
|
132
|
+
provisionerId,
|
|
133
|
+
reservationId,
|
|
134
|
+
expiresAt,
|
|
135
|
+
rows: [
|
|
136
|
+
row('provisioned-runner-c', generateOpaqueToken('ephemeralRegistrationToken')),
|
|
137
|
+
row('provisioned-runner-d', generateOpaqueToken('ephemeralRegistrationToken')),
|
|
138
|
+
],
|
|
139
|
+
}),
|
|
140
|
+
).rejects.toMatchObject({
|
|
141
|
+
name: 'RegistrationTokenBatchExceedsReservationError',
|
|
142
|
+
requested: 2,
|
|
143
|
+
reservationCount: 3,
|
|
144
|
+
alreadyMinted: 2,
|
|
145
|
+
} satisfies Partial<RegistrationTokenBatchExceedsReservationError>);
|
|
146
|
+
|
|
147
|
+
expect(await countReservationTokens()).toBe(2);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('counts consumed and expired tokens against the reservation total', async () => {
|
|
151
|
+
const consumed = await ephemeralRegistrationTokenFactory.create({
|
|
152
|
+
workspaceId,
|
|
153
|
+
provisionerId,
|
|
154
|
+
reservationId,
|
|
155
|
+
provisionedRunnerId: 'provisioned-runner-consumed',
|
|
156
|
+
expiresAt: new Date(Date.now() + 60_000),
|
|
157
|
+
});
|
|
158
|
+
await db()
|
|
159
|
+
.update(ephemeralRegistrationTokens)
|
|
160
|
+
.set({consumedAt: new Date()})
|
|
161
|
+
.where(eq(ephemeralRegistrationTokens.id, consumed.id));
|
|
162
|
+
await ephemeralRegistrationTokenFactory.create({
|
|
163
|
+
workspaceId,
|
|
164
|
+
provisionerId,
|
|
165
|
+
reservationId,
|
|
166
|
+
provisionedRunnerId: 'provisioned-runner-expired',
|
|
167
|
+
expiresAt: new Date(Date.now() - 60_000),
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
await expect(
|
|
171
|
+
createEphemeralRegistrationTokensBatch({
|
|
172
|
+
workspaceId,
|
|
173
|
+
provisionerId,
|
|
174
|
+
reservationId,
|
|
175
|
+
expiresAt: new Date(Date.now() + 300_000),
|
|
176
|
+
rows: [
|
|
177
|
+
row('provisioned-runner-a', generateOpaqueToken('ephemeralRegistrationToken')),
|
|
178
|
+
row('provisioned-runner-b', generateOpaqueToken('ephemeralRegistrationToken')),
|
|
179
|
+
],
|
|
180
|
+
}),
|
|
181
|
+
).rejects.toMatchObject({
|
|
182
|
+
name: 'RegistrationTokenBatchExceedsReservationError',
|
|
183
|
+
requested: 2,
|
|
184
|
+
reservationCount: 3,
|
|
185
|
+
alreadyMinted: 2,
|
|
186
|
+
} satisfies Partial<RegistrationTokenBatchExceedsReservationError>);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
async function createReservation(params: {count: number}): Promise<string> {
|
|
190
|
+
const [reservation] = await db()
|
|
191
|
+
.insert(reservations)
|
|
192
|
+
.values({
|
|
193
|
+
workspaceId,
|
|
194
|
+
provisionerId,
|
|
195
|
+
requiredLabels: ['linux'],
|
|
196
|
+
count: params.count,
|
|
197
|
+
expiresAt: new Date(Date.now() + 60_000),
|
|
198
|
+
})
|
|
199
|
+
.returning({id: reservations.id});
|
|
200
|
+
if (!reservation) throw new Error('Insert returned no rows');
|
|
201
|
+
return reservation.id;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function countReservationTokens(): Promise<number> {
|
|
205
|
+
const rows = await db()
|
|
206
|
+
.select()
|
|
207
|
+
.from(ephemeralRegistrationTokens)
|
|
208
|
+
.where(eq(ephemeralRegistrationTokens.reservationId, reservationId));
|
|
209
|
+
return rows.length;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function row(provisionedRunnerId: string, rawToken: string) {
|
|
213
|
+
return {
|
|
214
|
+
provisionedRunnerId,
|
|
215
|
+
hashedToken: hashOpaqueToken(rawToken),
|
|
216
|
+
prefix: extractDisplayPrefix(rawToken),
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
describe('deleteExpiredEphemeralRegistrationTokens', () => {
|
|
222
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
223
|
+
let workspaceId: string;
|
|
224
|
+
let provisionerId: string;
|
|
225
|
+
|
|
226
|
+
beforeEach(() => {
|
|
227
|
+
workspaceId = crypto.randomUUID();
|
|
228
|
+
provisionerId = crypto.randomUUID();
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('deletes consumed tokens whose consumption is older than the retention window', async () => {
|
|
232
|
+
const stale = await insertToken({consumedAt: daysAgo(8), expiresAt: daysAgo(8)});
|
|
233
|
+
const fresh = await insertToken({consumedAt: daysAgo(6), expiresAt: daysAgo(6)});
|
|
234
|
+
|
|
235
|
+
const deleted = await deleteExpiredEphemeralRegistrationTokens({retentionDays: 7, limit: 100});
|
|
236
|
+
|
|
237
|
+
const remaining = await listTokenIds([stale, fresh]);
|
|
238
|
+
expect(deleted).toBe(1);
|
|
239
|
+
expect(remaining).toEqual([fresh]);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('deletes expired unconsumed tokens older than the retention window', async () => {
|
|
243
|
+
const stale = await insertToken({consumedAt: null, expiresAt: daysAgo(8)});
|
|
244
|
+
const fresh = await insertToken({consumedAt: null, expiresAt: daysAgo(6)});
|
|
245
|
+
|
|
246
|
+
const deleted = await deleteExpiredEphemeralRegistrationTokens({retentionDays: 7, limit: 100});
|
|
247
|
+
|
|
248
|
+
const remaining = await listTokenIds([stale, fresh]);
|
|
249
|
+
expect(deleted).toBe(1);
|
|
250
|
+
expect(remaining).toEqual([fresh]);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('keeps active tokens that are neither consumed nor expired', async () => {
|
|
254
|
+
const active = await insertToken({consumedAt: null, expiresAt: daysFromNow(1)});
|
|
255
|
+
|
|
256
|
+
const deleted = await deleteExpiredEphemeralRegistrationTokens({retentionDays: 7, limit: 100});
|
|
257
|
+
|
|
258
|
+
const remaining = await listTokenIds([active]);
|
|
259
|
+
expect(deleted).toBe(0);
|
|
260
|
+
expect(remaining).toEqual([active]);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('keeps recently consumed tokens even when their expiry has already passed', async () => {
|
|
264
|
+
const recentlyConsumed = await insertToken({consumedAt: daysAgo(1), expiresAt: daysAgo(6)});
|
|
265
|
+
|
|
266
|
+
const deleted = await deleteExpiredEphemeralRegistrationTokens({retentionDays: 7, limit: 100});
|
|
267
|
+
|
|
268
|
+
const remaining = await listTokenIds([recentlyConsumed]);
|
|
269
|
+
expect(deleted).toBe(0);
|
|
270
|
+
expect(remaining).toEqual([recentlyConsumed]);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('honors the deletion limit', async () => {
|
|
274
|
+
const first = await insertToken({consumedAt: daysAgo(30), expiresAt: daysAgo(30)});
|
|
275
|
+
const second = await insertToken({consumedAt: daysAgo(20), expiresAt: daysAgo(20)});
|
|
276
|
+
const third = await insertToken({consumedAt: daysAgo(10), expiresAt: daysAgo(10)});
|
|
277
|
+
|
|
278
|
+
const deleted = await deleteExpiredEphemeralRegistrationTokens({retentionDays: 7, limit: 2});
|
|
279
|
+
|
|
280
|
+
const remaining = await listTokenIds([first, second, third]);
|
|
281
|
+
expect(deleted).toBe(2);
|
|
282
|
+
expect(remaining).toEqual([third]);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
function daysAgo(days: number): Date {
|
|
286
|
+
return new Date(Date.now() - days * DAY_MS);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function daysFromNow(days: number): Date {
|
|
290
|
+
return new Date(Date.now() + days * DAY_MS);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async function insertToken(params: {
|
|
294
|
+
consumedAt: Date | null;
|
|
295
|
+
expiresAt: Date;
|
|
296
|
+
createdAt?: Date;
|
|
297
|
+
}): Promise<string> {
|
|
298
|
+
const id = crypto.randomUUID();
|
|
299
|
+
|
|
300
|
+
await db()
|
|
301
|
+
.insert(ephemeralRegistrationTokens)
|
|
302
|
+
.values({
|
|
303
|
+
id,
|
|
304
|
+
workspaceId,
|
|
305
|
+
provisionerId,
|
|
306
|
+
provisionedRunnerId: `provisioned-${id}`,
|
|
307
|
+
hashedToken: crypto.randomUUID(),
|
|
308
|
+
prefix: 'sfxr_test',
|
|
309
|
+
expiresAt: params.expiresAt,
|
|
310
|
+
consumedAt: params.consumedAt,
|
|
311
|
+
createdAt: params.createdAt ?? params.expiresAt,
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
return id;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async function listTokenIds(ids: string[]): Promise<string[]> {
|
|
318
|
+
if (ids.length === 0) return [];
|
|
319
|
+
|
|
320
|
+
const rows = await db()
|
|
321
|
+
.select({id: ephemeralRegistrationTokens.id})
|
|
322
|
+
.from(ephemeralRegistrationTokens)
|
|
323
|
+
.where(
|
|
324
|
+
and(
|
|
325
|
+
eq(ephemeralRegistrationTokens.workspaceId, workspaceId),
|
|
326
|
+
inArray(ephemeralRegistrationTokens.id, ids),
|
|
327
|
+
),
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
return ids.filter((id) => rows.some((row) => row.id === id));
|
|
331
|
+
}
|
|
332
|
+
});
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import type {RunnerToolCapabilitiesDto} from '@shipfox/api-runners-dto';
|
|
2
|
+
import {and, asc, eq, gt, inArray, isNull, lt, sql} from 'drizzle-orm';
|
|
3
|
+
import type {EphemeralRegistrationToken} from '#core/entities/ephemeral-registration-token.js';
|
|
4
|
+
import {
|
|
5
|
+
ActiveEphemeralRegistrationTokenExistsError,
|
|
6
|
+
ActiveEphemeralRegistrationTokensExistError,
|
|
7
|
+
RegistrationTokenBatchExceedsReservationError,
|
|
8
|
+
RegistrationTokenConsumedError,
|
|
9
|
+
RegistrationTokenExpiredError,
|
|
10
|
+
RegistrationTokenWorkspaceMismatchError,
|
|
11
|
+
ReservationExpiredError,
|
|
12
|
+
ReservationNotFoundError,
|
|
13
|
+
} from '#core/errors.js';
|
|
14
|
+
import {db} from './db.js';
|
|
15
|
+
import {
|
|
16
|
+
ephemeralRegistrationTokens,
|
|
17
|
+
toEphemeralRegistrationToken,
|
|
18
|
+
} from './schema/ephemeral-registration-tokens.js';
|
|
19
|
+
import {provisionedRunners} from './schema/provisioned-runners.js';
|
|
20
|
+
import {reservations} from './schema/reservations.js';
|
|
21
|
+
import {runnerSessions, toRunnerSession} from './schema/runner-sessions.js';
|
|
22
|
+
|
|
23
|
+
export interface CreateEphemeralRegistrationTokenParams {
|
|
24
|
+
workspaceId: string;
|
|
25
|
+
provisionerId: string;
|
|
26
|
+
reservationId?: string | null | undefined;
|
|
27
|
+
provisionedRunnerId: string;
|
|
28
|
+
hashedToken: string;
|
|
29
|
+
prefix: string;
|
|
30
|
+
expiresAt: Date;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface CreateEphemeralRegistrationTokensBatchRow {
|
|
34
|
+
provisionedRunnerId: string;
|
|
35
|
+
hashedToken: string;
|
|
36
|
+
prefix: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface CreateEphemeralRegistrationTokensBatchParams {
|
|
40
|
+
workspaceId: string;
|
|
41
|
+
provisionerId: string;
|
|
42
|
+
reservationId: string;
|
|
43
|
+
expiresAt: Date;
|
|
44
|
+
rows: CreateEphemeralRegistrationTokensBatchRow[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function createEphemeralRegistrationToken(
|
|
48
|
+
params: CreateEphemeralRegistrationTokenParams,
|
|
49
|
+
): Promise<EphemeralRegistrationToken> {
|
|
50
|
+
const rows = await db().transaction(async (tx) => {
|
|
51
|
+
const provisionedRunnerLockKey = [
|
|
52
|
+
'runners_ephemeral_registration_tokens',
|
|
53
|
+
params.workspaceId,
|
|
54
|
+
params.provisionerId,
|
|
55
|
+
params.provisionedRunnerId,
|
|
56
|
+
].join(':');
|
|
57
|
+
await tx.execute(sql`select pg_advisory_xact_lock(hashtext(${provisionedRunnerLockKey}))`);
|
|
58
|
+
|
|
59
|
+
const [active] = await tx
|
|
60
|
+
.select({id: ephemeralRegistrationTokens.id})
|
|
61
|
+
.from(ephemeralRegistrationTokens)
|
|
62
|
+
.where(
|
|
63
|
+
and(
|
|
64
|
+
eq(ephemeralRegistrationTokens.workspaceId, params.workspaceId),
|
|
65
|
+
eq(ephemeralRegistrationTokens.provisionerId, params.provisionerId),
|
|
66
|
+
eq(ephemeralRegistrationTokens.provisionedRunnerId, params.provisionedRunnerId),
|
|
67
|
+
isNull(ephemeralRegistrationTokens.consumedAt),
|
|
68
|
+
gt(ephemeralRegistrationTokens.expiresAt, sql`now()`),
|
|
69
|
+
),
|
|
70
|
+
)
|
|
71
|
+
.limit(1);
|
|
72
|
+
|
|
73
|
+
if (active) {
|
|
74
|
+
throw new ActiveEphemeralRegistrationTokenExistsError(
|
|
75
|
+
params.workspaceId,
|
|
76
|
+
params.provisionerId,
|
|
77
|
+
params.provisionedRunnerId,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return await tx
|
|
82
|
+
.insert(ephemeralRegistrationTokens)
|
|
83
|
+
.values({
|
|
84
|
+
workspaceId: params.workspaceId,
|
|
85
|
+
provisionerId: params.provisionerId,
|
|
86
|
+
reservationId: params.reservationId ?? null,
|
|
87
|
+
provisionedRunnerId: params.provisionedRunnerId,
|
|
88
|
+
hashedToken: params.hashedToken,
|
|
89
|
+
prefix: params.prefix,
|
|
90
|
+
expiresAt: params.expiresAt,
|
|
91
|
+
})
|
|
92
|
+
.returning();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const row = rows[0];
|
|
96
|
+
if (!row) throw new Error('Insert returned no rows');
|
|
97
|
+
return toEphemeralRegistrationToken(row);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function createEphemeralRegistrationTokensBatch(
|
|
101
|
+
params: CreateEphemeralRegistrationTokensBatchParams,
|
|
102
|
+
): Promise<EphemeralRegistrationToken[]> {
|
|
103
|
+
const rows = await db().transaction(async (tx) => {
|
|
104
|
+
const provisionerLockKey = [
|
|
105
|
+
'runners_ephemeral_registration_tokens',
|
|
106
|
+
params.workspaceId,
|
|
107
|
+
params.provisionerId,
|
|
108
|
+
].join(':');
|
|
109
|
+
await tx.execute(sql`select pg_advisory_xact_lock(hashtext(${provisionerLockKey}))`);
|
|
110
|
+
for (const provisionedRunnerId of [
|
|
111
|
+
...new Set(params.rows.map((row) => row.provisionedRunnerId)),
|
|
112
|
+
].sort()) {
|
|
113
|
+
const provisionedRunnerLockKey = [
|
|
114
|
+
'runners_ephemeral_registration_tokens',
|
|
115
|
+
params.workspaceId,
|
|
116
|
+
params.provisionerId,
|
|
117
|
+
provisionedRunnerId,
|
|
118
|
+
].join(':');
|
|
119
|
+
await tx.execute(sql`select pg_advisory_xact_lock(hashtext(${provisionedRunnerLockKey}))`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const [reservation] = await tx
|
|
123
|
+
.select({
|
|
124
|
+
count: reservations.count,
|
|
125
|
+
isExpired: sql<boolean>`${reservations.expiresAt} <= now()`,
|
|
126
|
+
})
|
|
127
|
+
.from(reservations)
|
|
128
|
+
.where(
|
|
129
|
+
and(
|
|
130
|
+
eq(reservations.id, params.reservationId),
|
|
131
|
+
eq(reservations.workspaceId, params.workspaceId),
|
|
132
|
+
eq(reservations.provisionerId, params.provisionerId),
|
|
133
|
+
),
|
|
134
|
+
)
|
|
135
|
+
.limit(1);
|
|
136
|
+
|
|
137
|
+
if (!reservation) throw new ReservationNotFoundError(params.reservationId);
|
|
138
|
+
if (reservation.isExpired) throw new ReservationExpiredError(params.reservationId);
|
|
139
|
+
if (params.rows.length > reservation.count) {
|
|
140
|
+
throw new RegistrationTokenBatchExceedsReservationError(
|
|
141
|
+
params.rows.length,
|
|
142
|
+
reservation.count,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const [existingForReservation] = await tx
|
|
147
|
+
.select({count: sql<number>`count(*)::int`})
|
|
148
|
+
.from(ephemeralRegistrationTokens)
|
|
149
|
+
.where(
|
|
150
|
+
and(
|
|
151
|
+
eq(ephemeralRegistrationTokens.workspaceId, params.workspaceId),
|
|
152
|
+
eq(ephemeralRegistrationTokens.provisionerId, params.provisionerId),
|
|
153
|
+
eq(ephemeralRegistrationTokens.reservationId, params.reservationId),
|
|
154
|
+
),
|
|
155
|
+
);
|
|
156
|
+
const alreadyMinted = existingForReservation?.count ?? 0;
|
|
157
|
+
if (alreadyMinted + params.rows.length > reservation.count) {
|
|
158
|
+
throw new RegistrationTokenBatchExceedsReservationError(
|
|
159
|
+
params.rows.length,
|
|
160
|
+
reservation.count,
|
|
161
|
+
alreadyMinted,
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const provisionedRunnerIds = params.rows.map((row) => row.provisionedRunnerId);
|
|
166
|
+
const activeRows = await tx
|
|
167
|
+
.select({provisionedRunnerId: ephemeralRegistrationTokens.provisionedRunnerId})
|
|
168
|
+
.from(ephemeralRegistrationTokens)
|
|
169
|
+
.where(
|
|
170
|
+
and(
|
|
171
|
+
eq(ephemeralRegistrationTokens.workspaceId, params.workspaceId),
|
|
172
|
+
eq(ephemeralRegistrationTokens.provisionerId, params.provisionerId),
|
|
173
|
+
inArray(ephemeralRegistrationTokens.provisionedRunnerId, provisionedRunnerIds),
|
|
174
|
+
isNull(ephemeralRegistrationTokens.consumedAt),
|
|
175
|
+
gt(ephemeralRegistrationTokens.expiresAt, sql`now()`),
|
|
176
|
+
),
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
if (activeRows.length > 0) {
|
|
180
|
+
throw new ActiveEphemeralRegistrationTokensExistError(
|
|
181
|
+
activeRows.map((row) => row.provisionedRunnerId),
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return await tx
|
|
186
|
+
.insert(ephemeralRegistrationTokens)
|
|
187
|
+
.values(
|
|
188
|
+
params.rows.map((row) => ({
|
|
189
|
+
workspaceId: params.workspaceId,
|
|
190
|
+
provisionerId: params.provisionerId,
|
|
191
|
+
reservationId: params.reservationId,
|
|
192
|
+
provisionedRunnerId: row.provisionedRunnerId,
|
|
193
|
+
hashedToken: row.hashedToken,
|
|
194
|
+
prefix: row.prefix,
|
|
195
|
+
expiresAt: params.expiresAt,
|
|
196
|
+
})),
|
|
197
|
+
)
|
|
198
|
+
.returning();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
return rows.map(toEphemeralRegistrationToken);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export async function resolveEphemeralRegistrationTokenByHash(
|
|
205
|
+
hashedToken: string,
|
|
206
|
+
): Promise<EphemeralRegistrationToken | undefined> {
|
|
207
|
+
const rows = await db()
|
|
208
|
+
.select()
|
|
209
|
+
.from(ephemeralRegistrationTokens)
|
|
210
|
+
.where(eq(ephemeralRegistrationTokens.hashedToken, hashedToken))
|
|
211
|
+
.limit(1);
|
|
212
|
+
|
|
213
|
+
const row = rows[0];
|
|
214
|
+
if (!row) return undefined;
|
|
215
|
+
return toEphemeralRegistrationToken(row);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export interface DeleteExpiredEphemeralRegistrationTokensParams {
|
|
219
|
+
retentionDays: number;
|
|
220
|
+
limit?: number;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Deletes ephemeral registration tokens that have reached a terminal state and
|
|
225
|
+
* outlived the retention window. A token is terminal once it is consumed
|
|
226
|
+
* (single use spent) or expired without consumption; the retention window is
|
|
227
|
+
* measured from that terminal event so the audit record survives a bounded
|
|
228
|
+
* debugging window. Active tokens that are neither consumed nor expired are
|
|
229
|
+
* never deleted, so a live single-use credential is never removed early.
|
|
230
|
+
*/
|
|
231
|
+
export async function deleteExpiredEphemeralRegistrationTokens(
|
|
232
|
+
params: DeleteExpiredEphemeralRegistrationTokensParams,
|
|
233
|
+
): Promise<number> {
|
|
234
|
+
// coalesce(consumed_at, expires_at) is the terminal timestamp: consumed_at
|
|
235
|
+
// when the token was spent, otherwise its expiry. Comparing it to the cutoff
|
|
236
|
+
// both selects deletable rows and orders the batch, and it matches the
|
|
237
|
+
// terminal index so idle GC ticks never scan live rows.
|
|
238
|
+
const terminalAt = sql`coalesce(${ephemeralRegistrationTokens.consumedAt}, ${ephemeralRegistrationTokens.expiresAt})`;
|
|
239
|
+
const retentionCutoff = sql`now() - (${params.retentionDays} || ' days')::interval`;
|
|
240
|
+
|
|
241
|
+
const deletableIds = db()
|
|
242
|
+
.select({id: ephemeralRegistrationTokens.id})
|
|
243
|
+
.from(ephemeralRegistrationTokens)
|
|
244
|
+
.where(lt(terminalAt, retentionCutoff))
|
|
245
|
+
.orderBy(asc(terminalAt), asc(ephemeralRegistrationTokens.id))
|
|
246
|
+
.limit(params.limit ?? 1000);
|
|
247
|
+
|
|
248
|
+
const deleted = await db()
|
|
249
|
+
.delete(ephemeralRegistrationTokens)
|
|
250
|
+
.where(inArray(ephemeralRegistrationTokens.id, deletableIds))
|
|
251
|
+
.returning({id: ephemeralRegistrationTokens.id});
|
|
252
|
+
|
|
253
|
+
return deleted.length;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export async function createRunnerSessionConsumingEphemeralToken(params: {
|
|
257
|
+
ephemeralTokenId: string;
|
|
258
|
+
workspaceId: string;
|
|
259
|
+
labels: string[];
|
|
260
|
+
toolCapabilities?: RunnerToolCapabilitiesDto | null;
|
|
261
|
+
maxClaims: number;
|
|
262
|
+
}) {
|
|
263
|
+
return await db().transaction(async (tx) => {
|
|
264
|
+
await tx.execute(sql`select pg_advisory_xact_lock(hashtext(${params.workspaceId}))`);
|
|
265
|
+
|
|
266
|
+
const consumed = await tx
|
|
267
|
+
.update(ephemeralRegistrationTokens)
|
|
268
|
+
.set({consumedAt: sql`now()`})
|
|
269
|
+
.where(
|
|
270
|
+
and(
|
|
271
|
+
eq(ephemeralRegistrationTokens.id, params.ephemeralTokenId),
|
|
272
|
+
eq(ephemeralRegistrationTokens.workspaceId, params.workspaceId),
|
|
273
|
+
isNull(ephemeralRegistrationTokens.consumedAt),
|
|
274
|
+
gt(ephemeralRegistrationTokens.expiresAt, sql`now()`),
|
|
275
|
+
),
|
|
276
|
+
)
|
|
277
|
+
.returning({
|
|
278
|
+
id: ephemeralRegistrationTokens.id,
|
|
279
|
+
provisionerId: ephemeralRegistrationTokens.provisionerId,
|
|
280
|
+
provisionedRunnerId: ephemeralRegistrationTokens.provisionedRunnerId,
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
if (!consumed[0]) {
|
|
284
|
+
const [token] = await tx
|
|
285
|
+
.select({
|
|
286
|
+
workspaceId: ephemeralRegistrationTokens.workspaceId,
|
|
287
|
+
consumedAt: ephemeralRegistrationTokens.consumedAt,
|
|
288
|
+
isExpired: sql<boolean>`${ephemeralRegistrationTokens.expiresAt} <= now()`,
|
|
289
|
+
})
|
|
290
|
+
.from(ephemeralRegistrationTokens)
|
|
291
|
+
.where(eq(ephemeralRegistrationTokens.id, params.ephemeralTokenId))
|
|
292
|
+
.limit(1);
|
|
293
|
+
|
|
294
|
+
if (!token) {
|
|
295
|
+
throw new Error(`Ephemeral registration token not found: ${params.ephemeralTokenId}`);
|
|
296
|
+
}
|
|
297
|
+
if (token.workspaceId !== params.workspaceId) {
|
|
298
|
+
throw new RegistrationTokenWorkspaceMismatchError(
|
|
299
|
+
params.ephemeralTokenId,
|
|
300
|
+
params.workspaceId,
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
if (token.consumedAt) throw new RegistrationTokenConsumedError(params.ephemeralTokenId);
|
|
304
|
+
if (token.isExpired) throw new RegistrationTokenExpiredError(params.ephemeralTokenId);
|
|
305
|
+
throw new Error(
|
|
306
|
+
`Ephemeral registration token could not be consumed: ${params.ephemeralTokenId}`,
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const [session] = await tx
|
|
311
|
+
.insert(runnerSessions)
|
|
312
|
+
.values({
|
|
313
|
+
workspaceId: params.workspaceId,
|
|
314
|
+
scope: 'workspace',
|
|
315
|
+
registrationTokenId: params.ephemeralTokenId,
|
|
316
|
+
registrationTokenKind: 'ephemeral',
|
|
317
|
+
provisionerId: consumed[0].provisionerId,
|
|
318
|
+
provisionedRunnerId: consumed[0].provisionedRunnerId,
|
|
319
|
+
labels: params.labels,
|
|
320
|
+
toolCapabilities: params.toolCapabilities ?? null,
|
|
321
|
+
toolCapabilitiesReportedAt: params.toolCapabilities ? sql`now()` : null,
|
|
322
|
+
maxClaims: params.maxClaims,
|
|
323
|
+
claimsUsed: 0,
|
|
324
|
+
})
|
|
325
|
+
.returning();
|
|
326
|
+
|
|
327
|
+
if (!session) throw new Error('Insert returned no rows');
|
|
328
|
+
|
|
329
|
+
await tx
|
|
330
|
+
.update(ephemeralRegistrationTokens)
|
|
331
|
+
.set({consumedSessionId: session.id})
|
|
332
|
+
.where(eq(ephemeralRegistrationTokens.id, params.ephemeralTokenId));
|
|
333
|
+
|
|
334
|
+
await tx
|
|
335
|
+
.update(provisionedRunners)
|
|
336
|
+
.set({runnerSessionId: session.id, updatedAt: sql`now()`})
|
|
337
|
+
.where(
|
|
338
|
+
and(
|
|
339
|
+
eq(provisionedRunners.workspaceId, params.workspaceId),
|
|
340
|
+
eq(provisionedRunners.provisionerId, consumed[0].provisionerId),
|
|
341
|
+
eq(provisionedRunners.provisionedRunnerId, consumed[0].provisionedRunnerId),
|
|
342
|
+
isNull(provisionedRunners.reservationReleasedAt),
|
|
343
|
+
),
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
return toRunnerSession(session);
|
|
347
|
+
});
|
|
348
|
+
}
|