@vercel/factory 0.0.14 → 0.0.16
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/CHANGELOG.md +356 -0
- package/README.md +96 -2
- package/dist/agent-routes.d.mts +113 -0
- package/dist/agent-routes.mjs +91 -0
- package/dist/agent-routes.mjs.map +1 -0
- package/dist/api-contracts.d.mts +1768 -0
- package/dist/api-contracts.mjs +119 -0
- package/dist/api-contracts.mjs.map +1 -0
- package/dist/api-images.d.mts +30 -0
- package/dist/api-images.mjs +28 -0
- package/dist/api-images.mjs.map +1 -0
- package/dist/api-task-graph.mjs +57 -0
- package/dist/api-task-graph.mjs.map +1 -0
- package/dist/api.d.mts +111 -216
- package/dist/api.mjs +1063 -99
- package/dist/api.mjs.map +1 -1
- package/dist/approval-access.d.mts +39 -0
- package/dist/approval-access.mjs +62 -0
- package/dist/approval-access.mjs.map +1 -0
- package/dist/approval-contracts.d.mts +698 -0
- package/dist/approval-contracts.mjs +27 -0
- package/dist/approval-contracts.mjs.map +1 -0
- package/dist/approval.d.mts +49 -0
- package/dist/approval.mjs +39 -0
- package/dist/approval.mjs.map +1 -0
- package/dist/blob/artifacts.d.mts +37 -0
- package/dist/blob/artifacts.mjs +45 -0
- package/dist/blob/artifacts.mjs.map +1 -0
- package/dist/blob/index.d.mts +84 -12
- package/dist/blob/index.mjs +179 -45
- package/dist/blob/index.mjs.map +1 -1
- package/dist/budget.d.mts +115 -0
- package/dist/budget.mjs +147 -0
- package/dist/budget.mjs.map +1 -0
- package/dist/budgets.d.mts +4 -0
- package/dist/budgets.mjs +4 -0
- package/dist/build-factory.d.mts +7 -4
- package/dist/build-factory.mjs +2 -1
- package/dist/build-factory.mjs.map +1 -1
- package/dist/change-verification/brief.d.mts +9 -0
- package/dist/change-verification/brief.mjs +13 -0
- package/dist/change-verification/brief.mjs.map +1 -0
- package/dist/change-verification/dispatch.d.mts +37 -0
- package/dist/change-verification/dispatch.mjs +83 -0
- package/dist/change-verification/dispatch.mjs.map +1 -0
- package/dist/change-verification/eve-tool.d.mts +61 -0
- package/dist/change-verification/eve-tool.mjs +89 -0
- package/dist/change-verification/eve-tool.mjs.map +1 -0
- package/dist/change-verification/result.mjs +130 -0
- package/dist/change-verification/result.mjs.map +1 -0
- package/dist/changes/eve-record-change.d.mts +56 -0
- package/dist/changes/eve-record-change.mjs +131 -0
- package/dist/changes/eve-record-change.mjs.map +1 -0
- package/dist/changes/presentation.d.mts +14 -0
- package/dist/changes.d.mts +24 -0
- package/dist/changes.mjs +29 -0
- package/dist/changes.mjs.map +1 -0
- package/dist/client-events.d.mts +25 -0
- package/dist/client-events.mjs +45 -0
- package/dist/client-events.mjs.map +1 -0
- package/dist/client-stream.d.mts +28 -0
- package/dist/client-stream.mjs +210 -0
- package/dist/client-stream.mjs.map +1 -0
- package/dist/client-transcript.d.mts +41 -0
- package/dist/client-transcript.mjs +233 -0
- package/dist/client-transcript.mjs.map +1 -0
- package/dist/client.d.mts +343 -0
- package/dist/client.mjs +366 -0
- package/dist/client.mjs.map +1 -0
- package/dist/code-review/contracts.d.mts +27 -0
- package/dist/code-review/eve-post-review.d.mts +70 -0
- package/dist/code-review/eve-post-review.mjs +195 -0
- package/dist/code-review/eve-post-review.mjs.map +1 -0
- package/dist/code-review/eve-review-comments.d.mts +75 -0
- package/dist/code-review/eve-review-comments.mjs +193 -0
- package/dist/code-review/eve-review-comments.mjs.map +1 -0
- package/dist/code-review/github-contracts.d.mts +10 -0
- package/dist/code-review/github-reporter.d.mts +46 -0
- package/dist/code-review/github-reporter.mjs +262 -0
- package/dist/code-review/github-reporter.mjs.map +1 -0
- package/dist/code-review/preparation.d.mts +34 -0
- package/dist/code-review/preparation.mjs +23 -0
- package/dist/code-review/preparation.mjs.map +1 -0
- package/dist/code-review/stall.d.mts +17 -0
- package/dist/code-review/stall.mjs +30 -0
- package/dist/code-review/stall.mjs.map +1 -0
- package/dist/code-review.d.mts +7 -0
- package/dist/code-review.mjs +5 -0
- package/dist/communication.d.mts +9 -0
- package/dist/communication.mjs +9 -0
- package/dist/connectors/github.d.mts +26 -0
- package/dist/connectors/github.mjs +55 -0
- package/dist/connectors/github.mjs.map +1 -0
- package/dist/connectors/index.d.mts +35 -3
- package/dist/connectors/index.mjs +3 -52
- package/dist/connectors/index.mjs.map +1 -1
- package/dist/connectors/outbox.d.mts +34 -0
- package/dist/connectors/outbox.mjs +50 -0
- package/dist/connectors/outbox.mjs.map +1 -0
- package/dist/connectors/slack-approval-blocks.mjs +136 -0
- package/dist/connectors/slack-approval-blocks.mjs.map +1 -0
- package/dist/connectors/slack-approvals.d.mts +29 -0
- package/dist/connectors/slack-approvals.mjs +346 -0
- package/dist/connectors/slack-approvals.mjs.map +1 -0
- package/dist/connectors/slack.d.mts +35 -52
- package/dist/connectors/slack.mjs +13 -85
- package/dist/connectors/slack.mjs.map +1 -1
- package/dist/deepsec/eve-tool.d.mts +110 -0
- package/dist/deepsec/eve-tool.mjs +166 -0
- package/dist/deepsec/eve-tool.mjs.map +1 -0
- package/dist/deepsec/finding.d.mts +84 -0
- package/dist/deepsec/finding.mjs +56 -0
- package/dist/deepsec/finding.mjs.map +1 -0
- package/dist/deepsec.d.mts +3 -0
- package/dist/delivery-metrics/metrics.d.mts +93 -0
- package/dist/delivery-metrics/metrics.mjs +200 -0
- package/dist/delivery-metrics/metrics.mjs.map +1 -0
- package/dist/delivery-metrics.d.mts +2 -0
- package/dist/delivery-metrics.mjs +2 -0
- package/dist/dispatch.d.mts +152 -0
- package/dist/dispatch.mjs +199 -0
- package/dist/dispatch.mjs.map +1 -0
- package/dist/errors.d.mts +15 -0
- package/dist/errors.mjs +38 -0
- package/dist/errors.mjs.map +1 -0
- package/dist/eve/index.d.mts +209 -1277
- package/dist/eve/index.mjs +312 -1073
- package/dist/eve/index.mjs.map +1 -1
- package/dist/eve/invoke.d.mts +144 -0
- package/dist/eve/invoke.mjs +254 -0
- package/dist/eve/invoke.mjs.map +1 -0
- package/dist/eve/session-client.d.mts +186 -0
- package/dist/eve/session-client.mjs +245 -0
- package/dist/eve/session-client.mjs.map +1 -0
- package/dist/eve/task-execution.d.mts +380 -0
- package/dist/eve/task-execution.mjs +73 -0
- package/dist/eve/task-execution.mjs.map +1 -0
- package/dist/eve/task-session.d.mts +129 -0
- package/dist/eve/task-session.mjs +245 -0
- package/dist/eve/task-session.mjs.map +1 -0
- package/dist/eve/transcript.d.mts +37 -0
- package/dist/eve/transcript.mjs +69 -0
- package/dist/eve/transcript.mjs.map +1 -0
- package/dist/execution-public.d.mts +8 -0
- package/dist/execution-public.mjs +7 -0
- package/dist/execution.d.mts +183 -0
- package/dist/execution.mjs +223 -0
- package/dist/execution.mjs.map +1 -0
- package/dist/finding-remediation/admission.d.mts +53 -0
- package/dist/finding-remediation/admission.mjs +90 -0
- package/dist/finding-remediation/admission.mjs.map +1 -0
- package/dist/finding-remediation/brief.d.mts +14 -0
- package/dist/finding-remediation/brief.mjs +25 -0
- package/dist/finding-remediation/brief.mjs.map +1 -0
- package/dist/finding-remediation/eve-tool.d.mts +29 -0
- package/dist/finding-remediation/eve-tool.mjs +34 -0
- package/dist/finding-remediation/eve-tool.mjs.map +1 -0
- package/dist/findings/fingerprint.d.mts +14 -0
- package/dist/findings/fingerprint.mjs +28 -0
- package/dist/findings/fingerprint.mjs.map +1 -0
- package/dist/findings.d.mts +7 -0
- package/dist/findings.mjs +6 -0
- package/dist/github-app.d.mts +54 -0
- package/dist/github-app.mjs +121 -0
- package/dist/github-app.mjs.map +1 -0
- package/dist/github-publication.d.mts +199 -0
- package/dist/github-publication.mjs +442 -0
- package/dist/github-publication.mjs.map +1 -0
- package/dist/github-transfer.d.mts +81 -0
- package/dist/github-transfer.mjs +273 -0
- package/dist/github-transfer.mjs.map +1 -0
- package/dist/github.d.mts +446 -2
- package/dist/github.mjs +357 -56
- package/dist/github.mjs.map +1 -1
- package/dist/image-input.d.mts +51 -0
- package/dist/image-input.mjs +63 -0
- package/dist/image-input.mjs.map +1 -0
- package/dist/inbox-activity.d.mts +53 -0
- package/dist/inbox-activity.mjs +41 -0
- package/dist/inbox-activity.mjs.map +1 -0
- package/dist/index.d.mts +16 -484
- package/dist/index.mjs +15 -522
- package/dist/intake-contracts.d.mts +55 -0
- package/dist/intake-contracts.mjs +33 -0
- package/dist/intake-contracts.mjs.map +1 -0
- package/dist/integrations/deepsec.d.mts +5 -0
- package/dist/integrations/deepsec.mjs +3 -0
- package/dist/integrations/github.d.mts +11 -0
- package/dist/integrations/github.mjs +9 -0
- package/dist/integrations/slack.d.mts +5 -0
- package/dist/integrations/slack.mjs +5 -0
- package/dist/integrations/vercel.d.mts +5 -0
- package/dist/integrations/vercel.mjs +4 -0
- package/dist/merge-resolution/contracts.d.mts +17 -0
- package/dist/merge-resolution/contracts.mjs +19 -0
- package/dist/merge-resolution/contracts.mjs.map +1 -0
- package/dist/merge-resolution/eve-tools.d.mts +47 -0
- package/dist/merge-resolution/eve-tools.mjs +122 -0
- package/dist/merge-resolution/eve-tools.mjs.map +1 -0
- package/dist/model-settings.d.mts +41 -0
- package/dist/model-settings.mjs +35 -0
- package/dist/model-settings.mjs.map +1 -0
- package/dist/multi-repository-changes/coordination.d.mts +49 -0
- package/dist/multi-repository-changes/coordination.mjs +115 -0
- package/dist/multi-repository-changes/coordination.mjs.map +1 -0
- package/dist/planning/cancellation.mjs +82 -0
- package/dist/planning/cancellation.mjs.map +1 -0
- package/dist/planning/configuration.d.mts +23 -0
- package/dist/planning/configuration.mjs +56 -0
- package/dist/planning/configuration.mjs.map +1 -0
- package/dist/planning/contracts.d.mts +136 -0
- package/dist/planning/contracts.mjs +42 -0
- package/dist/planning/contracts.mjs.map +1 -0
- package/dist/planning/dependencies.mjs +44 -0
- package/dist/planning/dependencies.mjs.map +1 -0
- package/dist/planning/effects.mjs +122 -0
- package/dist/planning/effects.mjs.map +1 -0
- package/dist/planning/reconcile.mjs +279 -0
- package/dist/planning/reconcile.mjs.map +1 -0
- package/dist/planning/runtime.d.mts +27 -0
- package/dist/planning/runtime.mjs +25 -0
- package/dist/planning/runtime.mjs.map +1 -0
- package/dist/planning/state.mjs +92 -0
- package/dist/planning/state.mjs.map +1 -0
- package/dist/planning/turns.mjs +262 -0
- package/dist/planning/turns.mjs.map +1 -0
- package/dist/planning/validation.mjs +106 -0
- package/dist/planning/validation.mjs.map +1 -0
- package/dist/postgres/index.d.mts +48 -1
- package/dist/postgres/index.mjs +45 -3
- package/dist/postgres/index.mjs.map +1 -1
- package/dist/presets/software-development/approval.d.mts +14 -0
- package/dist/presets/software-development/approval.mjs +21 -0
- package/dist/presets/software-development/approval.mjs.map +1 -0
- package/dist/presets/software-development/deepsec-brief.d.mts +8 -0
- package/dist/presets/software-development/deepsec-brief.mjs +14 -0
- package/dist/presets/software-development/deepsec-brief.mjs.map +1 -0
- package/dist/presets/software-development/dispatch.d.mts +56 -0
- package/dist/presets/software-development/dispatch.mjs +67 -0
- package/dist/presets/software-development/dispatch.mjs.map +1 -0
- package/dist/presets/software-development/eve-delegation-policy.d.mts +7 -0
- package/dist/presets/software-development/eve-delegation-policy.mjs +34 -0
- package/dist/presets/software-development/eve-delegation-policy.mjs.map +1 -0
- package/dist/presets/software-development/eve.d.mts +18 -0
- package/dist/presets/software-development/eve.mjs +19 -0
- package/dist/presets/software-development/eve.mjs.map +1 -0
- package/dist/presets/software-development/finish-task-policy.d.mts +7 -0
- package/dist/presets/software-development/finish-task-policy.mjs +9 -0
- package/dist/presets/software-development/finish-task-policy.mjs.map +1 -0
- package/dist/presets/software-development/recovery.d.mts +32 -0
- package/dist/presets/software-development/recovery.mjs +120 -0
- package/dist/presets/software-development/recovery.mjs.map +1 -0
- package/dist/presets/software-development/task-communication.d.mts +24 -0
- package/dist/presets/software-development/task-communication.mjs +157 -0
- package/dist/presets/software-development/task-communication.mjs.map +1 -0
- package/dist/presets/software-development.d.mts +10 -0
- package/dist/presets/software-development.mjs +9 -0
- package/dist/pull-requests/contracts.d.mts +72 -0
- package/dist/pull-requests/eve-fetch.d.mts +57 -0
- package/dist/pull-requests/eve-fetch.mjs +151 -0
- package/dist/pull-requests/eve-fetch.mjs.map +1 -0
- package/dist/pull-requests/github-label-manager.d.mts +27 -0
- package/dist/pull-requests/github-label-manager.mjs +40 -0
- package/dist/pull-requests/github-label-manager.mjs.map +1 -0
- package/dist/pull-requests/github-publisher.d.mts +67 -0
- package/dist/pull-requests/github-publisher.mjs +405 -0
- package/dist/pull-requests/github-publisher.mjs.map +1 -0
- package/dist/pull-requests.d.mts +6 -0
- package/dist/pull-requests.mjs +4 -0
- package/dist/receipts.d.mts +4 -0
- package/dist/receipts.mjs +3 -0
- package/dist/repositories.d.mts +18 -0
- package/dist/repositories.mjs +33 -0
- package/dist/repositories.mjs.map +1 -0
- package/dist/sandbox/command.d.mts +18 -0
- package/dist/sandbox/execution-abort.d.mts +16 -0
- package/dist/sandbox/execution-abort.mjs +49 -0
- package/dist/sandbox/execution-abort.mjs.map +1 -0
- package/dist/sandbox/execution-environment.d.mts +25 -0
- package/dist/sandbox/execution-run-ledger.d.mts +115 -0
- package/dist/sandbox/execution-run-ledger.mjs +209 -0
- package/dist/sandbox/execution-run-ledger.mjs.map +1 -0
- package/dist/sandbox/execution.d.mts +54 -0
- package/dist/sandbox/execution.mjs +228 -0
- package/dist/sandbox/execution.mjs.map +1 -0
- package/dist/sandbox/harness-runner.d.mts +79 -0
- package/dist/sandbox/harness-runner.mjs +162 -0
- package/dist/sandbox/harness-runner.mjs.map +1 -0
- package/dist/sandbox/index.d.mts +10 -0
- package/dist/sandbox/index.mjs +7 -0
- package/dist/sandbox/task-environment.d.mts +50 -0
- package/dist/sandbox/task-environment.mjs +140 -0
- package/dist/sandbox/task-environment.mjs.map +1 -0
- package/dist/sandbox/workspace.d.mts +29 -0
- package/dist/sandbox/workspace.mjs +28 -0
- package/dist/sandbox/workspace.mjs.map +1 -0
- package/dist/schema/agent-route.d.mts +40 -0
- package/dist/schema/agent-route.mjs +33 -0
- package/dist/schema/agent-route.mjs.map +1 -0
- package/dist/schema/answer.d.mts +23 -0
- package/dist/schema/answer.mjs +21 -0
- package/dist/schema/answer.mjs.map +1 -0
- package/dist/schema/budget.d.mts +38 -0
- package/dist/schema/budget.mjs +30 -0
- package/dist/schema/budget.mjs.map +1 -0
- package/dist/schema/catalog.mjs +72 -0
- package/dist/schema/catalog.mjs.map +1 -0
- package/dist/schema/change.d.mts +117 -0
- package/dist/schema/change.mjs +68 -0
- package/dist/schema/change.mjs.map +1 -0
- package/dist/schema/common.d.mts +33 -0
- package/dist/{id-BLIkCVue.mjs → schema/common.mjs} +7 -16
- package/dist/schema/common.mjs.map +1 -0
- package/dist/schema/communication.d.mts +52 -0
- package/dist/{communication-Kd9nU5RP.mjs → schema/communication.mjs} +8 -3
- package/dist/schema/communication.mjs.map +1 -0
- package/dist/schema/execution.d.mts +101 -0
- package/dist/schema/execution.mjs +86 -0
- package/dist/schema/execution.mjs.map +1 -0
- package/dist/schema/factory-config.d.mts +122 -0
- package/dist/schema/factory-config.mjs +90 -0
- package/dist/schema/factory-config.mjs.map +1 -0
- package/dist/schema/finding.d.mts +125 -0
- package/dist/schema/finding.mjs +67 -0
- package/dist/schema/finding.mjs.map +1 -0
- package/dist/schema/id.d.mts +43 -0
- package/dist/schema/id.mjs +32 -0
- package/dist/schema/id.mjs.map +1 -0
- package/dist/schema/outbox.d.mts +240 -0
- package/dist/schema/outbox.mjs +53 -0
- package/dist/schema/outbox.mjs.map +1 -0
- package/dist/schema/question-route.mjs +16 -0
- package/dist/schema/question-route.mjs.map +1 -0
- package/dist/schema/receipt.d.mts +81 -0
- package/dist/schema/receipt.mjs +94 -0
- package/dist/schema/receipt.mjs.map +1 -0
- package/dist/schema/repository.d.mts +30 -0
- package/dist/schema/repository.mjs +29 -0
- package/dist/schema/repository.mjs.map +1 -0
- package/dist/schema/session.d.mts +136 -0
- package/dist/schema/session.mjs +54 -0
- package/dist/schema/session.mjs.map +1 -0
- package/dist/schema/signal.d.mts +105 -0
- package/dist/schema/signal.mjs +81 -0
- package/dist/schema/signal.mjs.map +1 -0
- package/dist/schema/slack-pr-notifications.d.mts +12 -0
- package/dist/schema/slack-pr-notifications.mjs +11 -0
- package/dist/schema/slack-pr-notifications.mjs.map +1 -0
- package/dist/schema/stored-session.mjs +10 -0
- package/dist/schema/stored-session.mjs.map +1 -0
- package/dist/schema/stored-task.mjs +20 -0
- package/dist/schema/stored-task.mjs.map +1 -0
- package/dist/schema/task-effect.d.mts +39 -0
- package/dist/schema/task-effect.mjs +29 -0
- package/dist/schema/task-effect.mjs.map +1 -0
- package/dist/schema/task-graph.d.mts +3173 -0
- package/dist/schema/task-graph.mjs +97 -0
- package/dist/schema/task-graph.mjs.map +1 -0
- package/dist/schema/task-message.d.mts +254 -0
- package/dist/schema/task-message.mjs +189 -0
- package/dist/schema/task-message.mjs.map +1 -0
- package/dist/schema/task.d.mts +273 -0
- package/dist/schema/task.mjs +200 -0
- package/dist/schema/task.mjs.map +1 -0
- package/dist/schema/transcript.d.mts +176 -0
- package/dist/schema/transcript.mjs +87 -0
- package/dist/schema/transcript.mjs.map +1 -0
- package/dist/schema/transitions.d.mts +50 -0
- package/dist/schema/transitions.mjs +303 -0
- package/dist/schema/transitions.mjs.map +1 -0
- package/dist/schema/work.d.mts +150 -0
- package/dist/schema/work.mjs +102 -0
- package/dist/schema/work.mjs.map +1 -0
- package/dist/session-previews.d.mts +76 -0
- package/dist/session-previews.mjs +55 -0
- package/dist/session-previews.mjs.map +1 -0
- package/dist/session-review.d.mts +120 -0
- package/dist/session-review.mjs +79 -0
- package/dist/session-review.mjs.map +1 -0
- package/dist/signal-triage.d.mts +20 -0
- package/dist/signal-triage.mjs +85 -0
- package/dist/signal-triage.mjs.map +1 -0
- package/dist/signals.d.mts +20 -0
- package/dist/signals.mjs +19 -0
- package/dist/signals.mjs.map +1 -0
- package/dist/stall.d.mts +41 -0
- package/dist/stall.mjs +83 -0
- package/dist/stall.mjs.map +1 -0
- package/dist/storage.d.mts +4 -0
- package/dist/storage.mjs +4 -0
- package/dist/store/driver.d.mts +65 -0
- package/dist/store/driver.mjs +42 -0
- package/dist/store/driver.mjs.map +1 -0
- package/dist/store/engine.d.mts +683 -0
- package/dist/store/engine.mjs +2309 -0
- package/dist/store/engine.mjs.map +1 -0
- package/dist/store/memory.d.mts +24 -0
- package/dist/store/memory.mjs +69 -0
- package/dist/store/memory.mjs.map +1 -0
- package/dist/store/slack-pr-notifications.d.mts +44 -0
- package/dist/store/slack-pr-notifications.mjs +121 -0
- package/dist/store/slack-pr-notifications.mjs.map +1 -0
- package/dist/store/task-effects.d.mts +38 -0
- package/dist/store/task-effects.mjs +118 -0
- package/dist/store/task-effects.mjs.map +1 -0
- package/dist/store/task-graphs.d.mts +60 -0
- package/dist/store/task-graphs.mjs +212 -0
- package/dist/store/task-graphs.mjs.map +1 -0
- package/dist/store/task-messages.d.mts +136 -0
- package/dist/store/task-messages.mjs +475 -0
- package/dist/store/task-messages.mjs.map +1 -0
- package/dist/store/task-work.d.mts +180 -0
- package/dist/store/task-work.mjs +137 -0
- package/dist/store/task-work.mjs.map +1 -0
- package/dist/store/work-notifications.d.mts +19 -0
- package/dist/store/work-notifications.mjs +48 -0
- package/dist/store/work-notifications.mjs.map +1 -0
- package/dist/sweep.d.mts +79 -0
- package/dist/sweep.mjs +171 -0
- package/dist/sweep.mjs.map +1 -0
- package/dist/task-communication.d.mts +35 -0
- package/dist/task-communication.mjs +47 -0
- package/dist/task-communication.mjs.map +1 -0
- package/dist/task-graph-view.d.mts +267 -0
- package/dist/task-graph-view.mjs +17 -0
- package/dist/task-graph-view.mjs.map +1 -0
- package/dist/task-message-processing.d.mts +60 -0
- package/dist/task-message-processing.mjs +200 -0
- package/dist/task-message-processing.mjs.map +1 -0
- package/dist/task-protocols.d.mts +80 -0
- package/dist/task-protocols.mjs +47 -0
- package/dist/task-protocols.mjs.map +1 -0
- package/dist/tasks.d.mts +17 -0
- package/dist/tasks.mjs +17 -0
- package/dist/vercel-git.d.mts +53 -0
- package/dist/vercel-git.mjs +344 -0
- package/dist/vercel-git.mjs.map +1 -0
- package/dist/vercel-github-api.d.mts +103 -0
- package/dist/vercel-github-api.mjs +363 -0
- package/dist/vercel-github-api.mjs.map +1 -0
- package/dist/vercel.d.mts +34 -2
- package/dist/vercel.mjs +49 -3
- package/dist/vercel.mjs.map +1 -1
- package/dist/work-triage/eve-tool.d.mts +29 -0
- package/dist/work-triage/eve-tool.mjs +32 -0
- package/dist/work-triage/eve-tool.mjs.map +1 -0
- package/dist/work-triage/find-existing-work.d.mts +32 -0
- package/dist/work-triage/find-existing-work.mjs +31 -0
- package/dist/work-triage/find-existing-work.mjs.map +1 -0
- package/dist/work-triage/github.d.mts +15 -0
- package/dist/work-triage/github.mjs +14 -0
- package/dist/work-triage/github.mjs.map +1 -0
- package/dist/work-triage.d.mts +4 -0
- package/dist/work-triage.mjs +3 -0
- package/dist/workflows.d.mts +146 -0
- package/dist/workflows.mjs +139 -0
- package/dist/workflows.mjs.map +1 -0
- package/dist/workspace-diff-assembly.d.mts +33 -0
- package/dist/workspace-diff-assembly.mjs +95 -0
- package/dist/workspace-diff-assembly.mjs.map +1 -0
- package/dist/workspace-diff.d.mts +90 -0
- package/dist/workspace-diff.mjs +61 -0
- package/dist/workspace-diff.mjs.map +1 -0
- package/dist/workspace-files-git.d.mts +15 -0
- package/dist/workspace-files-git.mjs +61 -0
- package/dist/workspace-files-git.mjs.map +1 -0
- package/dist/workspace-files.d.mts +107 -0
- package/dist/workspace-files.mjs +74 -0
- package/dist/workspace-files.mjs.map +1 -0
- package/dist/workspace-snapshot.d.mts +26 -0
- package/dist/workspace-snapshot.mjs +80 -0
- package/dist/workspace-snapshot.mjs.map +1 -0
- package/docs/getting-started.md +104 -0
- package/docs/index.md +100 -0
- package/docs/recipes/cancellation.md +215 -0
- package/docs/recipes/custom-workflow.md +153 -0
- package/docs/recipes/dependent-tasks.md +207 -0
- package/docs/recipes/eve-agent.md +277 -0
- package/docs/recipes/human-input.md +204 -0
- package/docs/recipes/persistence-recovery.md +268 -0
- package/docs/recipes/retry-recovery.md +241 -0
- package/docs/recipes/task-messaging.md +215 -0
- package/docs/recipes/typed-eve-result.md +161 -0
- package/docs/runtime-integration.md +137 -0
- package/package.json +142 -46
- package/dist/approval-JX2Px9IC.mjs +0 -132
- package/dist/approval-JX2Px9IC.mjs.map +0 -1
- package/dist/budget-DzSiIA4t.mjs +0 -42
- package/dist/budget-DzSiIA4t.mjs.map +0 -1
- package/dist/change-D2U6v7Ek.mjs +0 -554
- package/dist/change-D2U6v7Ek.mjs.map +0 -1
- package/dist/communication-Kd9nU5RP.mjs.map +0 -1
- package/dist/coordination-Cq08hb1l.mjs +0 -76
- package/dist/coordination-Cq08hb1l.mjs.map +0 -1
- package/dist/dispatch-BaZS3PT8.d.mts +0 -76
- package/dist/driver-BC0Mt5KG.d.mts +0 -34
- package/dist/driver-Bbd25rNM.mjs +0 -23
- package/dist/driver-Bbd25rNM.mjs.map +0 -1
- package/dist/engine-DFHU2Pe-.d.mts +0 -781
- package/dist/engine-WaUM2cGA.mjs +0 -1068
- package/dist/engine-WaUM2cGA.mjs.map +0 -1
- package/dist/factory-config-BoRXOw43.mjs +0 -50
- package/dist/factory-config-BoRXOw43.mjs.map +0 -1
- package/dist/factory-config-D2yU3G0c.d.mts +0 -58
- package/dist/github-C5HNBN6p.d.mts +0 -279
- package/dist/id-BLIkCVue.mjs.map +0 -1
- package/dist/index-Bw6hggoH.d.mts +0 -40
- package/dist/index.mjs.map +0 -1
- package/dist/task-CcOyOJ3k.mjs +0 -154
- package/dist/task-CcOyOJ3k.mjs.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-graphs.mjs","names":[],"sources":["../../src/store/task-graphs.ts"],"sourcesContent":["import { isDeepStrictEqual } from \"node:util\";\nimport type { WorkNotification } from \"./work-notifications\";\nimport { z } from \"zod\";\nimport { SCHEMA_VERSION } from \"../schema/common\";\nimport type { IsoDateTimeClock } from \"../schema/common\";\nimport { taskIdSchema, type TaskId } from \"../schema/id\";\nimport { taskSchema, type Task } from \"../schema/task\";\nimport {\n taskGraphAdmissionSchema,\n taskGraphDependenciesSchema,\n taskGraphSchema,\n type TaskGraph,\n type TaskGraphNode,\n} from \"../schema/task-graph\";\nimport {\n RecordExistsError,\n RecordNotFoundError,\n VersionConflictError,\n type StoreDriver,\n} from \"./driver\";\n\n/** Engine-owned persistence and validation dependencies for canonical Task graphs. */\nexport interface CreateTaskGraphStoreOptions {\n driver: StoreDriver;\n now: IsoDateTimeClock;\n getTask: (id: TaskId) => Promise<Task | null>;\n validateTask: (task: Task) => void;\n notify?: (event: WorkNotification) => Promise<void>;\n}\n\n/** Canonical bounded graph store for Task membership, ownership, and dependency edges. */\nexport interface TaskGraphStore {\n admit(input: AdmitTaskGraphInput): Promise<void>;\n get(rootTaskId: TaskId): Promise<TaskGraph | null>;\n setDependencies(input: SetTaskGraphDependenciesInput): Promise<TaskGraph>;\n dependenciesSatisfied(input: TaskGraphNodeRef): Promise<boolean>;\n /** Call before every Task transition to running. First admission freezes edges, even if launch fails. */\n seal(input: TaskGraphNodeRef): Promise<void>;\n}\n\n/** Canonical Task snapshot and initial dependency edges admitted atomically to its graph. */\nexport interface AdmitTaskGraphInput {\n task: Task;\n dependencies?: readonly TaskId[];\n}\n\n/** Identifies one Task within its canonical root graph. */\nexport interface TaskGraphNodeRef {\n rootTaskId: TaskId;\n taskId: TaskId;\n}\n\n/** Revision-fenced replacement of one queued Task's graph dependencies. */\nexport interface SetTaskGraphDependenciesInput extends TaskGraphNodeRef {\n dependencies: readonly TaskId[];\n expectedRevision: number;\n}\n\n/** Error raised when a creation retry differs from immutable graph admission intent. */\nexport class TaskGraphConflictError extends Error {\n constructor(taskId: TaskId) {\n super(\n `Task ${taskId} conflicts with its immutable graph admission; reuse the original creation intent`,\n );\n this.name = \"TaskGraphConflictError\";\n }\n}\n\n/** Canonical Task and immutable graph admission snapshot to compare. */\nexport interface AssertTaskAdmissionInput {\n task: Task;\n admission: Task;\n}\n\nfunction immutableTaskIdentity(task: Task): unknown {\n const { id, rootTaskId, parentTaskId, repositoryIds, work, kind, origin, replyTo, area } = task;\n // Omitted optional fields and explicit undefined have the same persisted identity.\n return JSON.parse(\n JSON.stringify({\n id,\n rootTaskId,\n parentTaskId,\n repositoryIds,\n work,\n kind,\n origin,\n replyTo,\n area,\n }),\n );\n}\n\n/** Verifies that a canonical Task preserves the immutable identity admitted to its graph. */\nexport function assertTaskAdmission({ task, admission }: AssertTaskAdmissionInput): void {\n if (!isDeepStrictEqual(immutableTaskIdentity(task), immutableTaskIdentity(admission))) {\n throw new TaskGraphConflictError(admission.id);\n }\n}\n\nfunction creationIntent({\n createdAt: _createdAt,\n updatedAt: _updatedAt,\n ...intent\n}: Task): unknown {\n // JSON persistence drops optional undefined fields; omitted and undefined have the same intent.\n const value: unknown = JSON.parse(JSON.stringify(intent));\n return value;\n}\n\n/**\n * Trusted engine/workflow capability, not an agent tool or HTTP boundary: callers own\n * authentication and workflow binding validation. The engine seals dependencies before\n * transitioning to running; sealing survives a failed launch and rechecks prerequisites\n * on retries. Succeeded prerequisites are terminal under the engine's transition rules.\n */\nexport function createTaskGraphStore({\n driver,\n now,\n getTask,\n validateTask,\n notify,\n}: CreateTaskGraphStoreOptions): TaskGraphStore {\n async function read(rootTaskId: TaskId) {\n taskIdSchema.parse(rootTaskId);\n const record = await driver.get(\"taskGraphs\", rootTaskId);\n if (!record) return null;\n const graph = taskGraphSchema.parse(record.value);\n for (const node of graph.nodes) validateTask(node.admission);\n if (graph.rootTaskId !== rootTaskId)\n throw new Error(\"Stored graph root does not match its storage key\");\n return { graph, version: record.version };\n }\n\n async function canonical(node: TaskGraphNode): Promise<Task | null> {\n const value = await getTask(node.taskId);\n if (value === null) return null;\n const task = taskSchema.parse(value);\n validateTask(task);\n assertTaskAdmission({ task, admission: node.admission });\n return task;\n }\n\n async function satisfied(graph: TaskGraph, node: TaskGraphNode): Promise<boolean> {\n for (const id of node.dependencies) {\n const dependency = graph.nodes.find((entry) => entry.taskId === id);\n if (!dependency || (await canonical(dependency))?.state !== \"succeeded\") return false;\n }\n return true;\n }\n\n return {\n async admit(input) {\n // Zod's custom JSON schema validates but does not clone work.input.\n const task = structuredClone(taskGraphAdmissionSchema.parse(input.task));\n validateTask(task);\n const initialDependencies = taskGraphDependenciesSchema.parse(input.dependencies ?? []);\n const rootTaskId = taskIdSchema.parse(task.rootTaskId);\n const node: TaskGraphNode = {\n taskId: task.id,\n ...(task.parentTaskId === undefined ? {} : { parentTaskId: task.parentTaskId }),\n dependencies: initialDependencies,\n sealed: false,\n admissionDependencies: [...initialDependencies],\n admission: task,\n };\n await canonical(node);\n for (;;) {\n const current = await read(rootTaskId);\n const existing = current?.graph.nodes.find((entry) => entry.taskId === task.id);\n if (existing) {\n if (\n !isDeepStrictEqual(creationIntent(existing.admission), creationIntent(task)) ||\n !isDeepStrictEqual(existing.admissionDependencies, initialDependencies)\n ) {\n throw new TaskGraphConflictError(task.id);\n }\n return;\n }\n const timestamp = now();\n const graph = taskGraphSchema.parse(\n current\n ? {\n ...current.graph,\n revision: current.graph.revision + 1,\n nodes: [...current.graph.nodes, node],\n updatedAt: timestamp,\n }\n : {\n schemaVersion: SCHEMA_VERSION,\n rootTaskId,\n revision: 1,\n nodes: [node],\n createdAt: timestamp,\n updatedAt: timestamp,\n },\n );\n try {\n if (current)\n await driver.update({\n collection: \"taskGraphs\",\n id: rootTaskId,\n value: graph,\n expectedVersion: current.version,\n });\n else await driver.insert({ collection: \"taskGraphs\", id: rootTaskId, value: graph });\n return;\n } catch (error) {\n if (!(error instanceof RecordExistsError || error instanceof VersionConflictError))\n throw error;\n }\n }\n },\n\n async get(rootTaskId) {\n return (await read(rootTaskId))?.graph ?? null;\n },\n\n async setDependencies(input) {\n const { rootTaskId, taskId, dependencies, expectedRevision } = input;\n taskIdSchema.parse(taskId);\n z.number().int().positive().parse(expectedRevision);\n const edges = taskGraphDependenciesSchema.parse(dependencies);\n const current = await read(rootTaskId);\n if (!current) throw new RecordNotFoundError(\"taskGraphs\", rootTaskId);\n if (current.graph.revision !== expectedRevision) {\n throw new VersionConflictError(\"taskGraphs\", rootTaskId, expectedRevision);\n }\n const node = current.graph.nodes.find((entry) => entry.taskId === taskId);\n if (!node) throw new Error(`Task ${taskId} is not registered in graph ${rootTaskId}`);\n if (node.sealed)\n throw new Error(\n `Task ${taskId} dependencies are sealed by first execution admission and cannot be changed`,\n );\n const task = await canonical(node);\n if (!task) throw new RecordNotFoundError(\"tasks\", taskId);\n if (task.state !== \"queued\")\n throw new Error(\n `Task ${taskId} must be queued to change dependencies; current state is ${task.state}`,\n );\n const graph = taskGraphSchema.parse({\n ...current.graph,\n revision: current.graph.revision + 1,\n nodes: current.graph.nodes.map((entry) =>\n entry.taskId === taskId ? { ...entry, dependencies: edges } : entry,\n ),\n updatedAt: now(),\n });\n // Deliberately do not retry: a losing editor must read the winning graph and replan.\n await driver.update({\n collection: \"taskGraphs\",\n id: rootTaskId,\n value: graph,\n expectedVersion: current.version,\n });\n await notify?.({ rootTaskId, key: `dependencies:${rootTaskId}:${graph.revision}` });\n return graph;\n },\n\n async dependenciesSatisfied({ rootTaskId, taskId }) {\n taskIdSchema.parse(taskId);\n const current = await read(rootTaskId);\n const node = current?.graph.nodes.find((entry) => entry.taskId === taskId);\n if (!current || !node || !(await canonical(node))) return false;\n return satisfied(current.graph, node);\n },\n\n async seal({ rootTaskId, taskId }) {\n taskIdSchema.parse(taskId);\n for (;;) {\n const current = await read(rootTaskId);\n if (!current) throw new RecordNotFoundError(\"taskGraphs\", rootTaskId);\n const node = current.graph.nodes.find((entry) => entry.taskId === taskId);\n if (!node) throw new Error(`Task ${taskId} is not registered in graph ${rootTaskId}`);\n const task = await canonical(node);\n if (!task) throw new RecordNotFoundError(\"tasks\", taskId);\n if (!node.sealed && task.state !== \"queued\") {\n throw new Error(\n `Task ${taskId} must be queued for first execution admission; current state is ${task.state}`,\n );\n }\n if (!(await satisfied(current.graph, node))) {\n throw new Error(\n `Task ${taskId} dependencies must all be succeeded before execution admission`,\n );\n }\n if (node.sealed) return;\n const graph = taskGraphSchema.parse({\n ...current.graph,\n revision: current.graph.revision + 1,\n nodes: current.graph.nodes.map((entry) =>\n entry.taskId === taskId ? { ...entry, sealed: true } : entry,\n ),\n updatedAt: now(),\n });\n try {\n await driver.update({\n collection: \"taskGraphs\",\n id: rootTaskId,\n value: graph,\n expectedVersion: current.version,\n });\n return;\n } catch (error) {\n // An edge edit may have won: re-read its dependencies and validate them before sealing.\n if (!(error instanceof VersionConflictError)) throw error;\n }\n }\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { TaskId } from "../schema/id.mjs";
|
|
2
|
+
import { Task } from "../schema/task.mjs";
|
|
3
|
+
import { SendTaskMessageInput, TaskMessage, TaskMessageClaim, TaskMessageDiscard, TaskMessageId, TaskMessagePrincipal, TaskMessageProcessing, TaskMessageProtocolBinding, TaskMessageProtocols } from "../schema/task-message.mjs";
|
|
4
|
+
import { WorkNotification } from "./work-notifications.mjs";
|
|
5
|
+
import { DateClock } from "../schema/common.mjs";
|
|
6
|
+
import { TaskAttemptRef } from "../schema/execution.mjs";
|
|
7
|
+
import { StoreDriver } from "./driver.mjs";
|
|
8
|
+
//#region src/store/task-messages.d.ts
|
|
9
|
+
/** Operation categories evaluated by the Task message authorization boundary. */
|
|
10
|
+
type TaskMessageAction = "send" | "send_result" | "read" | "claim" | "complete" | "discard";
|
|
11
|
+
/** Principal, requested action, and validated Task scope for one authorization decision. */
|
|
12
|
+
interface TaskMessageAuthorizationInput {
|
|
13
|
+
readonly principal: TaskMessagePrincipal;
|
|
14
|
+
readonly action: TaskMessageAction;
|
|
15
|
+
readonly fromTask?: Task;
|
|
16
|
+
readonly toTask: Task;
|
|
17
|
+
}
|
|
18
|
+
/** Trusted application authorization, NOT HTTP authentication. Only literal true grants access. */
|
|
19
|
+
type TaskMessageAuthorize = (input: TaskMessageAuthorizationInput) => boolean | Promise<boolean>;
|
|
20
|
+
/** Engine-owned dependencies and deny-by-default policy used to create a Task message store. */
|
|
21
|
+
interface CreateTaskMessageStoreOptions {
|
|
22
|
+
readonly driver: StoreDriver;
|
|
23
|
+
readonly now: DateClock;
|
|
24
|
+
/**
|
|
25
|
+
* Main engine supplies schema-validated snapshots. Task reads and inbox CAS are separate:
|
|
26
|
+
* cancellation/retry can race send, claim, and caller effects. The envelope retains expectAttempt;
|
|
27
|
+
* consumers must fence effects at their application boundary, not assume atomic cancellation here.
|
|
28
|
+
*/
|
|
29
|
+
readonly getTask: (id: TaskId) => Promise<Task | null>;
|
|
30
|
+
readonly protocols: TaskMessageProtocols;
|
|
31
|
+
readonly authorize: TaskMessageAuthorize;
|
|
32
|
+
readonly notify?: (event: WorkNotification) => Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
/** Authenticated principal and immutable message intent used for one send operation. */
|
|
35
|
+
interface TaskMessageSendInput<Type extends TaskMessageProtocolBinding = TaskMessageProtocolBinding> {
|
|
36
|
+
readonly principal: TaskMessagePrincipal;
|
|
37
|
+
readonly message: SendTaskMessageInput<Type>;
|
|
38
|
+
}
|
|
39
|
+
/** Authenticated principal and recipient identifying one Task inbox. */
|
|
40
|
+
interface TaskMessageRecipientInput {
|
|
41
|
+
readonly principal: TaskMessagePrincipal;
|
|
42
|
+
readonly toTaskId: TaskId;
|
|
43
|
+
}
|
|
44
|
+
/** Authenticated principal, recipient, and immutable message identifying one inbox record. */
|
|
45
|
+
interface TaskMessageLookupInput extends TaskMessageRecipientInput {
|
|
46
|
+
readonly messageId: TaskMessageId;
|
|
47
|
+
}
|
|
48
|
+
/** Recipient attempt and bounded lease requested when claiming one Task message. */
|
|
49
|
+
interface ClaimTaskMessageInput extends Pick<TaskMessageRecipientInput, "principal"> {
|
|
50
|
+
readonly task: TaskAttemptRef;
|
|
51
|
+
readonly leaseMs: number;
|
|
52
|
+
readonly recoverCompleted?: boolean;
|
|
53
|
+
}
|
|
54
|
+
/** Authenticated lease and committed-effects attestation used to acknowledge one message. */
|
|
55
|
+
interface CompleteTaskMessageInput {
|
|
56
|
+
readonly principal: TaskMessagePrincipal;
|
|
57
|
+
readonly claim: TaskMessageClaim;
|
|
58
|
+
readonly effectsCommitted: true;
|
|
59
|
+
}
|
|
60
|
+
/** Authenticated lease whose current validity must be checked before an external effect. */
|
|
61
|
+
interface AssertTaskMessageClaimInput {
|
|
62
|
+
readonly principal: TaskMessagePrincipal;
|
|
63
|
+
readonly claim: TaskMessageClaim;
|
|
64
|
+
}
|
|
65
|
+
/** Authorized reason for discarding one exact pending inbox head. */
|
|
66
|
+
interface DiscardTaskMessageInput extends TaskMessageLookupInput {
|
|
67
|
+
readonly reason: string;
|
|
68
|
+
}
|
|
69
|
+
/** Message identity and stable operation name used to derive an external-effect key. */
|
|
70
|
+
interface TaskMessageEffectKeyInput {
|
|
71
|
+
readonly messageId: TaskMessageId;
|
|
72
|
+
readonly operation: string;
|
|
73
|
+
}
|
|
74
|
+
/** Message and lease returned when one pending inbox head is claimed. */
|
|
75
|
+
interface ClaimTaskMessageResult {
|
|
76
|
+
readonly claim: TaskMessageClaim;
|
|
77
|
+
readonly message: TaskMessage;
|
|
78
|
+
}
|
|
79
|
+
/** Trusted API for immutable Task messages, recipient inboxes, leases, and acknowledgments. */
|
|
80
|
+
interface TaskMessageStore {
|
|
81
|
+
send<const Type extends TaskMessageProtocolBinding>(input: TaskMessageSendInput<Type>): Promise<TaskMessage<Type>>;
|
|
82
|
+
/** Trusted workflow delivery after the source outcome is persisted; requires separate send_result authorization. */
|
|
83
|
+
sendResult<const Type extends TaskMessageProtocolBinding>(input: TaskMessageSendInput<Type>): Promise<TaskMessage<Type>>;
|
|
84
|
+
get(input: TaskMessageLookupInput): Promise<TaskMessage | null>;
|
|
85
|
+
pending(input: TaskMessageRecipientInput): Promise<readonly TaskMessage[]>;
|
|
86
|
+
/**
|
|
87
|
+
* A stale attempt-fenced source or cancelled ordinary source raises TaskMessageSourceFenceError
|
|
88
|
+
* and leaves the head pending. Protocols default to attempt fencing; explicitly task-scoped
|
|
89
|
+
* protocols let workflow triggers survive source retries. Authorized discard can unblock the inbox.
|
|
90
|
+
* Source completion alone does not invalidate previously admitted messages from the same attempt.
|
|
91
|
+
* recoverCompleted defaults to false. It is a trusted application recovery capability for a
|
|
92
|
+
* succeeded/failed recipient: the caller must first validate an exact persisted decision for
|
|
93
|
+
* the pending message, then verify the claimed message matches it. It authorizes no new effects,
|
|
94
|
+
* never permits cancellation recovery, and does not bypass the authorization callback.
|
|
95
|
+
*/
|
|
96
|
+
claim(input: ClaimTaskMessageInput): Promise<ClaimTaskMessageResult | null>;
|
|
97
|
+
/**
|
|
98
|
+
* Call only after effects commit. A matching recipient attempt may acknowledge in any state,
|
|
99
|
+
* including cancellation; acknowledgment authorizes no new effects. Leases are not exactly-once.
|
|
100
|
+
*/
|
|
101
|
+
complete(input: CompleteTaskMessageInput): Promise<TaskMessageProcessing>;
|
|
102
|
+
/** Read-only claim/complete-authorized lease snapshot check, not a lease extension or external effect fence. Completed claims are invalid. */
|
|
103
|
+
assertClaim(input: AssertTaskMessageClaimInput): Promise<void>;
|
|
104
|
+
/** Discard only the pending head with no unexpired recipient lease, under explicit discard authorization. */
|
|
105
|
+
discard(input: DiscardTaskMessageInput): Promise<void>;
|
|
106
|
+
discarded(input: TaskMessageLookupInput): Promise<TaskMessageDiscard | null>;
|
|
107
|
+
processing(input: TaskMessageLookupInput): Promise<TaskMessageProcessing | null>;
|
|
108
|
+
/** Pure key derivation, not a read or an authorization capability. */
|
|
109
|
+
effectKey(input: TaskMessageEffectKeyInput): string;
|
|
110
|
+
}
|
|
111
|
+
/** Base error for Task message admission, authorization, and processing failures. */
|
|
112
|
+
declare class TaskMessageError extends Error {}
|
|
113
|
+
/** Error raised when an idempotency key is reused with different message intent. */
|
|
114
|
+
declare class TaskMessageConflictError extends TaskMessageError {}
|
|
115
|
+
/** Error raised when a configured Task message policy does not grant an operation. */
|
|
116
|
+
declare class TaskMessageAuthorizationError extends TaskMessageError {}
|
|
117
|
+
/** Error raised when a Task inbox claim is expired, replaced, or otherwise invalid. */
|
|
118
|
+
declare class TaskMessageLeaseError extends TaskMessageError {}
|
|
119
|
+
/** Error raised when an inbox head belongs to a stale or cancelled source attempt. */
|
|
120
|
+
declare class TaskMessageSourceFenceError extends TaskMessageError {
|
|
121
|
+
readonly messageId: TaskMessageId;
|
|
122
|
+
readonly source: TaskAttemptRef;
|
|
123
|
+
readonly currentAttempt: number;
|
|
124
|
+
readonly sourceState: Task["state"];
|
|
125
|
+
constructor(messageId: TaskMessageId, source: TaskAttemptRef, currentAttempt: number, sourceState: Task["state"]);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Authorizes recipient-local operations and direct parent-child sends for a trusted
|
|
129
|
+
* Task-bound principal while denying result delivery and discard.
|
|
130
|
+
*/
|
|
131
|
+
declare const taskBoundMessagePolicy: TaskMessageAuthorize;
|
|
132
|
+
/** Creates the trusted, CAS-backed Task message and recipient-inbox store. */
|
|
133
|
+
declare function createTaskMessageStore(options: CreateTaskMessageStoreOptions): TaskMessageStore;
|
|
134
|
+
//#endregion
|
|
135
|
+
export { AssertTaskMessageClaimInput, ClaimTaskMessageInput, ClaimTaskMessageResult, CompleteTaskMessageInput, CreateTaskMessageStoreOptions, DiscardTaskMessageInput, TaskMessageAction, TaskMessageAuthorizationError, TaskMessageAuthorizationInput, TaskMessageAuthorize, TaskMessageConflictError, TaskMessageEffectKeyInput, TaskMessageError, TaskMessageLeaseError, TaskMessageLookupInput, TaskMessageRecipientInput, TaskMessageSendInput, TaskMessageSourceFenceError, TaskMessageStore, createTaskMessageStore, taskBoundMessagePolicy };
|
|
136
|
+
//# sourceMappingURL=task-messages.d.mts.map
|
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
import { taskIdSchema } from "../schema/id.mjs";
|
|
2
|
+
import "../schema/common.mjs";
|
|
3
|
+
import { taskAttemptRefSchema } from "../schema/execution.mjs";
|
|
4
|
+
import { RecordExistsError, VersionConflictError } from "./driver.mjs";
|
|
5
|
+
import { sendTaskMessageInputSchema, taskInboxSchema, taskMessageClaimSchema, taskMessageDiscardSchema, taskMessageIdSchema, taskMessagePrincipalSchema, taskMessageProcessingSchema, taskMessageReservationSchema, taskMessageSchema } from "../schema/task-message.mjs";
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { isDeepStrictEqual } from "node:util";
|
|
9
|
+
//#region src/store/task-messages.ts
|
|
10
|
+
/** Base error for Task message admission, authorization, and processing failures. */
|
|
11
|
+
var TaskMessageError = class extends Error {};
|
|
12
|
+
/** Error raised when an idempotency key is reused with different message intent. */
|
|
13
|
+
var TaskMessageConflictError = class extends TaskMessageError {};
|
|
14
|
+
/** Error raised when a configured Task message policy does not grant an operation. */
|
|
15
|
+
var TaskMessageAuthorizationError = class extends TaskMessageError {};
|
|
16
|
+
/** Error raised when a Task inbox claim is expired, replaced, or otherwise invalid. */
|
|
17
|
+
var TaskMessageLeaseError = class extends TaskMessageError {};
|
|
18
|
+
/** Error raised when an inbox head belongs to a stale or cancelled source attempt. */
|
|
19
|
+
var TaskMessageSourceFenceError = class extends TaskMessageError {
|
|
20
|
+
messageId;
|
|
21
|
+
source;
|
|
22
|
+
currentAttempt;
|
|
23
|
+
sourceState;
|
|
24
|
+
constructor(messageId, source, currentAttempt, sourceState) {
|
|
25
|
+
super(`Inbox blocked by message ${messageId}: source ${source.taskId} attempt ${source.attempt}, current attempt ${currentAttempt}, state ${sourceState}. Message remains pending; an explicitly authorized discard is required to unblock this inbox.`);
|
|
26
|
+
this.messageId = messageId;
|
|
27
|
+
this.source = source;
|
|
28
|
+
this.currentAttempt = currentAttempt;
|
|
29
|
+
this.sourceState = sourceState;
|
|
30
|
+
this.name = "TaskMessageSourceFenceError";
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Authorizes recipient-local operations and direct parent-child sends for a trusted
|
|
35
|
+
* Task-bound principal while denying result delivery and discard.
|
|
36
|
+
*/
|
|
37
|
+
const taskBoundMessagePolicy = ({ principal, action, fromTask, toTask }) => {
|
|
38
|
+
if (action === "discard" || action === "send_result") return false;
|
|
39
|
+
if (action !== "send") return principal.taskId === toTask.id;
|
|
40
|
+
if (!fromTask || !principal.taskId) return false;
|
|
41
|
+
return principal.taskId === fromTask.id && (fromTask.parentTaskId === toTask.id || toTask.parentTaskId === fromTask.id);
|
|
42
|
+
};
|
|
43
|
+
const claimOptionsSchema = z.strictObject({
|
|
44
|
+
leaseMs: z.number().int().min(1).max(3e5).describe("Processing lease duration, at most five minutes."),
|
|
45
|
+
recoverCompleted: z.boolean().default(false).describe("Trusted recovery of an exact existing decision on a succeeded/failed recipient at the same attempt; not permission for new effects or cancelled Tasks.")
|
|
46
|
+
});
|
|
47
|
+
const acknowledgmentSchema = z.strictObject({ effectsCommitted: z.literal(true).describe("Effects committed before this acknowledgment.") });
|
|
48
|
+
const hash = (parts) => createHash("sha256").update(JSON.stringify(parts)).digest("hex");
|
|
49
|
+
const messageIdFor = (source, operation) => taskMessageIdSchema.parse(`msg_${hash([source, operation])}`);
|
|
50
|
+
const messageContent = ({ createdAt: _createdAt, ...content }) => content;
|
|
51
|
+
const retryable = (error) => error instanceof VersionConflictError || error instanceof RecordExistsError;
|
|
52
|
+
/** Creates the trusted, CAS-backed Task message and recipient-inbox store. */
|
|
53
|
+
function createTaskMessageStore(options) {
|
|
54
|
+
const { driver, now, getTask, protocols, authorize } = options;
|
|
55
|
+
if (typeof authorize !== "function") throw new TaskMessageAuthorizationError("Task messages require an explicit authorize callback; no access is granted by default");
|
|
56
|
+
async function task(id) {
|
|
57
|
+
const value = await getTask(taskIdSchema.parse(id));
|
|
58
|
+
if (!value || value.id !== id) throw new TaskMessageError(`Missing Task ${id}`);
|
|
59
|
+
taskIdSchema.parse(value.rootTaskId);
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
async function scope(principal, action, toTaskId, fromTaskId) {
|
|
63
|
+
const toTask = await task(toTaskId);
|
|
64
|
+
const fromTask = fromTaskId === void 0 ? void 0 : await task(fromTaskId);
|
|
65
|
+
if (fromTask && fromTask.rootTaskId !== toTask.rootTaskId) throw new TaskMessageAuthorizationError("Cross-root Task messages are not permitted");
|
|
66
|
+
if ((action === "send" || action === "send_result") && principal.taskId !== void 0 && principal.taskId !== fromTaskId) throw new TaskMessageAuthorizationError("Task-bound sender must equal fromTaskId");
|
|
67
|
+
if (await authorize(structuredClone({
|
|
68
|
+
principal,
|
|
69
|
+
action,
|
|
70
|
+
fromTask,
|
|
71
|
+
toTask
|
|
72
|
+
})) !== true) throw new TaskMessageAuthorizationError(`Task message ${action} denied for ${principal.id}`);
|
|
73
|
+
return {
|
|
74
|
+
fromTask,
|
|
75
|
+
toTask
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function matchingAttempt(value, expected) {
|
|
79
|
+
if (value.attempt !== expected) throw new TaskMessageError(`Task ${value.id} requires current attempt ${value.attempt}; received ${expected}`);
|
|
80
|
+
}
|
|
81
|
+
function activeAttempt(value, expected) {
|
|
82
|
+
if (value.attempt !== expected || ![
|
|
83
|
+
"queued",
|
|
84
|
+
"running",
|
|
85
|
+
"needs_human"
|
|
86
|
+
].includes(value.state)) throw new TaskMessageError(`Task ${value.id} requires current attempt ${value.attempt} and queued, running, or needs_human state`);
|
|
87
|
+
}
|
|
88
|
+
function resultAttempt(value, expected) {
|
|
89
|
+
matchingAttempt(value, expected);
|
|
90
|
+
if (![
|
|
91
|
+
"succeeded",
|
|
92
|
+
"failed",
|
|
93
|
+
"cancelled"
|
|
94
|
+
].includes(value.state)) throw new TaskMessageError(`Task ${value.id} requires a persisted succeeded, failed, or cancelled outcome for result delivery`);
|
|
95
|
+
}
|
|
96
|
+
function claimableAttempt(value, expected, recoverCompleted) {
|
|
97
|
+
if (recoverCompleted && (value.state === "succeeded" || value.state === "failed")) matchingAttempt(value, expected);
|
|
98
|
+
else activeAttempt(value, expected);
|
|
99
|
+
}
|
|
100
|
+
function parseMessage(value) {
|
|
101
|
+
const { causationId, delivery, ...message } = taskMessageSchema.parse(value);
|
|
102
|
+
if (message.id !== messageIdFor(message.fromTaskId, message.operationId)) throw new TaskMessageConflictError("Stored message ID does not match its source and operation");
|
|
103
|
+
return {
|
|
104
|
+
...message,
|
|
105
|
+
...causationId === void 0 ? {} : { causationId },
|
|
106
|
+
...delivery === void 0 ? {} : { delivery },
|
|
107
|
+
payload: protocols.validateStored(message.type, message.payload)
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function correlateMessage(message, type) {
|
|
111
|
+
if (message.type.id !== type.id || message.type.version !== type.version) throw new TaskMessageConflictError("Message protocol differs from its send intent");
|
|
112
|
+
const payload = protocols.validateStored(type, message.payload);
|
|
113
|
+
return structuredClone({
|
|
114
|
+
...message,
|
|
115
|
+
payload
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
async function deliveredMessage(intent) {
|
|
119
|
+
const stored = await driver.get("taskMessages", intent.id);
|
|
120
|
+
if (!stored) return null;
|
|
121
|
+
const delivered = parseMessage(stored.value);
|
|
122
|
+
if (!isDeepStrictEqual(messageContent(delivered), messageContent(intent))) throw new TaskMessageConflictError(`Operation ${intent.operationId} was reused with different content or attempt`);
|
|
123
|
+
return delivered;
|
|
124
|
+
}
|
|
125
|
+
async function immutable(collection, id, value, parse) {
|
|
126
|
+
const validated = parse(value);
|
|
127
|
+
try {
|
|
128
|
+
await driver.insert({
|
|
129
|
+
collection,
|
|
130
|
+
id,
|
|
131
|
+
value: validated
|
|
132
|
+
});
|
|
133
|
+
} catch (error) {
|
|
134
|
+
if (!(error instanceof RecordExistsError)) throw error;
|
|
135
|
+
const stored = await driver.get(collection, id);
|
|
136
|
+
if (!stored || !isDeepStrictEqual(parse(stored.value), validated)) throw new TaskMessageConflictError(`Immutable record ${id} has different content`);
|
|
137
|
+
}
|
|
138
|
+
return validated;
|
|
139
|
+
}
|
|
140
|
+
async function inbox(toTaskId) {
|
|
141
|
+
const stored = await driver.get("taskInboxes", toTaskId);
|
|
142
|
+
const value = stored ? taskInboxSchema.parse(stored.value) : taskInboxSchema.parse({
|
|
143
|
+
schemaVersion: 1,
|
|
144
|
+
toTaskId,
|
|
145
|
+
generation: 0,
|
|
146
|
+
pending: []
|
|
147
|
+
});
|
|
148
|
+
if (value.toTaskId !== toTaskId) throw new TaskMessageError("Stored inbox recipient mismatch");
|
|
149
|
+
value.pending = value.pending.map(parseMessage);
|
|
150
|
+
return {
|
|
151
|
+
value,
|
|
152
|
+
version: stored?.version
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
async function saveInbox(value, version) {
|
|
156
|
+
const validated = taskInboxSchema.parse(value);
|
|
157
|
+
try {
|
|
158
|
+
if (version === void 0) await driver.insert({
|
|
159
|
+
collection: "taskInboxes",
|
|
160
|
+
id: value.toTaskId,
|
|
161
|
+
value: validated
|
|
162
|
+
});
|
|
163
|
+
else await driver.update({
|
|
164
|
+
collection: "taskInboxes",
|
|
165
|
+
id: value.toTaskId,
|
|
166
|
+
value: validated,
|
|
167
|
+
expectedVersion: version
|
|
168
|
+
});
|
|
169
|
+
return true;
|
|
170
|
+
} catch (error) {
|
|
171
|
+
if (retryable(error)) return false;
|
|
172
|
+
throw error;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
async function repair(principal, toTaskId, action, targetId) {
|
|
176
|
+
for (;;) {
|
|
177
|
+
await scope(principal, action, toTaskId);
|
|
178
|
+
const current = await inbox(toTaskId);
|
|
179
|
+
for (const message of current.value.pending) {
|
|
180
|
+
if (targetId !== void 0 && message.id !== targetId) continue;
|
|
181
|
+
await immutable("taskMessages", message.id, message, parseMessage);
|
|
182
|
+
}
|
|
183
|
+
const { completion, rejection } = current.value;
|
|
184
|
+
const resolved = completion ?? rejection;
|
|
185
|
+
if (!resolved || targetId !== void 0 && resolved.messageId !== targetId) return current;
|
|
186
|
+
if (completion) await immutable("taskMessageProcessing", completion.messageId, completion, (value) => taskMessageProcessingSchema.parse(value));
|
|
187
|
+
if (rejection) await immutable("taskMessageDiscards", rejection.messageId, rejection, (value) => taskMessageDiscardSchema.parse(value));
|
|
188
|
+
if (await saveInbox({
|
|
189
|
+
...current.value,
|
|
190
|
+
pending: current.value.pending.filter((message) => message.id !== resolved.messageId),
|
|
191
|
+
lease: void 0,
|
|
192
|
+
completion: void 0,
|
|
193
|
+
rejection: void 0
|
|
194
|
+
}, current.version)) continue;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
async function reserve(message, expectAttempt) {
|
|
198
|
+
const key = `reservation_${message.id}`;
|
|
199
|
+
const reservation = taskMessageReservationSchema.parse({
|
|
200
|
+
schemaVersion: 1,
|
|
201
|
+
message,
|
|
202
|
+
expectAttempt
|
|
203
|
+
});
|
|
204
|
+
try {
|
|
205
|
+
await driver.insert({
|
|
206
|
+
collection: "taskMessageProcessing",
|
|
207
|
+
id: key,
|
|
208
|
+
value: reservation
|
|
209
|
+
});
|
|
210
|
+
return message;
|
|
211
|
+
} catch (error) {
|
|
212
|
+
if (!(error instanceof RecordExistsError)) throw error;
|
|
213
|
+
const stored = await driver.get("taskMessageProcessing", key);
|
|
214
|
+
if (!stored) throw new TaskMessageConflictError(`Missing operation reservation ${message.id}`);
|
|
215
|
+
const existing = taskMessageReservationSchema.parse(stored.value);
|
|
216
|
+
const original = parseMessage(existing.message);
|
|
217
|
+
if (existing.expectAttempt !== expectAttempt || !isDeepStrictEqual(messageContent(original), messageContent(message))) throw new TaskMessageConflictError(`Operation ${message.operationId} was reused with different content or attempt`);
|
|
218
|
+
return original;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
async function readMessage(principal, toTaskId, messageId, action) {
|
|
222
|
+
taskMessageIdSchema.parse(messageId);
|
|
223
|
+
await scope(principal, action, toTaskId);
|
|
224
|
+
await repair(principal, toTaskId, action, messageId);
|
|
225
|
+
const stored = await driver.get("taskMessages", messageId);
|
|
226
|
+
if (!stored) return null;
|
|
227
|
+
const message = parseMessage(stored.value);
|
|
228
|
+
if (message.id !== messageId || message.toTaskId !== toTaskId) throw new TaskMessageAuthorizationError("Message does not belong to this recipient");
|
|
229
|
+
await scope(principal, action, toTaskId, message.fromTaskId);
|
|
230
|
+
return message;
|
|
231
|
+
}
|
|
232
|
+
async function processingRecord(messageId) {
|
|
233
|
+
const stored = await driver.get("taskMessageProcessing", messageId);
|
|
234
|
+
if (!stored) return null;
|
|
235
|
+
const record = taskMessageProcessingSchema.parse(stored.value);
|
|
236
|
+
if (record.messageId !== messageId) throw new TaskMessageError("Processing record identity mismatch");
|
|
237
|
+
return record;
|
|
238
|
+
}
|
|
239
|
+
async function discardRecord(messageId) {
|
|
240
|
+
const stored = await driver.get("taskMessageDiscards", messageId);
|
|
241
|
+
if (!stored) return null;
|
|
242
|
+
const record = taskMessageDiscardSchema.parse(stored.value);
|
|
243
|
+
if (record.messageId !== messageId) throw new TaskMessageError("Discard record identity mismatch");
|
|
244
|
+
return record;
|
|
245
|
+
}
|
|
246
|
+
async function requireLiveClaim(principal, claim, current) {
|
|
247
|
+
if (principal.id !== claim.principalId) throw new TaskMessageAuthorizationError("Only the claiming principal may validate this lease");
|
|
248
|
+
matchingAttempt(await task(claim.toTaskId), claim.attempt);
|
|
249
|
+
const time = now();
|
|
250
|
+
if (current.completion || current.rejection || current.generation !== claim.generation || !isDeepStrictEqual(current.lease, claim) || Date.parse(claim.expiresAt) <= time.getTime()) throw new TaskMessageLeaseError("Lease expired, resolved, or was replaced; claim again before processing");
|
|
251
|
+
return time;
|
|
252
|
+
}
|
|
253
|
+
function requireClaim(record, claim) {
|
|
254
|
+
const { schemaVersion: _schemaVersion, completedAt: _completedAt, effectsCommitted: _effectsCommitted, ...accepted } = record;
|
|
255
|
+
if (!isDeepStrictEqual(accepted, claim)) throw new TaskMessageLeaseError("Processing acknowledgment belongs to a different lease generation");
|
|
256
|
+
return record;
|
|
257
|
+
}
|
|
258
|
+
async function send(context, input, delivery) {
|
|
259
|
+
const principal = taskMessagePrincipalSchema.parse(context);
|
|
260
|
+
const parsed = sendTaskMessageInputSchema.parse(input);
|
|
261
|
+
const payload = protocols.validate(input.type, parsed.payload);
|
|
262
|
+
const { expectAttempt } = parsed;
|
|
263
|
+
const action = delivery === "result" ? "send_result" : "send";
|
|
264
|
+
await scope(principal, action, parsed.toTaskId, parsed.fromTaskId);
|
|
265
|
+
const intent = correlateMessage(parseMessage({
|
|
266
|
+
...parsed,
|
|
267
|
+
payload,
|
|
268
|
+
delivery,
|
|
269
|
+
schemaVersion: 1,
|
|
270
|
+
id: messageIdFor(parsed.fromTaskId, parsed.operationId),
|
|
271
|
+
createdAt: now().toISOString()
|
|
272
|
+
}), input.type);
|
|
273
|
+
const delivered = await deliveredMessage(intent);
|
|
274
|
+
if (delivery === "result") resultAttempt(await task(parsed.fromTaskId), expectAttempt);
|
|
275
|
+
if (delivered) return correlateMessage(delivered, input.type);
|
|
276
|
+
if (delivery !== "result") activeAttempt(await task(parsed.fromTaskId), expectAttempt);
|
|
277
|
+
const recipient = await task(parsed.toTaskId);
|
|
278
|
+
activeAttempt(recipient, recipient.attempt);
|
|
279
|
+
if (parsed.causationId) {
|
|
280
|
+
const stored = await driver.get("taskMessages", parsed.causationId);
|
|
281
|
+
if (!stored) throw new TaskMessageError("Causation message must already be durably delivered");
|
|
282
|
+
const cause = parseMessage(stored.value);
|
|
283
|
+
await scope(principal, "read", cause.toTaskId, cause.fromTaskId);
|
|
284
|
+
if ((await task(cause.toTaskId)).rootTaskId !== recipient.rootTaskId) throw new TaskMessageAuthorizationError("Causation must stay within the Task root");
|
|
285
|
+
}
|
|
286
|
+
const message = correlateMessage(await reserve(intent, expectAttempt), input.type);
|
|
287
|
+
for (;;) {
|
|
288
|
+
await scope(principal, action, message.toTaskId, message.fromTaskId);
|
|
289
|
+
const current = await inbox(message.toTaskId);
|
|
290
|
+
const delivered = await deliveredMessage(message);
|
|
291
|
+
if (delivery === "result") resultAttempt(await task(message.fromTaskId), expectAttempt);
|
|
292
|
+
if (delivered) return correlateMessage(delivered, input.type);
|
|
293
|
+
if (delivery !== "result") activeAttempt(await task(message.fromTaskId), expectAttempt);
|
|
294
|
+
const recipient = await task(message.toTaskId);
|
|
295
|
+
activeAttempt(recipient, recipient.attempt);
|
|
296
|
+
const pending = current.value.pending.find((entry) => entry.id === message.id);
|
|
297
|
+
if (pending) {
|
|
298
|
+
if (!isDeepStrictEqual(pending, message)) throw new TaskMessageConflictError("Inbox message differs from reserved intent");
|
|
299
|
+
} else {
|
|
300
|
+
if (current.value.pending.length >= 256) throw new TaskMessageError(`Recipient inbox is full; maximum 256 pending messages`);
|
|
301
|
+
if (!await saveInbox({
|
|
302
|
+
...current.value,
|
|
303
|
+
pending: [...current.value.pending, message]
|
|
304
|
+
}, current.version)) continue;
|
|
305
|
+
}
|
|
306
|
+
return correlateMessage(await immutable("taskMessages", message.id, message, parseMessage), input.type);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
async function sendAndNotify(principal, input, delivery) {
|
|
310
|
+
const message = await send(principal, input, delivery);
|
|
311
|
+
if (options.notify) {
|
|
312
|
+
const recipient = await task(message.toTaskId);
|
|
313
|
+
await options.notify({
|
|
314
|
+
rootTaskId: recipient.rootTaskId,
|
|
315
|
+
key: `message:${message.id}`
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
return message;
|
|
319
|
+
}
|
|
320
|
+
return {
|
|
321
|
+
send: ({ principal, message }) => sendAndNotify(principal, message),
|
|
322
|
+
sendResult: ({ principal, message }) => sendAndNotify(principal, message, "result"),
|
|
323
|
+
async get({ principal: context, toTaskId, messageId }) {
|
|
324
|
+
return readMessage(taskMessagePrincipalSchema.parse(context), toTaskId, messageId, "read");
|
|
325
|
+
},
|
|
326
|
+
async pending({ principal: context, toTaskId }) {
|
|
327
|
+
const principal = taskMessagePrincipalSchema.parse(context);
|
|
328
|
+
const current = await repair(principal, toTaskId, "read");
|
|
329
|
+
for (const message of current.value.pending) await scope(principal, "read", toTaskId, message.fromTaskId);
|
|
330
|
+
return current.value.pending;
|
|
331
|
+
},
|
|
332
|
+
async claim({ principal: context, task: inputTask, ...input }) {
|
|
333
|
+
const principal = taskMessagePrincipalSchema.parse(context);
|
|
334
|
+
const { taskId: toTaskId, attempt } = taskAttemptRefSchema.parse(inputTask);
|
|
335
|
+
const { leaseMs, recoverCompleted } = claimOptionsSchema.parse(input);
|
|
336
|
+
for (;;) {
|
|
337
|
+
const current = await repair(principal, toTaskId, "claim");
|
|
338
|
+
const message = current.value.pending[0];
|
|
339
|
+
if (message) await scope(principal, "claim", toTaskId, message.fromTaskId);
|
|
340
|
+
const recipient = await task(toTaskId);
|
|
341
|
+
claimableAttempt(recipient, attempt, recoverCompleted);
|
|
342
|
+
if (current.value.lease && current.value.lease.attempt === recipient.attempt && Date.parse(current.value.lease.expiresAt) > now().getTime()) return null;
|
|
343
|
+
if (!message) return null;
|
|
344
|
+
const source = await task(message.fromTaskId);
|
|
345
|
+
if (protocols.sourceFence(message.type) === "attempt" && source.attempt !== message.expectAttempt || source.state === "cancelled" && message.delivery !== "result") throw new TaskMessageSourceFenceError(message.id, {
|
|
346
|
+
taskId: source.id,
|
|
347
|
+
attempt: message.expectAttempt
|
|
348
|
+
}, source.attempt, source.state);
|
|
349
|
+
claimableAttempt(await task(toTaskId), attempt, recoverCompleted);
|
|
350
|
+
const time = now();
|
|
351
|
+
const claim = taskMessageClaimSchema.parse({
|
|
352
|
+
messageId: message.id,
|
|
353
|
+
toTaskId,
|
|
354
|
+
generation: current.value.generation + 1,
|
|
355
|
+
principalId: principal.id,
|
|
356
|
+
attempt,
|
|
357
|
+
expiresAt: new Date(time.getTime() + leaseMs).toISOString()
|
|
358
|
+
});
|
|
359
|
+
if (await saveInbox({
|
|
360
|
+
...current.value,
|
|
361
|
+
generation: claim.generation,
|
|
362
|
+
lease: claim
|
|
363
|
+
}, current.version)) return {
|
|
364
|
+
claim,
|
|
365
|
+
message
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
},
|
|
369
|
+
async complete({ principal: context, claim: input, effectsCommitted }) {
|
|
370
|
+
const principal = taskMessagePrincipalSchema.parse(context);
|
|
371
|
+
const claim = taskMessageClaimSchema.parse(input);
|
|
372
|
+
acknowledgmentSchema.parse({ effectsCommitted });
|
|
373
|
+
if (principal.id !== claim.principalId) throw new TaskMessageAuthorizationError("Only the claiming principal may acknowledge processing");
|
|
374
|
+
const message = await readMessage(principal, claim.toTaskId, claim.messageId, "complete");
|
|
375
|
+
if (!message) throw new TaskMessageLeaseError("Claimed message does not exist");
|
|
376
|
+
for (;;) {
|
|
377
|
+
await scope(principal, "complete", claim.toTaskId, message.fromTaskId);
|
|
378
|
+
const current = await inbox(claim.toTaskId);
|
|
379
|
+
const completed = await processingRecord(claim.messageId);
|
|
380
|
+
if (completed) return requireClaim(completed, claim);
|
|
381
|
+
if (current.value.completion) {
|
|
382
|
+
requireClaim(current.value.completion, claim);
|
|
383
|
+
await repair(principal, claim.toTaskId, "complete", claim.messageId);
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
const time = await requireLiveClaim(principal, claim, current.value);
|
|
387
|
+
const completion = taskMessageProcessingSchema.parse({
|
|
388
|
+
...claim,
|
|
389
|
+
schemaVersion: 1,
|
|
390
|
+
effectsCommitted: true,
|
|
391
|
+
completedAt: time.toISOString()
|
|
392
|
+
});
|
|
393
|
+
if (!await saveInbox({
|
|
394
|
+
...current.value,
|
|
395
|
+
completion
|
|
396
|
+
}, current.version)) continue;
|
|
397
|
+
await repair(principal, claim.toTaskId, "complete", claim.messageId);
|
|
398
|
+
return completion;
|
|
399
|
+
}
|
|
400
|
+
},
|
|
401
|
+
async assertClaim({ principal: context, claim: input }) {
|
|
402
|
+
const principal = taskMessagePrincipalSchema.parse(context);
|
|
403
|
+
const claim = taskMessageClaimSchema.parse(input);
|
|
404
|
+
if (principal.id !== claim.principalId) throw new TaskMessageAuthorizationError("Only the claiming principal may validate this lease");
|
|
405
|
+
await scope(principal, "claim", claim.toTaskId);
|
|
406
|
+
const stored = await driver.get("taskMessages", claim.messageId);
|
|
407
|
+
if (!stored) throw new TaskMessageLeaseError("Claimed message does not exist");
|
|
408
|
+
const message = parseMessage(stored.value);
|
|
409
|
+
if (message.id !== claim.messageId || message.toTaskId !== claim.toTaskId) throw new TaskMessageAuthorizationError("Claim does not belong to this recipient");
|
|
410
|
+
await scope(principal, "claim", claim.toTaskId, message.fromTaskId);
|
|
411
|
+
await scope(principal, "complete", claim.toTaskId, message.fromTaskId);
|
|
412
|
+
const completed = await processingRecord(claim.messageId);
|
|
413
|
+
if (completed) {
|
|
414
|
+
requireClaim(completed, claim);
|
|
415
|
+
throw new TaskMessageLeaseError("Message processing is already complete; the claim is no longer active");
|
|
416
|
+
}
|
|
417
|
+
await requireLiveClaim(principal, claim, (await inbox(claim.toTaskId)).value);
|
|
418
|
+
},
|
|
419
|
+
async discard({ principal: context, toTaskId, messageId, reason: inputReason }) {
|
|
420
|
+
const principal = taskMessagePrincipalSchema.parse(context);
|
|
421
|
+
const reason = taskMessageDiscardSchema.shape.reason.parse(inputReason);
|
|
422
|
+
const message = await readMessage(principal, toTaskId, messageId, "discard");
|
|
423
|
+
if (!message) throw new TaskMessageError("Cannot discard a missing message");
|
|
424
|
+
for (;;) {
|
|
425
|
+
await scope(principal, "discard", toTaskId, message.fromTaskId);
|
|
426
|
+
const current = await repair(principal, toTaskId, "discard", messageId);
|
|
427
|
+
const discarded = await discardRecord(messageId);
|
|
428
|
+
if (discarded) {
|
|
429
|
+
if (discarded.toTaskId !== toTaskId || discarded.reason !== reason || !isDeepStrictEqual(discarded.by, principal)) throw new TaskMessageConflictError("Message already has a different discard decision");
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
if (await processingRecord(messageId)) throw new TaskMessageConflictError("Processed messages cannot be discarded");
|
|
433
|
+
if (current.value.pending[0]?.id !== messageId) throw new TaskMessageError("Only the pending inbox head may be discarded");
|
|
434
|
+
const time = now();
|
|
435
|
+
if (current.value.lease && Date.parse(current.value.lease.expiresAt) > time.getTime()) throw new TaskMessageLeaseError("Cannot discard while the recipient has an unexpired lease, including a prior Task attempt");
|
|
436
|
+
const rejection = taskMessageDiscardSchema.parse({
|
|
437
|
+
schemaVersion: 1,
|
|
438
|
+
messageId,
|
|
439
|
+
toTaskId,
|
|
440
|
+
reason,
|
|
441
|
+
by: principal,
|
|
442
|
+
discardedAt: time.toISOString()
|
|
443
|
+
});
|
|
444
|
+
if (!await saveInbox({
|
|
445
|
+
...current.value,
|
|
446
|
+
lease: void 0,
|
|
447
|
+
rejection
|
|
448
|
+
}, current.version)) continue;
|
|
449
|
+
await repair(principal, toTaskId, "discard", messageId);
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
},
|
|
453
|
+
async discarded({ principal: context, toTaskId, messageId }) {
|
|
454
|
+
if (!await readMessage(taskMessagePrincipalSchema.parse(context), toTaskId, messageId, "read")) return null;
|
|
455
|
+
const record = await discardRecord(messageId);
|
|
456
|
+
if (record && record.toTaskId !== toTaskId) throw new TaskMessageError("Discard record recipient mismatch");
|
|
457
|
+
return record;
|
|
458
|
+
},
|
|
459
|
+
async processing({ principal: context, toTaskId, messageId }) {
|
|
460
|
+
if (!await readMessage(taskMessagePrincipalSchema.parse(context), toTaskId, messageId, "read")) return null;
|
|
461
|
+
const record = await processingRecord(messageId);
|
|
462
|
+
if (record && record.toTaskId !== toTaskId) throw new TaskMessageError("Processing record recipient mismatch");
|
|
463
|
+
return record;
|
|
464
|
+
},
|
|
465
|
+
effectKey({ messageId, operation }) {
|
|
466
|
+
taskMessageIdSchema.parse(messageId);
|
|
467
|
+
z.string().min(1).max(256).parse(operation);
|
|
468
|
+
return `effect_${hash([messageId, operation])}`;
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
//#endregion
|
|
473
|
+
export { TaskMessageAuthorizationError, TaskMessageConflictError, TaskMessageError, TaskMessageLeaseError, TaskMessageSourceFenceError, createTaskMessageStore, taskBoundMessagePolicy };
|
|
474
|
+
|
|
475
|
+
//# sourceMappingURL=task-messages.mjs.map
|