@shipfox/api-workflows 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 +311 -0
- package/LICENSE +21 -0
- package/dist/core/agent-tool-capability-warning.d.ts +7 -0
- package/dist/core/agent-tool-capability-warning.d.ts.map +1 -0
- package/dist/core/agent-tool-capability-warning.js +149 -0
- package/dist/core/agent-tool-capability-warning.js.map +1 -0
- package/dist/core/agent-tools.d.ts +51 -0
- package/dist/core/agent-tools.d.ts.map +1 -0
- package/dist/core/agent-tools.js +296 -0
- package/dist/core/agent-tools.js.map +1 -0
- package/dist/core/checkout.d.ts +29 -0
- package/dist/core/checkout.d.ts.map +1 -0
- package/dist/core/checkout.js +42 -0
- package/dist/core/checkout.js.map +1 -0
- package/dist/core/condition-trace.d.ts +13 -0
- package/dist/core/condition-trace.d.ts.map +1 -0
- package/dist/core/condition-trace.js +51 -0
- package/dist/core/condition-trace.js.map +1 -0
- package/dist/core/entities/index.d.ts +5 -0
- package/dist/core/entities/index.d.ts.map +1 -0
- package/dist/core/entities/index.js +3 -0
- package/dist/core/entities/index.js.map +1 -0
- package/dist/core/entities/job-execution.d.ts +30 -0
- package/dist/core/entities/job-execution.d.ts.map +1 -0
- package/dist/core/entities/job-execution.js +3 -0
- package/dist/core/entities/job-execution.js.map +1 -0
- package/dist/core/entities/job-listener-event.d.ts +15 -0
- package/dist/core/entities/job-listener-event.d.ts.map +1 -0
- package/dist/core/entities/job-listener-event.js +3 -0
- package/dist/core/entities/job-listener-event.js.map +1 -0
- package/dist/core/entities/job.d.ts +54 -0
- package/dist/core/entities/job.d.ts.map +1 -0
- package/dist/core/entities/job.js +29 -0
- package/dist/core/entities/job.js.map +1 -0
- package/dist/core/entities/step.d.ts +86 -0
- package/dist/core/entities/step.d.ts.map +1 -0
- package/dist/core/entities/step.js +12 -0
- package/dist/core/entities/step.js.map +1 -0
- package/dist/core/entities/workflow-run-attempt.d.ts +20 -0
- package/dist/core/entities/workflow-run-attempt.d.ts.map +1 -0
- package/dist/core/entities/workflow-run-attempt.js +3 -0
- package/dist/core/entities/workflow-run-attempt.js.map +1 -0
- package/dist/core/entities/workflow-run.d.ts +66 -0
- package/dist/core/entities/workflow-run.d.ts.map +1 -0
- package/dist/core/entities/workflow-run.js +10 -0
- package/dist/core/entities/workflow-run.js.map +1 -0
- package/dist/core/errors.d.ts +85 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +134 -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 +9 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/job-execution-outcome.d.ts +29 -0
- package/dist/core/job-execution-outcome.d.ts.map +1 -0
- package/dist/core/job-execution-outcome.js +31 -0
- package/dist/core/job-execution-outcome.js.map +1 -0
- package/dist/core/job-execution.d.ts +37 -0
- package/dist/core/job-execution.d.ts.map +1 -0
- package/dist/core/job-execution.js +425 -0
- package/dist/core/job-execution.js.map +1 -0
- package/dist/core/job-transition/decide-job-activation.d.ts +29 -0
- package/dist/core/job-transition/decide-job-activation.d.ts.map +1 -0
- package/dist/core/job-transition/decide-job-activation.js +51 -0
- package/dist/core/job-transition/decide-job-activation.js.map +1 -0
- package/dist/core/job-transition/derive-job-execution-outputs.d.ts +18 -0
- package/dist/core/job-transition/derive-job-execution-outputs.d.ts.map +1 -0
- package/dist/core/job-transition/derive-job-execution-outputs.js +23 -0
- package/dist/core/job-transition/derive-job-execution-outputs.js.map +1 -0
- package/dist/core/job-transition/derive-job-success.d.ts +16 -0
- package/dist/core/job-transition/derive-job-success.d.ts.map +1 -0
- package/dist/core/job-transition/derive-job-success.js +47 -0
- package/dist/core/job-transition/derive-job-success.js.map +1 -0
- package/dist/core/job-transition/index.d.ts +5 -0
- package/dist/core/job-transition/index.d.ts.map +1 -0
- package/dist/core/job-transition/index.js +6 -0
- package/dist/core/job-transition/index.js.map +1 -0
- package/dist/core/job-transition/runtime-completion-status-for-job.d.ts +4 -0
- package/dist/core/job-transition/runtime-completion-status-for-job.d.ts.map +1 -0
- package/dist/core/job-transition/runtime-completion-status-for-job.js +8 -0
- package/dist/core/job-transition/runtime-completion-status-for-job.js.map +1 -0
- package/dist/core/listener-execution-materialization.d.ts +30 -0
- package/dist/core/listener-execution-materialization.d.ts.map +1 -0
- package/dist/core/listener-execution-materialization.js +87 -0
- package/dist/core/listener-execution-materialization.js.map +1 -0
- package/dist/core/run-workflow.d.ts +11 -0
- package/dist/core/run-workflow.d.ts.map +1 -0
- package/dist/core/run-workflow.js +28 -0
- package/dist/core/run-workflow.js.map +1 -0
- package/dist/core/source-control.d.ts +5 -0
- package/dist/core/source-control.d.ts.map +1 -0
- package/dist/core/source-control.js +19 -0
- package/dist/core/source-control.js.map +1 -0
- package/dist/core/step-config/agent.d.ts +49 -0
- package/dist/core/step-config/agent.d.ts.map +1 -0
- package/dist/core/step-config/agent.js +337 -0
- package/dist/core/step-config/agent.js.map +1 -0
- package/dist/core/step-config/assemble-run-context.d.ts +94 -0
- package/dist/core/step-config/assemble-run-context.d.ts.map +1 -0
- package/dist/core/step-config/assemble-run-context.js +393 -0
- package/dist/core/step-config/assemble-run-context.js.map +1 -0
- package/dist/core/step-config/complete-step-dispatch-config.d.ts +13 -0
- package/dist/core/step-config/complete-step-dispatch-config.d.ts.map +1 -0
- package/dist/core/step-config/complete-step-dispatch-config.js +38 -0
- package/dist/core/step-config/complete-step-dispatch-config.js.map +1 -0
- package/dist/core/step-config/fields.d.ts +38 -0
- package/dist/core/step-config/fields.d.ts.map +1 -0
- package/dist/core/step-config/fields.js +72 -0
- package/dist/core/step-config/fields.js.map +1 -0
- package/dist/core/step-config/index.d.ts +8 -0
- package/dist/core/step-config/index.d.ts.map +1 -0
- package/dist/core/step-config/index.js +7 -0
- package/dist/core/step-config/index.js.map +1 -0
- package/dist/core/step-config/materialize-job-execution-steps.d.ts +35 -0
- package/dist/core/step-config/materialize-job-execution-steps.d.ts.map +1 -0
- package/dist/core/step-config/materialize-job-execution-steps.js +92 -0
- package/dist/core/step-config/materialize-job-execution-steps.js.map +1 -0
- package/dist/core/step-config/materialize-workflow-model.d.ts +42 -0
- package/dist/core/step-config/materialize-workflow-model.d.ts.map +1 -0
- package/dist/core/step-config/materialize-workflow-model.js +96 -0
- package/dist/core/step-config/materialize-workflow-model.js.map +1 -0
- package/dist/core/step-config/resolve-job-execution-name.d.ts +18 -0
- package/dist/core/step-config/resolve-job-execution-name.d.ts.map +1 -0
- package/dist/core/step-config/resolve-job-execution-name.js +36 -0
- package/dist/core/step-config/resolve-job-execution-name.js.map +1 -0
- package/dist/core/step-config/resolve-step-config.d.ts +33 -0
- package/dist/core/step-config/resolve-step-config.d.ts.map +1 -0
- package/dist/core/step-config/resolve-step-config.js +164 -0
- package/dist/core/step-config/resolve-step-config.js.map +1 -0
- package/dist/core/step-config/run.d.ts +39 -0
- package/dist/core/step-config/run.d.ts.map +1 -0
- package/dist/core/step-config/run.js +360 -0
- package/dist/core/step-config/run.js.map +1 -0
- package/dist/core/step-config/workflow-evaluation-context.d.ts +6 -0
- package/dist/core/step-config/workflow-evaluation-context.d.ts.map +1 -0
- package/dist/core/step-config/workflow-evaluation-context.js +3 -0
- package/dist/core/step-config/workflow-evaluation-context.js.map +1 -0
- package/dist/core/step-transition/apply-step-transition.d.ts +26 -0
- package/dist/core/step-transition/apply-step-transition.d.ts.map +1 -0
- package/dist/core/step-transition/apply-step-transition.js +141 -0
- package/dist/core/step-transition/apply-step-transition.js.map +1 -0
- package/dist/core/step-transition/decide-step-transition.d.ts +71 -0
- package/dist/core/step-transition/decide-step-transition.d.ts.map +1 -0
- package/dist/core/step-transition/decide-step-transition.js +110 -0
- package/dist/core/step-transition/decide-step-transition.js.map +1 -0
- package/dist/core/step-transition/evaluate-gate.d.ts +28 -0
- package/dist/core/step-transition/evaluate-gate.d.ts.map +1 -0
- package/dist/core/step-transition/evaluate-gate.js +159 -0
- package/dist/core/step-transition/evaluate-gate.js.map +1 -0
- package/dist/core/step-transition/read-step-outputs.d.ts +3 -0
- package/dist/core/step-transition/read-step-outputs.d.ts.map +1 -0
- package/dist/core/step-transition/read-step-outputs.js +24 -0
- package/dist/core/step-transition/read-step-outputs.js.map +1 -0
- package/dist/core/workflow-run-creation.d.ts +43 -0
- package/dist/core/workflow-run-creation.d.ts.map +1 -0
- package/dist/core/workflow-run-creation.js +80 -0
- package/dist/core/workflow-run-creation.js.map +1 -0
- package/dist/core/workflow-scheduling/index.d.ts +5 -0
- package/dist/core/workflow-scheduling/index.d.ts.map +1 -0
- package/dist/core/workflow-scheduling/index.js +4 -0
- package/dist/core/workflow-scheduling/index.js.map +1 -0
- package/dist/core/workflow-scheduling/run-progress.d.ts +20 -0
- package/dist/core/workflow-scheduling/run-progress.d.ts.map +1 -0
- package/dist/core/workflow-scheduling/run-progress.js +40 -0
- package/dist/core/workflow-scheduling/run-progress.js.map +1 -0
- package/dist/core/workflow-scheduling/runtime-dag.d.ts +10 -0
- package/dist/core/workflow-scheduling/runtime-dag.d.ts.map +1 -0
- package/dist/core/workflow-scheduling/runtime-dag.js +3 -0
- package/dist/core/workflow-scheduling/runtime-dag.js.map +1 -0
- package/dist/core/workflow-scheduling/runtime-scheduling-command.d.ts +16 -0
- package/dist/core/workflow-scheduling/runtime-scheduling-command.d.ts.map +1 -0
- package/dist/core/workflow-scheduling/runtime-scheduling-command.js +3 -0
- package/dist/core/workflow-scheduling/runtime-scheduling-command.js.map +1 -0
- package/dist/core/workflow-scheduling/schedule-runtime-dag.d.ts +9 -0
- package/dist/core/workflow-scheduling/schedule-runtime-dag.d.ts.map +1 -0
- package/dist/core/workflow-scheduling/schedule-runtime-dag.js +72 -0
- package/dist/core/workflow-scheduling/schedule-runtime-dag.js.map +1 -0
- package/dist/db/db.d.ts +4959 -0
- package/dist/db/db.d.ts.map +1 -0
- package/dist/db/db.js +37 -0
- package/dist/db/db.js.map +1 -0
- package/dist/db/index.d.ts +9 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +10 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/job-listener-events.d.ts +18 -0
- package/dist/db/job-listener-events.d.ts.map +1 -0
- package/dist/db/job-listener-events.js +59 -0
- package/dist/db/job-listener-events.js.map +1 -0
- package/dist/db/job-listeners.d.ts +55 -0
- package/dist/db/job-listeners.d.ts.map +1 -0
- package/dist/db/job-listeners.js +358 -0
- package/dist/db/job-listeners.js.map +1 -0
- package/dist/db/outbox-writes.d.ts +12 -0
- package/dist/db/outbox-writes.d.ts.map +1 -0
- package/dist/db/outbox-writes.js +20 -0
- package/dist/db/outbox-writes.js.map +1 -0
- package/dist/db/runner-lease-table.d.ts +469 -0
- package/dist/db/runner-lease-table.d.ts.map +1 -0
- package/dist/db/runner-lease-table.js +42 -0
- package/dist/db/runner-lease-table.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/job-executions.d.ts +311 -0
- package/dist/db/schema/job-executions.d.ts.map +1 -0
- package/dist/db/schema/job-executions.js +76 -0
- package/dist/db/schema/job-executions.js.map +1 -0
- package/dist/db/schema/job-listener-events.d.ts +200 -0
- package/dist/db/schema/job-listener-events.d.ts.map +1 -0
- package/dist/db/schema/job-listener-events.js +50 -0
- package/dist/db/schema/job-listener-events.js.map +1 -0
- package/dist/db/schema/jobs.d.ts +542 -0
- package/dist/db/schema/jobs.d.ts.map +1 -0
- package/dist/db/schema/jobs.js +121 -0
- package/dist/db/schema/jobs.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/step-attempts.d.ts +330 -0
- package/dist/db/schema/step-attempts.d.ts.map +1 -0
- package/dist/db/schema/step-attempts.js +80 -0
- package/dist/db/schema/step-attempts.js.map +1 -0
- package/dist/db/schema/steps.d.ts +352 -0
- package/dist/db/schema/steps.d.ts.map +1 -0
- package/dist/db/schema/steps.js +80 -0
- package/dist/db/schema/steps.js.map +1 -0
- package/dist/db/schema/workflow-run-attempts.d.ts +239 -0
- package/dist/db/schema/workflow-run-attempts.d.ts.map +1 -0
- package/dist/db/schema/workflow-run-attempts.js +53 -0
- package/dist/db/schema/workflow-run-attempts.js.map +1 -0
- package/dist/db/schema/workflow-runs.d.ts +360 -0
- package/dist/db/schema/workflow-runs.d.ts.map +1 -0
- package/dist/db/schema/workflow-runs.js +85 -0
- package/dist/db/schema/workflow-runs.js.map +1 -0
- package/dist/db/workflow-runs/job-executions.d.ts +49 -0
- package/dist/db/workflow-runs/job-executions.d.ts.map +1 -0
- package/dist/db/workflow-runs/job-executions.js +271 -0
- package/dist/db/workflow-runs/job-executions.js.map +1 -0
- package/dist/db/workflow-runs/jobs.d.ts +60 -0
- package/dist/db/workflow-runs/jobs.d.ts.map +1 -0
- package/dist/db/workflow-runs/jobs.js +288 -0
- package/dist/db/workflow-runs/jobs.js.map +1 -0
- package/dist/db/workflow-runs/outbox.d.ts +20 -0
- package/dist/db/workflow-runs/outbox.d.ts.map +1 -0
- package/dist/db/workflow-runs/outbox.js +68 -0
- package/dist/db/workflow-runs/outbox.js.map +1 -0
- package/dist/db/workflow-runs/queries.d.ts +71 -0
- package/dist/db/workflow-runs/queries.d.ts.map +1 -0
- package/dist/db/workflow-runs/queries.js +253 -0
- package/dist/db/workflow-runs/queries.js.map +1 -0
- package/dist/db/workflow-runs/run-create.d.ts +32 -0
- package/dist/db/workflow-runs/run-create.d.ts.map +1 -0
- package/dist/db/workflow-runs/run-create.js +288 -0
- package/dist/db/workflow-runs/run-create.js.map +1 -0
- package/dist/db/workflow-runs/run-graph.d.ts +27 -0
- package/dist/db/workflow-runs/run-graph.d.ts.map +1 -0
- package/dist/db/workflow-runs/run-graph.js +63 -0
- package/dist/db/workflow-runs/run-graph.js.map +1 -0
- package/dist/db/workflow-runs/run-rerun.d.ts +8 -0
- package/dist/db/workflow-runs/run-rerun.d.ts.map +1 -0
- package/dist/db/workflow-runs/run-rerun.js +207 -0
- package/dist/db/workflow-runs/run-rerun.js.map +1 -0
- package/dist/db/workflow-runs/run-status.d.ts +24 -0
- package/dist/db/workflow-runs/run-status.d.ts.map +1 -0
- package/dist/db/workflow-runs/run-status.js +249 -0
- package/dist/db/workflow-runs/run-status.js.map +1 -0
- package/dist/db/workflow-runs/runs.d.ts +7 -0
- package/dist/db/workflow-runs/runs.d.ts.map +1 -0
- package/dist/db/workflow-runs/runs.js +5 -0
- package/dist/db/workflow-runs/runs.js.map +1 -0
- package/dist/db/workflow-runs/shared.d.ts +24 -0
- package/dist/db/workflow-runs/shared.d.ts.map +1 -0
- package/dist/db/workflow-runs/shared.js +50 -0
- package/dist/db/workflow-runs/shared.js.map +1 -0
- package/dist/db/workflow-runs/steps.d.ts +83 -0
- package/dist/db/workflow-runs/steps.d.ts.map +1 -0
- package/dist/db/workflow-runs/steps.js +246 -0
- package/dist/db/workflow-runs/steps.js.map +1 -0
- package/dist/db/workflow-runs.d.ts +12 -0
- package/dist/db/workflow-runs.d.ts.map +1 -0
- package/dist/db/workflow-runs.js +8 -0
- package/dist/db/workflow-runs.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +54 -0
- package/dist/index.js.map +1 -0
- package/dist/metrics/index.d.ts +2 -0
- package/dist/metrics/index.d.ts.map +1 -0
- package/dist/metrics/index.js +3 -0
- package/dist/metrics/index.js.map +1 -0
- package/dist/metrics/instance.d.ts +20 -0
- package/dist/metrics/instance.d.ts.map +1 -0
- package/dist/metrics/instance.js +127 -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 +30 -0
- package/dist/metrics/service.js.map +1 -0
- package/dist/presentation/dto/checkout-token.d.ts +6 -0
- package/dist/presentation/dto/checkout-token.d.ts.map +1 -0
- package/dist/presentation/dto/checkout-token.js +74 -0
- package/dist/presentation/dto/checkout-token.js.map +1 -0
- package/dist/presentation/dto/index.d.ts +4 -0
- package/dist/presentation/dto/index.d.ts.map +1 -0
- package/dist/presentation/dto/index.js +5 -0
- package/dist/presentation/dto/index.js.map +1 -0
- package/dist/presentation/dto/job.d.ts +6 -0
- package/dist/presentation/dto/job.d.ts.map +1 -0
- package/dist/presentation/dto/job.js +59 -0
- package/dist/presentation/dto/job.js.map +1 -0
- package/dist/presentation/dto/step.d.ts +6 -0
- package/dist/presentation/dto/step.d.ts.map +1 -0
- package/dist/presentation/dto/step.js +162 -0
- package/dist/presentation/dto/step.js.map +1 -0
- package/dist/presentation/dto/workflow-run.d.ts +6 -0
- package/dist/presentation/dto/workflow-run.d.ts.map +1 -0
- package/dist/presentation/dto/workflow-run.js +35 -0
- package/dist/presentation/dto/workflow-run.js.map +1 -0
- package/dist/presentation/index.d.ts +3 -0
- package/dist/presentation/index.d.ts.map +1 -0
- package/dist/presentation/index.js +4 -0
- package/dist/presentation/index.js.map +1 -0
- package/dist/presentation/routes/agent-runtime-config.d.ts +2 -0
- package/dist/presentation/routes/agent-runtime-config.d.ts.map +1 -0
- package/dist/presentation/routes/agent-runtime-config.js +71 -0
- package/dist/presentation/routes/agent-runtime-config.js.map +1 -0
- package/dist/presentation/routes/cancel-run.d.ts +2 -0
- package/dist/presentation/routes/cancel-run.d.ts.map +1 -0
- package/dist/presentation/routes/cancel-run.js +46 -0
- package/dist/presentation/routes/cancel-run.js.map +1 -0
- package/dist/presentation/routes/checkout-token.d.ts +2 -0
- package/dist/presentation/routes/checkout-token.d.ts.map +1 -0
- package/dist/presentation/routes/checkout-token.js +64 -0
- package/dist/presentation/routes/checkout-token.js.map +1 -0
- package/dist/presentation/routes/get-run-aggregates.d.ts +2 -0
- package/dist/presentation/routes/get-run-aggregates.d.ts.map +1 -0
- package/dist/presentation/routes/get-run-aggregates.js +60 -0
- package/dist/presentation/routes/get-run-aggregates.js.map +1 -0
- package/dist/presentation/routes/get-run.d.ts +2 -0
- package/dist/presentation/routes/get-run.d.ts.map +1 -0
- package/dist/presentation/routes/get-run.js +60 -0
- package/dist/presentation/routes/get-run.js.map +1 -0
- package/dist/presentation/routes/get-step-secrets.d.ts +2 -0
- package/dist/presentation/routes/get-step-secrets.d.ts.map +1 -0
- package/dist/presentation/routes/get-step-secrets.js +115 -0
- package/dist/presentation/routes/get-step-secrets.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 +41 -0
- package/dist/presentation/routes/index.js.map +1 -0
- package/dist/presentation/routes/leased-step.d.ts +14 -0
- package/dist/presentation/routes/leased-step.d.ts.map +1 -0
- package/dist/presentation/routes/leased-step.js +50 -0
- package/dist/presentation/routes/leased-step.js.map +1 -0
- package/dist/presentation/routes/list-run-attempts.d.ts +2 -0
- package/dist/presentation/routes/list-run-attempts.d.ts.map +1 -0
- package/dist/presentation/routes/list-run-attempts.js +35 -0
- package/dist/presentation/routes/list-run-attempts.js.map +1 -0
- package/dist/presentation/routes/list-runs.d.ts +2 -0
- package/dist/presentation/routes/list-runs.d.ts.map +1 -0
- package/dist/presentation/routes/list-runs.js +70 -0
- package/dist/presentation/routes/list-runs.js.map +1 -0
- package/dist/presentation/routes/next-step.d.ts +2 -0
- package/dist/presentation/routes/next-step.d.ts.map +1 -0
- package/dist/presentation/routes/next-step.js +65 -0
- package/dist/presentation/routes/next-step.js.map +1 -0
- package/dist/presentation/routes/report-step.d.ts +2 -0
- package/dist/presentation/routes/report-step.d.ts.map +1 -0
- package/dist/presentation/routes/report-step.js +73 -0
- package/dist/presentation/routes/report-step.js.map +1 -0
- package/dist/presentation/routes/require-accessible-run.d.ts +7 -0
- package/dist/presentation/routes/require-accessible-run.d.ts.map +1 -0
- package/dist/presentation/routes/require-accessible-run.js +25 -0
- package/dist/presentation/routes/require-accessible-run.js.map +1 -0
- package/dist/presentation/routes/rerun-run.d.ts +2 -0
- package/dist/presentation/routes/rerun-run.d.ts.map +1 -0
- package/dist/presentation/routes/rerun-run.js +56 -0
- package/dist/presentation/routes/rerun-run.js.map +1 -0
- package/dist/presentation/subscribers/index.d.ts +8 -0
- package/dist/presentation/subscribers/index.d.ts.map +1 -0
- package/dist/presentation/subscribers/index.js +9 -0
- package/dist/presentation/subscribers/index.js.map +1 -0
- package/dist/presentation/subscribers/on-job-event-delivered.d.ts +3 -0
- package/dist/presentation/subscribers/on-job-event-delivered.d.ts.map +1 -0
- package/dist/presentation/subscribers/on-job-event-delivered.js +24 -0
- package/dist/presentation/subscribers/on-job-event-delivered.js.map +1 -0
- package/dist/presentation/subscribers/on-job-steps-settled.d.ts +3 -0
- package/dist/presentation/subscribers/on-job-steps-settled.d.ts.map +1 -0
- package/dist/presentation/subscribers/on-job-steps-settled.js +36 -0
- package/dist/presentation/subscribers/on-job-steps-settled.js.map +1 -0
- package/dist/presentation/subscribers/on-runner-job-claimed.d.ts +3 -0
- package/dist/presentation/subscribers/on-runner-job-claimed.d.ts.map +1 -0
- package/dist/presentation/subscribers/on-runner-job-claimed.js +20 -0
- package/dist/presentation/subscribers/on-runner-job-claimed.js.map +1 -0
- package/dist/presentation/subscribers/on-runner-job-lease-expired.d.ts +3 -0
- package/dist/presentation/subscribers/on-runner-job-lease-expired.d.ts.map +1 -0
- package/dist/presentation/subscribers/on-runner-job-lease-expired.js +35 -0
- package/dist/presentation/subscribers/on-runner-job-lease-expired.js.map +1 -0
- package/dist/presentation/subscribers/on-runner-job-queued.d.ts +3 -0
- package/dist/presentation/subscribers/on-runner-job-queued.d.ts.map +1 -0
- package/dist/presentation/subscribers/on-runner-job-queued.js +20 -0
- package/dist/presentation/subscribers/on-runner-job-queued.js.map +1 -0
- package/dist/presentation/subscribers/on-workflow-run-attempt-created.d.ts +3 -0
- package/dist/presentation/subscribers/on-workflow-run-attempt-created.d.ts.map +1 -0
- package/dist/presentation/subscribers/on-workflow-run-attempt-created.js +35 -0
- package/dist/presentation/subscribers/on-workflow-run-attempt-created.js.map +1 -0
- package/dist/presentation/subscribers/on-workflow-run-cancelled.d.ts +3 -0
- package/dist/presentation/subscribers/on-workflow-run-cancelled.d.ts.map +1 -0
- package/dist/presentation/subscribers/on-workflow-run-cancelled.js +25 -0
- package/dist/presentation/subscribers/on-workflow-run-cancelled.js.map +1 -0
- package/dist/temporal/activities/index.d.ts +23 -0
- package/dist/temporal/activities/index.d.ts.map +1 -0
- package/dist/temporal/activities/index.js +26 -0
- package/dist/temporal/activities/index.js.map +1 -0
- package/dist/temporal/activities/orchestration-activities.d.ts +130 -0
- package/dist/temporal/activities/orchestration-activities.d.ts.map +1 -0
- package/dist/temporal/activities/orchestration-activities.js +188 -0
- package/dist/temporal/activities/orchestration-activities.js.map +1 -0
- package/dist/temporal/constants.d.ts +7 -0
- package/dist/temporal/constants.d.ts.map +1 -0
- package/dist/temporal/constants.js +8 -0
- package/dist/temporal/constants.js.map +1 -0
- package/dist/temporal/index.d.ts +3 -0
- package/dist/temporal/index.d.ts.map +1 -0
- package/dist/temporal/index.js +4 -0
- package/dist/temporal/index.js.map +1 -0
- package/dist/temporal/workflow-not-found.d.ts +2 -0
- package/dist/temporal/workflow-not-found.d.ts.map +1 -0
- package/dist/temporal/workflow-not-found.js +10 -0
- package/dist/temporal/workflow-not-found.js.map +1 -0
- package/dist/temporal/workflows/deadline.d.ts +3 -0
- package/dist/temporal/workflows/deadline.d.ts.map +1 -0
- package/dist/temporal/workflows/deadline.js +8 -0
- package/dist/temporal/workflows/deadline.js.map +1 -0
- package/dist/temporal/workflows/index.d.ts +4 -0
- package/dist/temporal/workflows/index.d.ts.map +1 -0
- package/dist/temporal/workflows/index.js +5 -0
- package/dist/temporal/workflows/index.js.map +1 -0
- package/dist/temporal/workflows/job-execution-orchestration.d.ts +27 -0
- package/dist/temporal/workflows/job-execution-orchestration.d.ts.map +1 -0
- package/dist/temporal/workflows/job-execution-orchestration.js +265 -0
- package/dist/temporal/workflows/job-execution-orchestration.js.map +1 -0
- package/dist/temporal/workflows/job-listener-orchestration.d.ts +40 -0
- package/dist/temporal/workflows/job-listener-orchestration.d.ts.map +1 -0
- package/dist/temporal/workflows/job-listener-orchestration.js +332 -0
- package/dist/temporal/workflows/job-listener-orchestration.js.map +1 -0
- package/dist/temporal/workflows/run-orchestration.d.ts +8 -0
- package/dist/temporal/workflows/run-orchestration.d.ts.map +1 -0
- package/dist/temporal/workflows/run-orchestration.js +243 -0
- package/dist/temporal/workflows/run-orchestration.js.map +1 -0
- package/dist/temporal/workflows/test-env.d.ts +114 -0
- package/dist/temporal/workflows/test-env.d.ts.map +1 -0
- package/dist/temporal/workflows/test-env.js +323 -0
- package/dist/temporal/workflows/test-env.js.map +1 -0
- package/dist/tsconfig.test.tsbuildinfo +1 -0
- package/drizzle/0000_initial.sql +211 -0
- package/drizzle/0001_agent_tool_materialization.sql +1 -0
- package/drizzle/meta/0000_snapshot.json +1613 -0
- package/drizzle/meta/_journal.json +20 -0
- package/drizzle.config.ts +7 -0
- package/package.json +91 -0
- package/src/core/README.md +79 -0
- package/src/core/agent-tool-capability-warning.test.ts +280 -0
- package/src/core/agent-tool-capability-warning.ts +200 -0
- package/src/core/agent-tools.ts +422 -0
- package/src/core/checkout.test.ts +112 -0
- package/src/core/checkout.ts +63 -0
- package/src/core/condition-trace.ts +64 -0
- package/src/core/entities/index.ts +9 -0
- package/src/core/entities/job-execution.ts +32 -0
- package/src/core/entities/job-listener-event.ts +15 -0
- package/src/core/entities/job.ts +91 -0
- package/src/core/entities/step.ts +126 -0
- package/src/core/entities/workflow-run-attempt.ts +21 -0
- package/src/core/entities/workflow-run.ts +94 -0
- package/src/core/errors.test.ts +45 -0
- package/src/core/errors.ts +209 -0
- package/src/core/index.ts +54 -0
- package/src/core/job-execution-outcome.ts +64 -0
- package/src/core/job-execution.test.ts +1560 -0
- package/src/core/job-execution.ts +581 -0
- package/src/core/job-transition/decide-job-activation.test.ts +196 -0
- package/src/core/job-transition/decide-job-activation.ts +79 -0
- package/src/core/job-transition/derive-job-execution-outputs.ts +41 -0
- package/src/core/job-transition/derive-job-success.test.ts +133 -0
- package/src/core/job-transition/derive-job-success.ts +67 -0
- package/src/core/job-transition/index.ts +11 -0
- package/src/core/job-transition/runtime-completion-status-for-job.ts +14 -0
- package/src/core/listener-execution-materialization.test.ts +49 -0
- package/src/core/listener-execution-materialization.ts +162 -0
- package/src/core/run-workflow.test.ts +229 -0
- package/src/core/run-workflow.ts +41 -0
- package/src/core/source-control.test.ts +17 -0
- package/src/core/source-control.ts +22 -0
- package/src/core/step-config/agent.ts +459 -0
- package/src/core/step-config/assemble-run-context.test.ts +1021 -0
- package/src/core/step-config/assemble-run-context.ts +535 -0
- package/src/core/step-config/complete-step-dispatch-config.test.ts +354 -0
- package/src/core/step-config/complete-step-dispatch-config.ts +40 -0
- package/src/core/step-config/fields.ts +111 -0
- package/src/core/step-config/index.ts +41 -0
- package/src/core/step-config/materialize-job-execution-steps.test.ts +494 -0
- package/src/core/step-config/materialize-job-execution-steps.ts +136 -0
- package/src/core/step-config/materialize-workflow-model.test.ts +1214 -0
- package/src/core/step-config/materialize-workflow-model.ts +140 -0
- package/src/core/step-config/resolve-job-execution-name.test.ts +94 -0
- package/src/core/step-config/resolve-job-execution-name.ts +51 -0
- package/src/core/step-config/resolve-step-config.ts +199 -0
- package/src/core/step-config/run.ts +467 -0
- package/src/core/step-config/workflow-evaluation-context.ts +6 -0
- package/src/core/step-transition/apply-step-transition.ts +163 -0
- package/src/core/step-transition/decide-step-transition.test.ts +307 -0
- package/src/core/step-transition/decide-step-transition.ts +200 -0
- package/src/core/step-transition/evaluate-gate.test.ts +317 -0
- package/src/core/step-transition/evaluate-gate.ts +168 -0
- package/src/core/step-transition/read-step-outputs.ts +27 -0
- package/src/core/workflow-run-creation.test.ts +155 -0
- package/src/core/workflow-run-creation.ts +131 -0
- package/src/core/workflow-scheduling/index.ts +15 -0
- package/src/core/workflow-scheduling/run-progress.test.ts +46 -0
- package/src/core/workflow-scheduling/run-progress.ts +68 -0
- package/src/core/workflow-scheduling/runtime-dag.ts +10 -0
- package/src/core/workflow-scheduling/runtime-scheduling-command.ts +20 -0
- package/src/core/workflow-scheduling/schedule-runtime-dag.test.ts +197 -0
- package/src/core/workflow-scheduling/schedule-runtime-dag.ts +115 -0
- package/src/db/db.ts +40 -0
- package/src/db/index.ts +86 -0
- package/src/db/job-listener-events.test.ts +113 -0
- package/src/db/job-listener-events.ts +73 -0
- package/src/db/job-listeners.test.ts +865 -0
- package/src/db/job-listeners.ts +564 -0
- package/src/db/outbox-writes.ts +40 -0
- package/src/db/rerun-listener.test.ts +64 -0
- package/src/db/run-timeout.test.ts +102 -0
- package/src/db/runner-lease-table.ts +39 -0
- package/src/db/schema/common.ts +3 -0
- package/src/db/schema/job-executions.ts +85 -0
- package/src/db/schema/job-listener-events.ts +58 -0
- package/src/db/schema/jobs.ts +136 -0
- package/src/db/schema/outbox.ts +4 -0
- package/src/db/schema/step-attempts.ts +99 -0
- package/src/db/schema/steps.ts +99 -0
- package/src/db/schema/workflow-run-attempts.ts +59 -0
- package/src/db/schema/workflow-runs.ts +112 -0
- package/src/db/workflow-runs/job-executions.test.ts +99 -0
- package/src/db/workflow-runs/job-executions.ts +421 -0
- package/src/db/workflow-runs/job-status.test.ts +532 -0
- package/src/db/workflow-runs/jobs.test.ts +194 -0
- package/src/db/workflow-runs/jobs.ts +418 -0
- package/src/db/workflow-runs/outbox.ts +109 -0
- package/src/db/workflow-runs/queries.test.ts +256 -0
- package/src/db/workflow-runs/queries.ts +437 -0
- package/src/db/workflow-runs/run-create.test.ts +1281 -0
- package/src/db/workflow-runs/run-create.ts +373 -0
- package/src/db/workflow-runs/run-graph.ts +95 -0
- package/src/db/workflow-runs/run-rerun.test.ts +625 -0
- package/src/db/workflow-runs/run-rerun.ts +266 -0
- package/src/db/workflow-runs/run-status.test.ts +425 -0
- package/src/db/workflow-runs/run-status.ts +391 -0
- package/src/db/workflow-runs/runs.ts +17 -0
- package/src/db/workflow-runs/shared.ts +84 -0
- package/src/db/workflow-runs/steps.test.ts +191 -0
- package/src/db/workflow-runs/steps.ts +476 -0
- package/src/db/workflow-runs.ts +111 -0
- package/src/index.ts +99 -0
- package/src/metrics/index.ts +1 -0
- package/src/metrics/instance.ts +160 -0
- package/src/metrics/service.ts +30 -0
- package/src/presentation/dto/checkout-token.test.ts +107 -0
- package/src/presentation/dto/checkout-token.ts +85 -0
- package/src/presentation/dto/index.ts +3 -0
- package/src/presentation/dto/job.test.ts +108 -0
- package/src/presentation/dto/job.ts +64 -0
- package/src/presentation/dto/step.test.ts +307 -0
- package/src/presentation/dto/step.ts +145 -0
- package/src/presentation/dto/workflow-run.ts +38 -0
- package/src/presentation/index.ts +10 -0
- package/src/presentation/routes/agent-runtime-config.test.ts +478 -0
- package/src/presentation/routes/agent-runtime-config.ts +81 -0
- package/src/presentation/routes/cancel-run.test.ts +156 -0
- package/src/presentation/routes/cancel-run.ts +37 -0
- package/src/presentation/routes/checkout-token.test.ts +366 -0
- package/src/presentation/routes/checkout-token.ts +61 -0
- package/src/presentation/routes/get-run-aggregates.test.ts +114 -0
- package/src/presentation/routes/get-run-aggregates.ts +69 -0
- package/src/presentation/routes/get-run.test.ts +604 -0
- package/src/presentation/routes/get-run.ts +66 -0
- package/src/presentation/routes/get-step-secrets.test.ts +260 -0
- package/src/presentation/routes/get-step-secrets.ts +117 -0
- package/src/presentation/routes/index.test.ts +71 -0
- package/src/presentation/routes/index.ts +42 -0
- package/src/presentation/routes/leased-step.ts +54 -0
- package/src/presentation/routes/list-run-attempts.test.ts +163 -0
- package/src/presentation/routes/list-run-attempts.ts +27 -0
- package/src/presentation/routes/list-runs.test.ts +244 -0
- package/src/presentation/routes/list-runs.ts +83 -0
- package/src/presentation/routes/next-step.test.ts +366 -0
- package/src/presentation/routes/next-step.ts +62 -0
- package/src/presentation/routes/report-step.test.ts +374 -0
- package/src/presentation/routes/report-step.ts +61 -0
- package/src/presentation/routes/require-accessible-run.ts +27 -0
- package/src/presentation/routes/rerun-run.test.ts +202 -0
- package/src/presentation/routes/rerun-run.ts +48 -0
- package/src/presentation/subscribers/index.ts +7 -0
- package/src/presentation/subscribers/on-job-event-delivered.test.ts +71 -0
- package/src/presentation/subscribers/on-job-event-delivered.ts +30 -0
- package/src/presentation/subscribers/on-job-steps-settled.test.ts +75 -0
- package/src/presentation/subscribers/on-job-steps-settled.ts +42 -0
- package/src/presentation/subscribers/on-runner-job-claimed.test.ts +57 -0
- package/src/presentation/subscribers/on-runner-job-claimed.ts +23 -0
- package/src/presentation/subscribers/on-runner-job-lease-expired.test.ts +73 -0
- package/src/presentation/subscribers/on-runner-job-lease-expired.ts +36 -0
- package/src/presentation/subscribers/on-runner-job-queued.test.ts +57 -0
- package/src/presentation/subscribers/on-runner-job-queued.ts +23 -0
- package/src/presentation/subscribers/on-workflow-run-attempt-created.test.ts +69 -0
- package/src/presentation/subscribers/on-workflow-run-attempt-created.ts +40 -0
- package/src/presentation/subscribers/on-workflow-run-cancelled.test.ts +59 -0
- package/src/presentation/subscribers/on-workflow-run-cancelled.ts +29 -0
- package/src/temporal/activities/index.ts +45 -0
- package/src/temporal/activities/orchestration-activities.test.ts +115 -0
- package/src/temporal/activities/orchestration-activities.ts +305 -0
- package/src/temporal/constants.ts +6 -0
- package/src/temporal/index.ts +6 -0
- package/src/temporal/workflow-not-found.ts +9 -0
- package/src/temporal/workflows/deadline.ts +7 -0
- package/src/temporal/workflows/index.ts +11 -0
- package/src/temporal/workflows/job-execution-orchestration-timeout.test.ts +165 -0
- package/src/temporal/workflows/job-execution-orchestration.test.ts +311 -0
- package/src/temporal/workflows/job-execution-orchestration.ts +314 -0
- package/src/temporal/workflows/job-listener-orchestration-continuation.test.ts +93 -0
- package/src/temporal/workflows/job-listener-orchestration.test.ts +406 -0
- package/src/temporal/workflows/job-listener-orchestration.ts +490 -0
- package/src/temporal/workflows/run-orchestration.test.ts +409 -0
- package/src/temporal/workflows/run-orchestration.ts +283 -0
- package/src/temporal/workflows/test-env.ts +409 -0
- package/test/env.ts +11 -0
- package/test/factories/job.ts +83 -0
- package/test/factories/project.ts +16 -0
- package/test/factories/workflow-model.test.ts +26 -0
- package/test/factories/workflow-model.ts +268 -0
- package/test/factories/workflow-run.ts +58 -0
- package/test/fixtures/active-lease-token.ts +85 -0
- package/test/fixtures/job-with-steps.ts +42 -0
- package/test/fixtures/lease-token.ts +44 -0
- package/test/fixtures/strip-setup-step.ts +23 -0
- package/test/globalSetup.ts +54 -0
- package/test/helpers/workflow-runs.ts +210 -0
- package/test/index.ts +3 -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 +12 -0
|
@@ -0,0 +1,865 @@
|
|
|
1
|
+
import {
|
|
2
|
+
WORKFLOWS_JOB_ACTIVATED,
|
|
3
|
+
type WorkflowsJobActivatedEventDto,
|
|
4
|
+
} from '@shipfox/api-workflows-dto';
|
|
5
|
+
import {createWorkflowExpression} from '@shipfox/expression';
|
|
6
|
+
import {and, asc, eq, isNull} from 'drizzle-orm';
|
|
7
|
+
import type {JobListeningTrigger, JobStatus} from '#core/entities/job.js';
|
|
8
|
+
import type {JobExecutionStatus} from '#core/entities/job-execution.js';
|
|
9
|
+
import {nextStepForJob, recordStepResult} from '#core/job-execution.js';
|
|
10
|
+
import {db} from '#db/db.js';
|
|
11
|
+
import {deliverEventToListener} from '#db/job-listener-events.js';
|
|
12
|
+
import {
|
|
13
|
+
activateJobListener,
|
|
14
|
+
drainListenerEventsIntoExecution,
|
|
15
|
+
peekListenerBuffer,
|
|
16
|
+
resolveJobListener,
|
|
17
|
+
} from '#db/job-listeners.js';
|
|
18
|
+
import {jobExecutions} from '#db/schema/job-executions.js';
|
|
19
|
+
import {jobListenerEvents} from '#db/schema/job-listener-events.js';
|
|
20
|
+
import {jobs} from '#db/schema/jobs.js';
|
|
21
|
+
import {workflowsOutbox} from '#db/schema/outbox.js';
|
|
22
|
+
import {steps} from '#db/schema/steps.js';
|
|
23
|
+
import {workflowRunAttempts} from '#db/schema/workflow-run-attempts.js';
|
|
24
|
+
import {jobFactory, workflowModel, workflowRunFactory} from '#test/index.js';
|
|
25
|
+
import {getJobsByWorkflowRunId, updateJobExecutionStatus} from './workflow-runs.js';
|
|
26
|
+
|
|
27
|
+
interface ListeningJobOptions {
|
|
28
|
+
status?: JobStatus;
|
|
29
|
+
listenerStatus?: 'inactive' | 'listening' | 'resolved';
|
|
30
|
+
key?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function createListeningJob(options: ListeningJobOptions = {}) {
|
|
34
|
+
const job = await jobFactory.create({}, {transient: {status: options.status ?? 'running'}});
|
|
35
|
+
await db()
|
|
36
|
+
.update(jobs)
|
|
37
|
+
.set({
|
|
38
|
+
mode: 'listening',
|
|
39
|
+
listenerStatus: options.listenerStatus ?? 'listening',
|
|
40
|
+
...(options.key === undefined ? {} : {key: options.key}),
|
|
41
|
+
})
|
|
42
|
+
.where(eq(jobs.id, job.id));
|
|
43
|
+
// A real listener starts with no firings; the factory seeds a one_shot execution.
|
|
44
|
+
await db().delete(jobExecutions).where(eq(jobExecutions.jobId, job.id));
|
|
45
|
+
return job;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function createListeningJobFromModel(model: Parameters<typeof workflowModel>[0]) {
|
|
49
|
+
const materializationModel = workflowModel(model);
|
|
50
|
+
const modelJob = materializationModel.jobs[0];
|
|
51
|
+
if (!modelJob) throw new Error('createListeningJobFromModel: model has no jobs');
|
|
52
|
+
const run = await workflowRunFactory.create();
|
|
53
|
+
const [job] = await getJobsByWorkflowRunId(run.id);
|
|
54
|
+
if (!job) throw new Error('createListeningJobFromModel: run created no jobs');
|
|
55
|
+
await db()
|
|
56
|
+
.update(workflowRunAttempts)
|
|
57
|
+
.set({model: materializationModel})
|
|
58
|
+
.where(eq(workflowRunAttempts.id, job.workflowRunAttemptId));
|
|
59
|
+
await db()
|
|
60
|
+
.update(jobs)
|
|
61
|
+
.set({
|
|
62
|
+
key: modelJob.key,
|
|
63
|
+
mode: 'listening',
|
|
64
|
+
status: 'running',
|
|
65
|
+
listenerStatus: 'listening',
|
|
66
|
+
})
|
|
67
|
+
.where(eq(jobs.id, job.id));
|
|
68
|
+
await db().delete(jobExecutions).where(eq(jobExecutions.jobId, job.id));
|
|
69
|
+
return {...job, key: modelJob.key, mode: 'listening' as const};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function insertExecution(jobId: string, sequence: number, status: JobExecutionStatus) {
|
|
73
|
+
const [row] = await db()
|
|
74
|
+
.insert(jobExecutions)
|
|
75
|
+
.values({jobId, sequence, name: `firing #${sequence}`, status, triggerEvents: []})
|
|
76
|
+
.returning();
|
|
77
|
+
if (!row) throw new Error('insertExecution: no row returned');
|
|
78
|
+
return row;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function bufferEvent(
|
|
82
|
+
jobId: string,
|
|
83
|
+
disposition: 'fire' | 'resolve' = 'fire',
|
|
84
|
+
eventRef = crypto.randomUUID(),
|
|
85
|
+
receivedAt = new Date('2026-01-01T00:00:00.000Z'),
|
|
86
|
+
) {
|
|
87
|
+
return deliverEventToListener({
|
|
88
|
+
jobId,
|
|
89
|
+
disposition,
|
|
90
|
+
eventRef,
|
|
91
|
+
deliveryId: crypto.randomUUID(),
|
|
92
|
+
source: 'github',
|
|
93
|
+
event: 'pull_request',
|
|
94
|
+
provider: 'github',
|
|
95
|
+
payload: {action: 'opened'},
|
|
96
|
+
receivedAt,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function readJob(jobId: string) {
|
|
101
|
+
return db()
|
|
102
|
+
.select()
|
|
103
|
+
.from(jobs)
|
|
104
|
+
.where(eq(jobs.id, jobId))
|
|
105
|
+
.limit(1)
|
|
106
|
+
.then((rows) => rows[0]);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function activatedPayload(jobId: string): Promise<WorkflowsJobActivatedEventDto> {
|
|
110
|
+
const rows = await db()
|
|
111
|
+
.select({payload: workflowsOutbox.payload})
|
|
112
|
+
.from(workflowsOutbox)
|
|
113
|
+
.where(eq(workflowsOutbox.eventType, WORKFLOWS_JOB_ACTIVATED));
|
|
114
|
+
const payload = rows
|
|
115
|
+
.map((row) => row.payload as WorkflowsJobActivatedEventDto)
|
|
116
|
+
.find((candidate) => candidate.jobId === jobId);
|
|
117
|
+
if (!payload) throw new Error(`No activated payload for job ${jobId}`);
|
|
118
|
+
return payload;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function expectListeningPayload(
|
|
122
|
+
payload: WorkflowsJobActivatedEventDto,
|
|
123
|
+
): asserts payload is Extract<WorkflowsJobActivatedEventDto, {mode: 'listening'}> {
|
|
124
|
+
expect(payload.mode).toBe('listening');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function createInactiveListeningJobWithMatchers(params: {
|
|
128
|
+
readonly on: readonly JobListeningTrigger[];
|
|
129
|
+
readonly until?: readonly JobListeningTrigger[] | null;
|
|
130
|
+
}) {
|
|
131
|
+
const job = await createListeningJob({
|
|
132
|
+
key: 'await',
|
|
133
|
+
status: 'pending',
|
|
134
|
+
listenerStatus: 'inactive',
|
|
135
|
+
});
|
|
136
|
+
await db()
|
|
137
|
+
.update(jobs)
|
|
138
|
+
.set({listeningOn: [...params.on], listeningUntil: params.until ? [...params.until] : null})
|
|
139
|
+
.where(eq(jobs.id, job.id));
|
|
140
|
+
const updated = await readJob(job.id);
|
|
141
|
+
if (!updated) throw new Error('Expected inactive listener job');
|
|
142
|
+
return updated;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function createListenerWithDependencies(params: {
|
|
146
|
+
readonly on: readonly JobListeningTrigger[];
|
|
147
|
+
}) {
|
|
148
|
+
const run = await workflowRunFactory.create(
|
|
149
|
+
{
|
|
150
|
+
inputs: {environment: 'prod'},
|
|
151
|
+
triggerPayload: {
|
|
152
|
+
source: 'github',
|
|
153
|
+
event: 'pull_request',
|
|
154
|
+
deliveryId: 'delivery-1',
|
|
155
|
+
data: {action: 'opened'},
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
transient: {
|
|
160
|
+
model: workflowModel({
|
|
161
|
+
jobs: {
|
|
162
|
+
build: {steps: [{run: 'echo build'}]},
|
|
163
|
+
review: {steps: [{run: 'echo review'}]},
|
|
164
|
+
await: {needs: ['build', 'review'], steps: [{run: 'echo await'}]},
|
|
165
|
+
},
|
|
166
|
+
}),
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
);
|
|
170
|
+
const runJobs = await getJobsByWorkflowRunId(run.id);
|
|
171
|
+
const build = runJobs.find((job) => job.key === 'build');
|
|
172
|
+
const review = runJobs.find((job) => job.key === 'review');
|
|
173
|
+
const listener = runJobs.find((job) => job.key === 'await');
|
|
174
|
+
if (!build || !review || !listener) throw new Error('Expected dependency fixture jobs');
|
|
175
|
+
|
|
176
|
+
await db()
|
|
177
|
+
.update(jobs)
|
|
178
|
+
.set({status: 'succeeded', outputs: {pr_number: 42}})
|
|
179
|
+
.where(eq(jobs.id, build.id));
|
|
180
|
+
await db()
|
|
181
|
+
.update(jobs)
|
|
182
|
+
.set({status: 'succeeded', outputs: {pr_number: 99}})
|
|
183
|
+
.where(eq(jobs.id, review.id));
|
|
184
|
+
await db()
|
|
185
|
+
.update(jobs)
|
|
186
|
+
.set({
|
|
187
|
+
mode: 'listening',
|
|
188
|
+
status: 'pending',
|
|
189
|
+
listenerStatus: 'inactive',
|
|
190
|
+
listeningOn: [...params.on],
|
|
191
|
+
listeningUntil: null,
|
|
192
|
+
})
|
|
193
|
+
.where(eq(jobs.id, listener.id));
|
|
194
|
+
await db().delete(jobExecutions).where(eq(jobExecutions.jobId, listener.id));
|
|
195
|
+
|
|
196
|
+
const updated = await readJob(listener.id);
|
|
197
|
+
if (!updated) throw new Error('Expected listener fixture job');
|
|
198
|
+
return updated;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function template(source: string): string {
|
|
202
|
+
return `\${{ ${source} }}`;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function conditionExpression(source: string) {
|
|
206
|
+
return createWorkflowExpression({source, check: {mode: 'syntax'}});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
describe('activateJobListener', () => {
|
|
210
|
+
it('moves a pending listener to running and marks it listening', async () => {
|
|
211
|
+
const job = await createListeningJob({status: 'pending', listenerStatus: 'inactive'});
|
|
212
|
+
|
|
213
|
+
const result = await activateJobListener({jobId: job.id, expectedVersion: job.version});
|
|
214
|
+
|
|
215
|
+
const stored = await readJob(job.id);
|
|
216
|
+
expect(result).toMatchObject({status: 'running', jobStatus: 'running', executionCount: 0});
|
|
217
|
+
expect(stored?.status).toBe('running');
|
|
218
|
+
expect(stored?.listenerStatus).toBe('listening');
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('emits a job-activated outbox event on first activation only', async () => {
|
|
222
|
+
const job = await createListeningJob({status: 'pending', listenerStatus: 'inactive'});
|
|
223
|
+
|
|
224
|
+
await activateJobListener({jobId: job.id, expectedVersion: job.version});
|
|
225
|
+
const rerun = await readJob(job.id);
|
|
226
|
+
await activateJobListener({jobId: job.id, expectedVersion: rerun?.version ?? job.version});
|
|
227
|
+
|
|
228
|
+
const activatedEvents = await db()
|
|
229
|
+
.select()
|
|
230
|
+
.from(workflowsOutbox)
|
|
231
|
+
.where(eq(workflowsOutbox.eventType, WORKFLOWS_JOB_ACTIVATED));
|
|
232
|
+
const forJob = activatedEvents.filter(
|
|
233
|
+
(row) => (row.payload as Record<string, unknown>).jobId === job.id,
|
|
234
|
+
);
|
|
235
|
+
expect(forJob).toHaveLength(1);
|
|
236
|
+
expect(forJob[0]?.payload).toMatchObject({jobId: job.id, mode: 'listening'});
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('reports a terminal job and carries its status without activating', async () => {
|
|
240
|
+
const job = await createListeningJob({status: 'cancelled', listenerStatus: 'resolved'});
|
|
241
|
+
|
|
242
|
+
const result = await activateJobListener({jobId: job.id, expectedVersion: job.version});
|
|
243
|
+
|
|
244
|
+
expect(result).toMatchObject({status: 'terminal', jobStatus: 'cancelled'});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('counts prior executions so the caller resumes from the next sequence', async () => {
|
|
248
|
+
const job = await createListeningJob({status: 'running', listenerStatus: 'listening'});
|
|
249
|
+
await insertExecution(job.id, 1, 'succeeded');
|
|
250
|
+
await insertExecution(job.id, 2, 'failed');
|
|
251
|
+
|
|
252
|
+
const result = await activateJobListener({jobId: job.id, expectedVersion: job.version});
|
|
253
|
+
|
|
254
|
+
expect(result.executionCount).toBe(2);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('omits filter snapshots for matchers without non-event roots', async () => {
|
|
258
|
+
const job = await createInactiveListeningJobWithMatchers({
|
|
259
|
+
on: [{source: 'github', event: 'pull_request'}],
|
|
260
|
+
until: [{source: 'github', event: 'pull_request', filter: 'event.action == "closed"'}],
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
await activateJobListener({jobId: job.id, expectedVersion: job.version});
|
|
264
|
+
|
|
265
|
+
const payload = await activatedPayload(job.id);
|
|
266
|
+
expectListeningPayload(payload);
|
|
267
|
+
expect(payload.on[0]).toEqual({source: 'github', event: 'pull_request'});
|
|
268
|
+
expect(payload.until?.[0]).toEqual({
|
|
269
|
+
source: 'github',
|
|
270
|
+
event: 'pull_request',
|
|
271
|
+
filter: 'event.action == "closed"',
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('omits filter snapshots for reserved roots without concrete activation data', async () => {
|
|
276
|
+
const job = await createInactiveListeningJobWithMatchers({
|
|
277
|
+
on: [{source: 'github', event: 'pull_request', filter: 'matrix.os == "linux"'}],
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
await activateJobListener({jobId: job.id, expectedVersion: job.version});
|
|
281
|
+
|
|
282
|
+
const payload = await activatedPayload(job.id);
|
|
283
|
+
expectListeningPayload(payload);
|
|
284
|
+
expect(payload.on[0]).toEqual({
|
|
285
|
+
source: 'github',
|
|
286
|
+
event: 'pull_request',
|
|
287
|
+
filter: 'matrix.os == "linux"',
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it('snapshots null run inputs when listener filters reference inputs', async () => {
|
|
292
|
+
const job = await createInactiveListeningJobWithMatchers({
|
|
293
|
+
on: [{source: 'github', event: 'pull_request', filter: 'inputs == null'}],
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
await activateJobListener({jobId: job.id, expectedVersion: job.version});
|
|
297
|
+
|
|
298
|
+
const payload = await activatedPayload(job.id);
|
|
299
|
+
expectListeningPayload(payload);
|
|
300
|
+
expect(payload.on[0]).toEqual({
|
|
301
|
+
source: 'github',
|
|
302
|
+
event: 'pull_request',
|
|
303
|
+
filter: 'inputs == null',
|
|
304
|
+
filter_snapshot: {inputs: null},
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it('snapshots only referenced activation roots for listener filters', async () => {
|
|
309
|
+
const job = await createListenerWithDependencies({
|
|
310
|
+
on: [
|
|
311
|
+
{
|
|
312
|
+
source: 'github',
|
|
313
|
+
event: 'pull_request',
|
|
314
|
+
filter:
|
|
315
|
+
'jobs.build.outputs.pr_number == event.pull_request.number && inputs.environment == "prod" && trigger.event == "pull_request" && run.id != "" && job.key == "await"',
|
|
316
|
+
},
|
|
317
|
+
],
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
await activateJobListener({jobId: job.id, expectedVersion: job.version});
|
|
321
|
+
|
|
322
|
+
const payload = await activatedPayload(job.id);
|
|
323
|
+
expectListeningPayload(payload);
|
|
324
|
+
const snapshot = payload.on[0]?.filter_snapshot;
|
|
325
|
+
expect(snapshot).toEqual({
|
|
326
|
+
run: expect.objectContaining({id: expect.any(String), name: 'Test Workflow'}),
|
|
327
|
+
trigger: {source: 'github', event: 'pull_request'},
|
|
328
|
+
inputs: {environment: 'prod'},
|
|
329
|
+
job: {key: 'await'},
|
|
330
|
+
jobs: {
|
|
331
|
+
build: expect.objectContaining({
|
|
332
|
+
status: 'succeeded',
|
|
333
|
+
outputs: {pr_number: 42},
|
|
334
|
+
executions: expect.any(Array),
|
|
335
|
+
}),
|
|
336
|
+
},
|
|
337
|
+
});
|
|
338
|
+
expect(snapshot).not.toHaveProperty('event');
|
|
339
|
+
expect(snapshot?.jobs).not.toHaveProperty('review');
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it('snapshots an empty jobs root when referenced job keys are absent', async () => {
|
|
343
|
+
const job = await createListenerWithDependencies({
|
|
344
|
+
on: [
|
|
345
|
+
{
|
|
346
|
+
source: 'github',
|
|
347
|
+
event: 'pull_request',
|
|
348
|
+
filter: 'jobs.missing.outputs.pr_number == event.pull_request.number',
|
|
349
|
+
},
|
|
350
|
+
],
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
await activateJobListener({jobId: job.id, expectedVersion: job.version});
|
|
354
|
+
|
|
355
|
+
const payload = await activatedPayload(job.id);
|
|
356
|
+
expectListeningPayload(payload);
|
|
357
|
+
expect(payload.on[0]).toEqual({
|
|
358
|
+
source: 'github',
|
|
359
|
+
event: 'pull_request',
|
|
360
|
+
filter: 'jobs.missing.outputs.pr_number == event.pull_request.number',
|
|
361
|
+
filter_snapshot: {jobs: {}},
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it('keeps listener activation total when filter root extraction fails', async () => {
|
|
366
|
+
const job = await createInactiveListeningJobWithMatchers({
|
|
367
|
+
on: [{source: 'github', event: 'pull_request', filter: 'event.'}],
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
const result = await activateJobListener({jobId: job.id, expectedVersion: job.version});
|
|
371
|
+
|
|
372
|
+
const payload = await activatedPayload(job.id);
|
|
373
|
+
expect(result.status).toBe('running');
|
|
374
|
+
expectListeningPayload(payload);
|
|
375
|
+
expect(payload.on[0]).toEqual({source: 'github', event: 'pull_request', filter: 'event.'});
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
describe('resolveJobListener', () => {
|
|
380
|
+
it('resolves a listener with all-succeeded firings to succeeded', async () => {
|
|
381
|
+
const job = await createListeningJob({status: 'running', listenerStatus: 'listening'});
|
|
382
|
+
await insertExecution(job.id, 1, 'succeeded');
|
|
383
|
+
|
|
384
|
+
const result = await resolveJobListener({jobId: job.id, reason: 'until'});
|
|
385
|
+
|
|
386
|
+
const stored = await readJob(job.id);
|
|
387
|
+
expect(result.status).toBe('succeeded');
|
|
388
|
+
expect(stored?.status).toBe('succeeded');
|
|
389
|
+
expect(stored?.listenerStatus).toBe('resolved');
|
|
390
|
+
expect(stored?.resolutionReason).toBe('until');
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it('resolves a listener with a failed firing to failed', async () => {
|
|
394
|
+
const job = await createListeningJob({status: 'running', listenerStatus: 'listening'});
|
|
395
|
+
await insertExecution(job.id, 1, 'succeeded');
|
|
396
|
+
await insertExecution(job.id, 2, 'failed');
|
|
397
|
+
|
|
398
|
+
const result = await resolveJobListener({jobId: job.id, reason: 'max_executions'});
|
|
399
|
+
|
|
400
|
+
const stored = await readJob(job.id);
|
|
401
|
+
expect(result.status).toBe('failed');
|
|
402
|
+
expect(stored?.status).toBe('failed');
|
|
403
|
+
expect(stored?.resolutionReason).toBe('max_executions');
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it('resolves custom success expressions with direct dependency context', async () => {
|
|
407
|
+
const model = workflowModel({
|
|
408
|
+
jobs: {
|
|
409
|
+
build: {
|
|
410
|
+
steps: [{run: 'echo build'}],
|
|
411
|
+
},
|
|
412
|
+
listen: {
|
|
413
|
+
needs: 'build',
|
|
414
|
+
success: 'jobs.build.status == "succeeded" && jobs.build.outputs.release == "yes"',
|
|
415
|
+
steps: [{run: 'echo listen'}],
|
|
416
|
+
},
|
|
417
|
+
},
|
|
418
|
+
});
|
|
419
|
+
const run = await workflowRunFactory.create({}, {transient: {model}});
|
|
420
|
+
const [build, listener] = await getJobsByWorkflowRunId(run.id);
|
|
421
|
+
if (!build || !listener) throw new Error('Expected build and listener jobs');
|
|
422
|
+
await db()
|
|
423
|
+
.update(jobs)
|
|
424
|
+
.set({status: 'succeeded', outputs: {release: 'yes'}})
|
|
425
|
+
.where(eq(jobs.id, build.id));
|
|
426
|
+
await db()
|
|
427
|
+
.update(jobs)
|
|
428
|
+
.set({mode: 'listening', status: 'running', listenerStatus: 'listening'})
|
|
429
|
+
.where(eq(jobs.id, listener.id));
|
|
430
|
+
await db().delete(jobExecutions).where(eq(jobExecutions.jobId, listener.id));
|
|
431
|
+
await insertExecution(listener.id, 1, 'succeeded');
|
|
432
|
+
|
|
433
|
+
const result = await resolveJobListener({jobId: listener.id, reason: 'until'});
|
|
434
|
+
|
|
435
|
+
const stored = await readJob(listener.id);
|
|
436
|
+
expect(result.status).toBe('succeeded');
|
|
437
|
+
expect(stored?.status).toBe('succeeded');
|
|
438
|
+
expect(stored?.listenerStatus).toBe('resolved');
|
|
439
|
+
expect(stored?.resolutionReason).toBe('until');
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it('resolves a listener with zero firings under the default success rule', async () => {
|
|
443
|
+
const job = await createListeningJob({status: 'running', listenerStatus: 'listening'});
|
|
444
|
+
|
|
445
|
+
const result = await resolveJobListener({jobId: job.id, reason: 'timeout'});
|
|
446
|
+
|
|
447
|
+
const stored = await readJob(job.id);
|
|
448
|
+
expect(stored?.listenerStatus).toBe('resolved');
|
|
449
|
+
expect(['succeeded', 'failed']).toContain(result.status);
|
|
450
|
+
});
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
describe('drainListenerEventsIntoExecution', () => {
|
|
454
|
+
it('stores trigger events in received_at order', async () => {
|
|
455
|
+
const job = await createListeningJob({status: 'running', listenerStatus: 'listening'});
|
|
456
|
+
const middle = new Date('2026-01-01T00:01:00.000Z');
|
|
457
|
+
const first = new Date('2026-01-01T00:00:00.000Z');
|
|
458
|
+
const last = new Date('2026-01-01T00:02:00.000Z');
|
|
459
|
+
await bufferEvent(job.id, 'fire', crypto.randomUUID(), middle);
|
|
460
|
+
await bufferEvent(job.id, 'fire', crypto.randomUUID(), first);
|
|
461
|
+
await bufferEvent(job.id, 'fire', crypto.randomUUID(), last);
|
|
462
|
+
|
|
463
|
+
await drainListenerEventsIntoExecution({jobId: job.id, expectedSequence: 1});
|
|
464
|
+
|
|
465
|
+
const [execution] = await db()
|
|
466
|
+
.select()
|
|
467
|
+
.from(jobExecutions)
|
|
468
|
+
.where(and(eq(jobExecutions.jobId, job.id), eq(jobExecutions.sequence, 1)));
|
|
469
|
+
expect(execution?.triggerEvents.map((event) => event.received_at)).toEqual([
|
|
470
|
+
first.toISOString(),
|
|
471
|
+
middle.toISOString(),
|
|
472
|
+
last.toISOString(),
|
|
473
|
+
]);
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
it('materializes a pending execution from buffered fire events and consumes them', async () => {
|
|
477
|
+
const job = await createListeningJob({status: 'running', listenerStatus: 'listening'});
|
|
478
|
+
await bufferEvent(job.id);
|
|
479
|
+
await bufferEvent(job.id);
|
|
480
|
+
|
|
481
|
+
const result = await drainListenerEventsIntoExecution({jobId: job.id, expectedSequence: 1});
|
|
482
|
+
|
|
483
|
+
const executions = await db()
|
|
484
|
+
.select()
|
|
485
|
+
.from(jobExecutions)
|
|
486
|
+
.where(eq(jobExecutions.jobId, job.id));
|
|
487
|
+
const events = await db()
|
|
488
|
+
.select()
|
|
489
|
+
.from(jobListenerEvents)
|
|
490
|
+
.where(eq(jobListenerEvents.jobId, job.id));
|
|
491
|
+
expect(result).toMatchObject({kind: 'execution', sequence: 1, status: 'pending'});
|
|
492
|
+
expect(executions).toHaveLength(1);
|
|
493
|
+
expect(events.every((event) => event.consumedByExecutionId === executions[0]?.id)).toBe(true);
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
it('uses the frozen agent tool materialization snapshot for listener executions', async () => {
|
|
497
|
+
const model = workflowModel({
|
|
498
|
+
jobs: {
|
|
499
|
+
review: {
|
|
500
|
+
steps: [
|
|
501
|
+
{
|
|
502
|
+
prompt: 'Review the pull request.',
|
|
503
|
+
integrations: [{include: ['issue_read.get'], allowWrite: false}],
|
|
504
|
+
},
|
|
505
|
+
],
|
|
506
|
+
},
|
|
507
|
+
},
|
|
508
|
+
});
|
|
509
|
+
const modelJob = model.jobs[0];
|
|
510
|
+
const modelStep = modelJob?.steps[0];
|
|
511
|
+
if (modelJob === undefined || modelStep === undefined) throw new Error('Expected model step');
|
|
512
|
+
const job = await createListeningJobFromModel({
|
|
513
|
+
jobs: {
|
|
514
|
+
review: {
|
|
515
|
+
steps: [
|
|
516
|
+
{
|
|
517
|
+
prompt: 'Review the pull request.',
|
|
518
|
+
integrations: [{include: ['issue_read.get'], allowWrite: false}],
|
|
519
|
+
},
|
|
520
|
+
],
|
|
521
|
+
},
|
|
522
|
+
},
|
|
523
|
+
});
|
|
524
|
+
const frozenIntegrations = [
|
|
525
|
+
{
|
|
526
|
+
connectionId: crypto.randomUUID(),
|
|
527
|
+
connectionSlug: 'github',
|
|
528
|
+
provider: 'github',
|
|
529
|
+
requiredScope: [{permission: 'issues', access: 'read'}],
|
|
530
|
+
tools: [
|
|
531
|
+
{
|
|
532
|
+
id: 'issue_read',
|
|
533
|
+
sensitivity: 'read' as const,
|
|
534
|
+
sensitive: false,
|
|
535
|
+
requiredScope: [{permission: 'issues', access: 'read'}],
|
|
536
|
+
inputSchema: {type: 'object'},
|
|
537
|
+
methods: [
|
|
538
|
+
{
|
|
539
|
+
id: 'get',
|
|
540
|
+
token: 'issue_read.get',
|
|
541
|
+
sensitivity: 'read' as const,
|
|
542
|
+
sensitive: false,
|
|
543
|
+
requiredScope: [{permission: 'issues', access: 'read'}],
|
|
544
|
+
},
|
|
545
|
+
],
|
|
546
|
+
},
|
|
547
|
+
],
|
|
548
|
+
},
|
|
549
|
+
];
|
|
550
|
+
await db()
|
|
551
|
+
.update(workflowRunAttempts)
|
|
552
|
+
.set({
|
|
553
|
+
agentToolMaterialization: {
|
|
554
|
+
steps: [{jobKey: modelJob.key, stepId: modelStep.id, integrations: frozenIntegrations}],
|
|
555
|
+
},
|
|
556
|
+
})
|
|
557
|
+
.where(eq(workflowRunAttempts.id, job.workflowRunAttemptId));
|
|
558
|
+
await bufferEvent(job.id);
|
|
559
|
+
|
|
560
|
+
const result = await drainListenerEventsIntoExecution({
|
|
561
|
+
jobId: job.id,
|
|
562
|
+
expectedSequence: 1,
|
|
563
|
+
resolveAgentDefaults: () => ({
|
|
564
|
+
harness: 'pi',
|
|
565
|
+
provider: 'openai',
|
|
566
|
+
model: 'gpt-5.5-pro',
|
|
567
|
+
thinking: 'medium',
|
|
568
|
+
}),
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
const [execution] = await db()
|
|
572
|
+
.select()
|
|
573
|
+
.from(jobExecutions)
|
|
574
|
+
.where(and(eq(jobExecutions.jobId, job.id), eq(jobExecutions.sequence, 1)));
|
|
575
|
+
const executionSteps = await db()
|
|
576
|
+
.select()
|
|
577
|
+
.from(steps)
|
|
578
|
+
.where(eq(steps.jobExecutionId, execution?.id as string))
|
|
579
|
+
.orderBy(asc(steps.position));
|
|
580
|
+
expect(result).toMatchObject({kind: 'execution', status: 'pending'});
|
|
581
|
+
expect(executionSteps.find((step) => step.type === 'agent')?.config.integrations).toEqual(
|
|
582
|
+
frozenIntegrations,
|
|
583
|
+
);
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
it('caps a drain at maxSize and leaves the remainder buffered for the next firing', async () => {
|
|
587
|
+
const job = await createListeningJob({status: 'running', listenerStatus: 'listening'});
|
|
588
|
+
for (let index = 0; index < 5; index += 1) {
|
|
589
|
+
await bufferEvent(
|
|
590
|
+
job.id,
|
|
591
|
+
'fire',
|
|
592
|
+
crypto.randomUUID(),
|
|
593
|
+
new Date(Date.UTC(2026, 0, 1, 0, index, 0)),
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
const firstDrain = await drainListenerEventsIntoExecution({
|
|
598
|
+
jobId: job.id,
|
|
599
|
+
expectedSequence: 1,
|
|
600
|
+
maxSize: 2,
|
|
601
|
+
});
|
|
602
|
+
const secondDrain = await drainListenerEventsIntoExecution({
|
|
603
|
+
jobId: job.id,
|
|
604
|
+
expectedSequence: 2,
|
|
605
|
+
maxSize: 2,
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
const executions = await db()
|
|
609
|
+
.select()
|
|
610
|
+
.from(jobExecutions)
|
|
611
|
+
.where(eq(jobExecutions.jobId, job.id));
|
|
612
|
+
const unconsumedEvents = await db()
|
|
613
|
+
.select()
|
|
614
|
+
.from(jobListenerEvents)
|
|
615
|
+
.where(and(eq(jobListenerEvents.jobId, job.id), eq(jobListenerEvents.disposition, 'fire')));
|
|
616
|
+
expect(firstDrain).toMatchObject({kind: 'execution', sequence: 1});
|
|
617
|
+
expect(secondDrain).toMatchObject({kind: 'execution', sequence: 2});
|
|
618
|
+
expect(
|
|
619
|
+
executions.map((execution) => execution.triggerEvents).map((events) => events.length),
|
|
620
|
+
).toEqual([2, 2]);
|
|
621
|
+
expect(unconsumedEvents.filter((event) => event.consumedByExecutionId === null)).toHaveLength(
|
|
622
|
+
1,
|
|
623
|
+
);
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
it('materializes runner labels separately for each listener firing', async () => {
|
|
627
|
+
const job = await createListeningJobFromModel({
|
|
628
|
+
jobs: {
|
|
629
|
+
review: {
|
|
630
|
+
runner: ['linux'],
|
|
631
|
+
runnerTemplates: [template('execution.events[0].data.runner')],
|
|
632
|
+
steps: [{run: 'echo review'}],
|
|
633
|
+
},
|
|
634
|
+
},
|
|
635
|
+
});
|
|
636
|
+
await bufferEvent(job.id, 'fire', crypto.randomUUID(), new Date('2026-01-01T00:00:00.000Z'));
|
|
637
|
+
await db()
|
|
638
|
+
.update(jobListenerEvents)
|
|
639
|
+
.set({payload: {runner: 'GPU'}})
|
|
640
|
+
.where(eq(jobListenerEvents.jobId, job.id));
|
|
641
|
+
|
|
642
|
+
const first = await drainListenerEventsIntoExecution({jobId: job.id, expectedSequence: 1});
|
|
643
|
+
await bufferEvent(job.id, 'fire', crypto.randomUUID(), new Date('2026-01-01T00:01:00.000Z'));
|
|
644
|
+
await db()
|
|
645
|
+
.update(jobListenerEvents)
|
|
646
|
+
.set({payload: {runner: 'ARM'}})
|
|
647
|
+
.where(
|
|
648
|
+
and(eq(jobListenerEvents.jobId, job.id), isNull(jobListenerEvents.consumedByExecutionId)),
|
|
649
|
+
);
|
|
650
|
+
|
|
651
|
+
const second = await drainListenerEventsIntoExecution({jobId: job.id, expectedSequence: 2});
|
|
652
|
+
|
|
653
|
+
const executions = await db()
|
|
654
|
+
.select()
|
|
655
|
+
.from(jobExecutions)
|
|
656
|
+
.where(eq(jobExecutions.jobId, job.id))
|
|
657
|
+
.orderBy(asc(jobExecutions.sequence));
|
|
658
|
+
expect(first).toMatchObject({kind: 'execution', requiredLabels: ['gpu', 'linux']});
|
|
659
|
+
expect(second).toMatchObject({kind: 'execution', requiredLabels: ['arm', 'linux']});
|
|
660
|
+
expect(executions.map((execution) => execution.runner)).toEqual([
|
|
661
|
+
['gpu', 'linux'],
|
|
662
|
+
['arm', 'linux'],
|
|
663
|
+
]);
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
it('persists step conditions for listener firings', async () => {
|
|
667
|
+
const condition = conditionExpression('false');
|
|
668
|
+
const job = await createListeningJobFromModel({
|
|
669
|
+
jobs: {
|
|
670
|
+
review: {
|
|
671
|
+
steps: [{if: condition, run: 'echo review'}],
|
|
672
|
+
},
|
|
673
|
+
},
|
|
674
|
+
});
|
|
675
|
+
await bufferEvent(job.id);
|
|
676
|
+
|
|
677
|
+
await drainListenerEventsIntoExecution({jobId: job.id, expectedSequence: 1});
|
|
678
|
+
|
|
679
|
+
const [execution] = await db()
|
|
680
|
+
.select()
|
|
681
|
+
.from(jobExecutions)
|
|
682
|
+
.where(and(eq(jobExecutions.jobId, job.id), eq(jobExecutions.sequence, 1)));
|
|
683
|
+
if (!execution) throw new Error('Expected listener execution');
|
|
684
|
+
const materialized = await db()
|
|
685
|
+
.select()
|
|
686
|
+
.from(steps)
|
|
687
|
+
.where(eq(steps.jobExecutionId, execution.id))
|
|
688
|
+
.orderBy(asc(steps.position));
|
|
689
|
+
expect(materialized[1]?.condition).toEqual(condition);
|
|
690
|
+
expect(materialized[1]?.config).toEqual({run: 'echo review'});
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
it('persists listener step config plans for dispatch-time templates', async () => {
|
|
694
|
+
const job = await createListeningJobFromModel({
|
|
695
|
+
jobs: {
|
|
696
|
+
deploy: {
|
|
697
|
+
steps: [
|
|
698
|
+
{key: 'build', run: 'echo build'},
|
|
699
|
+
{
|
|
700
|
+
key: 'deploy',
|
|
701
|
+
run: 'echo "$SHA"',
|
|
702
|
+
env: {SHA: template('steps.build.outputs.sha')},
|
|
703
|
+
},
|
|
704
|
+
],
|
|
705
|
+
},
|
|
706
|
+
},
|
|
707
|
+
});
|
|
708
|
+
await bufferEvent(job.id);
|
|
709
|
+
|
|
710
|
+
const drained = await drainListenerEventsIntoExecution({jobId: job.id, expectedSequence: 1});
|
|
711
|
+
|
|
712
|
+
if (drained.kind !== 'execution') throw new Error('Expected listener execution');
|
|
713
|
+
const materialized = await db()
|
|
714
|
+
.select()
|
|
715
|
+
.from(steps)
|
|
716
|
+
.where(eq(steps.jobExecutionId, drained.jobExecutionId))
|
|
717
|
+
.orderBy(asc(steps.position));
|
|
718
|
+
expect(materialized[0]?.configPlan).toBeNull();
|
|
719
|
+
expect(materialized[2]?.configPlan).toMatchObject({
|
|
720
|
+
env: {
|
|
721
|
+
SHA: {
|
|
722
|
+
segments: [
|
|
723
|
+
{
|
|
724
|
+
kind: 'deferred',
|
|
725
|
+
roots: ['steps'],
|
|
726
|
+
fillTarget: 'step-dispatch',
|
|
727
|
+
expression: {source: 'steps.build.outputs.sha'},
|
|
728
|
+
},
|
|
729
|
+
],
|
|
730
|
+
},
|
|
731
|
+
},
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
const setupStep = await nextStepForJob(job.id);
|
|
735
|
+
if (setupStep.kind !== 'step') throw new Error('Expected setup step');
|
|
736
|
+
await recordStepResult({
|
|
737
|
+
jobExecutionId: drained.jobExecutionId,
|
|
738
|
+
stepId: setupStep.step.id,
|
|
739
|
+
status: 'succeeded',
|
|
740
|
+
});
|
|
741
|
+
const buildStep = await nextStepForJob(job.id);
|
|
742
|
+
if (buildStep.kind !== 'step') throw new Error('Expected build step');
|
|
743
|
+
await recordStepResult({
|
|
744
|
+
jobExecutionId: drained.jobExecutionId,
|
|
745
|
+
stepId: buildStep.step.id,
|
|
746
|
+
status: 'succeeded',
|
|
747
|
+
output: {sha: 'abc123'},
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
const deployStep = await nextStepForJob(job.id);
|
|
751
|
+
|
|
752
|
+
expect(deployStep).toEqual({
|
|
753
|
+
kind: 'step',
|
|
754
|
+
step: expect.objectContaining({
|
|
755
|
+
key: 'deploy',
|
|
756
|
+
config: {run: 'echo "$SHA"', env: {SHA: 'abc123'}},
|
|
757
|
+
}),
|
|
758
|
+
dispatched: true,
|
|
759
|
+
});
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
it('peeks the unconsumed listener buffer from DB state', async () => {
|
|
763
|
+
const job = await createListeningJob({status: 'running', listenerStatus: 'listening'});
|
|
764
|
+
await bufferEvent(job.id, 'fire', crypto.randomUUID(), new Date(Date.now() - 10_000));
|
|
765
|
+
await bufferEvent(job.id, 'fire', crypto.randomUUID(), new Date(Date.now() - 2_000));
|
|
766
|
+
await bufferEvent(job.id, 'resolve', crypto.randomUUID(), new Date());
|
|
767
|
+
|
|
768
|
+
const result = await peekListenerBuffer({jobId: job.id});
|
|
769
|
+
|
|
770
|
+
expect(result.fireCount).toBe(2);
|
|
771
|
+
expect(result.resolvePending).toBe(true);
|
|
772
|
+
expect(result.oldestAgeMs).toBeGreaterThanOrEqual(result.newestAgeMs);
|
|
773
|
+
expect(result.oldestAgeMs).toBeGreaterThan(0);
|
|
774
|
+
expect(result.newestAgeMs).toBeGreaterThan(0);
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
it('reports a resolve request when a resolve event is buffered', async () => {
|
|
778
|
+
const job = await createListeningJob({status: 'running', listenerStatus: 'listening'});
|
|
779
|
+
await bufferEvent(job.id, 'resolve');
|
|
780
|
+
|
|
781
|
+
const result = await drainListenerEventsIntoExecution({jobId: job.id, expectedSequence: 1});
|
|
782
|
+
|
|
783
|
+
expect(result).toEqual({kind: 'resolve-requested'});
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
it('resolves outputs for listener executions', async () => {
|
|
787
|
+
const job = await createListeningJobFromModel({
|
|
788
|
+
jobs: {
|
|
789
|
+
listen: {
|
|
790
|
+
steps: [{key: 'show_event', run: 'echo listener'}],
|
|
791
|
+
outputs: {message: template('steps.show_event.outputs.message')},
|
|
792
|
+
},
|
|
793
|
+
},
|
|
794
|
+
});
|
|
795
|
+
await bufferEvent(job.id);
|
|
796
|
+
const drained = await drainListenerEventsIntoExecution({jobId: job.id, expectedSequence: 1});
|
|
797
|
+
if (drained.kind !== 'execution') throw new Error('Expected listener execution');
|
|
798
|
+
const setupStep = await nextStepForJob(job.id);
|
|
799
|
+
if (setupStep.kind !== 'step') throw new Error('Expected setup step');
|
|
800
|
+
await recordStepResult({
|
|
801
|
+
jobExecutionId: drained.jobExecutionId,
|
|
802
|
+
stepId: setupStep.step.id,
|
|
803
|
+
status: 'succeeded',
|
|
804
|
+
});
|
|
805
|
+
const runStep = await nextStepForJob(job.id);
|
|
806
|
+
if (runStep.kind !== 'step') throw new Error('Expected run step');
|
|
807
|
+
await recordStepResult({
|
|
808
|
+
jobExecutionId: drained.jobExecutionId,
|
|
809
|
+
stepId: runStep.step.id,
|
|
810
|
+
status: 'succeeded',
|
|
811
|
+
output: {message: 'hello'},
|
|
812
|
+
});
|
|
813
|
+
await updateJobExecutionStatus({
|
|
814
|
+
jobExecutionId: drained.jobExecutionId,
|
|
815
|
+
expectedVersion: drained.executionVersion,
|
|
816
|
+
status: 'succeeded',
|
|
817
|
+
});
|
|
818
|
+
|
|
819
|
+
const result = await resolveJobListener({jobId: job.id, reason: 'until'});
|
|
820
|
+
|
|
821
|
+
const stored = await readJob(job.id);
|
|
822
|
+
expect(result).toEqual({status: 'succeeded', jobVersion: stored?.version});
|
|
823
|
+
expect(stored?.outputs).toEqual({message: 'hello'});
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
it('reports empty when nothing is buffered', async () => {
|
|
827
|
+
const job = await createListeningJob({status: 'running', listenerStatus: 'listening'});
|
|
828
|
+
|
|
829
|
+
const result = await drainListenerEventsIntoExecution({jobId: job.id, expectedSequence: 1});
|
|
830
|
+
|
|
831
|
+
expect(result).toEqual({kind: 'empty'});
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
it('returns the existing execution when the sequence was already materialized', async () => {
|
|
835
|
+
const job = await createListeningJob({status: 'running', listenerStatus: 'listening'});
|
|
836
|
+
const existing = await insertExecution(job.id, 1, 'running');
|
|
837
|
+
await bufferEvent(job.id);
|
|
838
|
+
|
|
839
|
+
const result = await drainListenerEventsIntoExecution({jobId: job.id, expectedSequence: 1});
|
|
840
|
+
|
|
841
|
+
expect(result).toMatchObject({
|
|
842
|
+
kind: 'execution',
|
|
843
|
+
jobExecutionId: existing.id,
|
|
844
|
+
status: 'running',
|
|
845
|
+
});
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
it('creates a failed execution when materialization hits a permanent error', async () => {
|
|
849
|
+
const job = await createListeningJob({
|
|
850
|
+
status: 'running',
|
|
851
|
+
listenerStatus: 'listening',
|
|
852
|
+
key: 'not-in-model',
|
|
853
|
+
});
|
|
854
|
+
await bufferEvent(job.id);
|
|
855
|
+
|
|
856
|
+
const result = await drainListenerEventsIntoExecution({jobId: job.id, expectedSequence: 1});
|
|
857
|
+
|
|
858
|
+
const [execution] = await db()
|
|
859
|
+
.select()
|
|
860
|
+
.from(jobExecutions)
|
|
861
|
+
.where(and(eq(jobExecutions.jobId, job.id), eq(jobExecutions.sequence, 1)));
|
|
862
|
+
expect(result).toMatchObject({kind: 'execution', status: 'failed'});
|
|
863
|
+
expect(execution?.status).toBe('failed');
|
|
864
|
+
});
|
|
865
|
+
});
|