@smthrs/control 0.0.0-stage → 1.0.0-rc.4
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 +194 -0
- package/LICENSE +21 -0
- package/README.md +168 -2
- package/dist/cjs/ApprovalAuthority.d.ts +73 -0
- package/dist/cjs/ApprovalAuthority.d.ts.map +1 -0
- package/dist/cjs/ApprovalAuthority.js +62 -0
- package/dist/cjs/ApprovalAuthority.js.map +7 -0
- package/dist/cjs/Cancellation.d.ts +107 -0
- package/dist/cjs/Cancellation.d.ts.map +1 -0
- package/dist/cjs/Cancellation.js +72 -0
- package/dist/cjs/Cancellation.js.map +7 -0
- package/dist/cjs/Channels.d.ts +170 -0
- package/dist/cjs/Channels.d.ts.map +1 -0
- package/dist/cjs/Channels.js +278 -0
- package/dist/cjs/Channels.js.map +7 -0
- package/dist/cjs/Control.d.ts +202 -0
- package/dist/cjs/Control.d.ts.map +1 -0
- package/dist/cjs/Control.js +47 -0
- package/dist/cjs/Control.js.map +7 -0
- package/dist/cjs/ControlClient.d.ts +52 -0
- package/dist/cjs/ControlClient.d.ts.map +1 -0
- package/dist/cjs/ControlClient.js +191 -0
- package/dist/cjs/ControlClient.js.map +7 -0
- package/dist/cjs/ControlError.d.ts +318 -0
- package/dist/cjs/ControlError.d.ts.map +1 -0
- package/dist/cjs/ControlError.js +249 -0
- package/dist/cjs/ControlError.js.map +7 -0
- package/dist/cjs/ControlExecutor.d.ts +372 -0
- package/dist/cjs/ControlExecutor.d.ts.map +1 -0
- package/dist/cjs/ControlExecutor.js +123 -0
- package/dist/cjs/ControlExecutor.js.map +7 -0
- package/dist/cjs/ControlFacts.d.ts +454 -0
- package/dist/cjs/ControlFacts.d.ts.map +1 -0
- package/dist/cjs/ControlFacts.js +261 -0
- package/dist/cjs/ControlFacts.js.map +7 -0
- package/dist/cjs/ControlLive.d.ts +23 -0
- package/dist/cjs/ControlLive.d.ts.map +1 -0
- package/dist/cjs/ControlLive.js +1293 -0
- package/dist/cjs/ControlLive.js.map +7 -0
- package/dist/cjs/ControlRpcs.d.ts +1204 -0
- package/dist/cjs/ControlRpcs.d.ts.map +1 -0
- package/dist/cjs/ControlRpcs.js +247 -0
- package/dist/cjs/ControlRpcs.js.map +7 -0
- package/dist/cjs/ControlRuntime.d.ts +635 -0
- package/dist/cjs/ControlRuntime.d.ts.map +1 -0
- package/dist/cjs/ControlRuntime.js +744 -0
- package/dist/cjs/ControlRuntime.js.map +7 -0
- package/dist/cjs/ControlSchema.d.ts +2642 -0
- package/dist/cjs/ControlSchema.d.ts.map +1 -0
- package/dist/cjs/ControlSchema.js +634 -0
- package/dist/cjs/ControlSchema.js.map +7 -0
- package/dist/cjs/ControlServer.d.ts +51 -0
- package/dist/cjs/ControlServer.d.ts.map +1 -0
- package/dist/cjs/ControlServer.js +121 -0
- package/dist/cjs/ControlServer.js.map +7 -0
- package/dist/cjs/Credential.d.ts +136 -0
- package/dist/cjs/Credential.d.ts.map +1 -0
- package/dist/cjs/Credential.js +168 -0
- package/dist/cjs/Credential.js.map +7 -0
- package/dist/cjs/CredentialCipher.d.ts +90 -0
- package/dist/cjs/CredentialCipher.d.ts.map +1 -0
- package/dist/cjs/CredentialCipher.js +45 -0
- package/dist/cjs/CredentialCipher.js.map +7 -0
- package/dist/cjs/CredentialStore.d.ts +97 -0
- package/dist/cjs/CredentialStore.d.ts.map +1 -0
- package/dist/cjs/CredentialStore.js +81 -0
- package/dist/cjs/CredentialStore.js.map +7 -0
- package/dist/cjs/DispatchReader.d.ts +112 -0
- package/dist/cjs/DispatchReader.d.ts.map +1 -0
- package/dist/cjs/DispatchReader.js +45 -0
- package/dist/cjs/DispatchReader.js.map +7 -0
- package/dist/cjs/Health.d.ts +333 -0
- package/dist/cjs/Health.d.ts.map +1 -0
- package/dist/cjs/Health.js +311 -0
- package/dist/cjs/Health.js.map +7 -0
- package/dist/cjs/JevSessionChecker.d.ts +57 -0
- package/dist/cjs/JevSessionChecker.d.ts.map +1 -0
- package/dist/cjs/JevSessionChecker.js +113 -0
- package/dist/cjs/JevSessionChecker.js.map +7 -0
- package/dist/cjs/Lineage.d.ts +131 -0
- package/dist/cjs/Lineage.d.ts.map +1 -0
- package/dist/cjs/Lineage.js +81 -0
- package/dist/cjs/Lineage.js.map +7 -0
- package/dist/cjs/Migrations.d.ts +34 -0
- package/dist/cjs/Migrations.d.ts.map +1 -0
- package/dist/cjs/Migrations.js +60 -0
- package/dist/cjs/Migrations.js.map +7 -0
- package/dist/cjs/Monitor.d.ts +282 -0
- package/dist/cjs/Monitor.d.ts.map +1 -0
- package/dist/cjs/Monitor.js +283 -0
- package/dist/cjs/Monitor.js.map +7 -0
- package/dist/cjs/ScopedToken.d.ts +193 -0
- package/dist/cjs/ScopedToken.d.ts.map +1 -0
- package/dist/cjs/ScopedToken.js +135 -0
- package/dist/cjs/ScopedToken.js.map +7 -0
- package/dist/cjs/SqlControlRuntime.d.ts +161 -0
- package/dist/cjs/SqlControlRuntime.d.ts.map +1 -0
- package/dist/cjs/SqlControlRuntime.js +1522 -0
- package/dist/cjs/SqlControlRuntime.js.map +7 -0
- package/dist/cjs/SqlCredentialStore.d.ts +43 -0
- package/dist/cjs/SqlCredentialStore.d.ts.map +1 -0
- package/dist/cjs/SqlCredentialStore.js +113 -0
- package/dist/cjs/SqlCredentialStore.js.map +7 -0
- package/dist/cjs/Steering.d.ts +69 -0
- package/dist/cjs/Steering.d.ts.map +1 -0
- package/dist/cjs/Steering.js +49 -0
- package/dist/cjs/Steering.js.map +7 -0
- package/dist/cjs/SystemFlows.d.ts +223 -0
- package/dist/cjs/SystemFlows.d.ts.map +1 -0
- package/dist/cjs/SystemFlows.js +195 -0
- package/dist/cjs/SystemFlows.js.map +7 -0
- package/dist/cjs/WebCryptoCipher.d.ts +49 -0
- package/dist/cjs/WebCryptoCipher.d.ts.map +1 -0
- package/dist/cjs/WebCryptoCipher.js +129 -0
- package/dist/cjs/WebCryptoCipher.js.map +7 -0
- package/dist/cjs/WebhookChannel.d.ts +113 -0
- package/dist/cjs/WebhookChannel.d.ts.map +1 -0
- package/dist/cjs/WebhookChannel.js +98 -0
- package/dist/cjs/WebhookChannel.js.map +7 -0
- package/dist/cjs/index.d.ts +160 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +91 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/cjs/internal/MutationBoundary.d.ts +27 -0
- package/dist/cjs/internal/MutationBoundary.d.ts.map +1 -0
- package/dist/cjs/internal/MutationBoundary.js +50 -0
- package/dist/cjs/internal/MutationBoundary.js.map +7 -0
- package/dist/cjs/internal/activeFibers.d.ts +12 -0
- package/dist/cjs/internal/activeFibers.d.ts.map +1 -0
- package/dist/cjs/internal/activeFibers.js +30 -0
- package/dist/cjs/internal/activeFibers.js.map +7 -0
- package/dist/cjs/internal/issues.d.ts +28 -0
- package/dist/cjs/internal/issues.d.ts.map +1 -0
- package/dist/cjs/internal/issues.js +34 -0
- package/dist/cjs/internal/issues.js.map +7 -0
- package/dist/cjs/internal/planning.d.ts +347 -0
- package/dist/cjs/internal/planning.d.ts.map +1 -0
- package/dist/cjs/internal/planning.js +137 -0
- package/dist/cjs/internal/planning.js.map +7 -0
- package/dist/cjs/internal/sqlSchemaErrors.d.ts +18 -0
- package/dist/cjs/internal/sqlSchemaErrors.d.ts.map +1 -0
- package/dist/cjs/internal/sqlSchemaErrors.js +40 -0
- package/dist/cjs/internal/sqlSchemaErrors.js.map +7 -0
- package/dist/cjs/migrations/0001_control_tables.d.ts +18 -0
- package/dist/cjs/migrations/0001_control_tables.d.ts.map +1 -0
- package/dist/cjs/migrations/0001_control_tables.js +115 -0
- package/dist/cjs/migrations/0001_control_tables.js.map +7 -0
- package/dist/cjs/migrations/0002_run_keys.d.ts +15 -0
- package/dist/cjs/migrations/0002_run_keys.d.ts.map +1 -0
- package/dist/cjs/migrations/0002_run_keys.js +44 -0
- package/dist/cjs/migrations/0002_run_keys.js.map +7 -0
- package/dist/cjs/migrations/0003_signal_commands.d.ts +13 -0
- package/dist/cjs/migrations/0003_signal_commands.d.ts.map +1 -0
- package/dist/cjs/migrations/0003_signal_commands.js +50 -0
- package/dist/cjs/migrations/0003_signal_commands.js.map +7 -0
- package/dist/cjs/migrations/0004_approval_decisions.d.ts +13 -0
- package/dist/cjs/migrations/0004_approval_decisions.d.ts.map +1 -0
- package/dist/cjs/migrations/0004_approval_decisions.js +45 -0
- package/dist/cjs/migrations/0004_approval_decisions.js.map +7 -0
- package/dist/cjs/migrations/0005_signal_principals.d.ts +18 -0
- package/dist/cjs/migrations/0005_signal_principals.d.ts.map +1 -0
- package/dist/cjs/migrations/0005_signal_principals.js +45 -0
- package/dist/cjs/migrations/0005_signal_principals.js.map +7 -0
- package/dist/cjs/migrations/0006_run_principals.d.ts +18 -0
- package/dist/cjs/migrations/0006_run_principals.d.ts.map +1 -0
- package/dist/cjs/migrations/0006_run_principals.js +49 -0
- package/dist/cjs/migrations/0006_run_principals.js.map +7 -0
- package/dist/cjs/migrations/0007_resume_consent.d.ts +18 -0
- package/dist/cjs/migrations/0007_resume_consent.d.ts.map +1 -0
- package/dist/cjs/migrations/0007_resume_consent.js +46 -0
- package/dist/cjs/migrations/0007_resume_consent.js.map +7 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/test/TestControl.d.ts +19 -0
- package/dist/cjs/test/TestControl.d.ts.map +1 -0
- package/dist/cjs/test/TestControl.js +62 -0
- package/dist/cjs/test/TestControl.js.map +7 -0
- package/dist/esm/ApprovalAuthority.d.ts +73 -0
- package/dist/esm/ApprovalAuthority.d.ts.map +1 -0
- package/dist/esm/ApprovalAuthority.js +72 -0
- package/dist/esm/ApprovalAuthority.js.map +1 -0
- package/dist/esm/Cancellation.d.ts +107 -0
- package/dist/esm/Cancellation.d.ts.map +1 -0
- package/dist/esm/Cancellation.js +116 -0
- package/dist/esm/Cancellation.js.map +1 -0
- package/dist/esm/Channels.d.ts +170 -0
- package/dist/esm/Channels.d.ts.map +1 -0
- package/dist/esm/Channels.js +312 -0
- package/dist/esm/Channels.js.map +1 -0
- package/dist/esm/Control.d.ts +202 -0
- package/dist/esm/Control.d.ts.map +1 -0
- package/dist/esm/Control.js +42 -0
- package/dist/esm/Control.js.map +1 -0
- package/dist/esm/ControlClient.d.ts +52 -0
- package/dist/esm/ControlClient.d.ts.map +1 -0
- package/dist/esm/ControlClient.js +217 -0
- package/dist/esm/ControlClient.js.map +1 -0
- package/dist/esm/ControlError.d.ts +318 -0
- package/dist/esm/ControlError.d.ts.map +1 -0
- package/dist/esm/ControlError.js +359 -0
- package/dist/esm/ControlError.js.map +1 -0
- package/dist/esm/ControlExecutor.d.ts +372 -0
- package/dist/esm/ControlExecutor.d.ts.map +1 -0
- package/dist/esm/ControlExecutor.js +212 -0
- package/dist/esm/ControlExecutor.js.map +1 -0
- package/dist/esm/ControlFacts.d.ts +454 -0
- package/dist/esm/ControlFacts.d.ts.map +1 -0
- package/dist/esm/ControlFacts.js +324 -0
- package/dist/esm/ControlFacts.js.map +1 -0
- package/dist/esm/ControlLive.d.ts +23 -0
- package/dist/esm/ControlLive.d.ts.map +1 -0
- package/dist/esm/ControlLive.js +1589 -0
- package/dist/esm/ControlLive.js.map +1 -0
- package/dist/esm/ControlRpcs.d.ts +1204 -0
- package/dist/esm/ControlRpcs.d.ts.map +1 -0
- package/dist/esm/ControlRpcs.js +299 -0
- package/dist/esm/ControlRpcs.js.map +1 -0
- package/dist/esm/ControlRuntime.d.ts +635 -0
- package/dist/esm/ControlRuntime.d.ts.map +1 -0
- package/dist/esm/ControlRuntime.js +808 -0
- package/dist/esm/ControlRuntime.js.map +1 -0
- package/dist/esm/ControlSchema.d.ts +2642 -0
- package/dist/esm/ControlSchema.d.ts.map +1 -0
- package/dist/esm/ControlSchema.js +1030 -0
- package/dist/esm/ControlSchema.js.map +1 -0
- package/dist/esm/ControlServer.d.ts +51 -0
- package/dist/esm/ControlServer.d.ts.map +1 -0
- package/dist/esm/ControlServer.js +145 -0
- package/dist/esm/ControlServer.js.map +1 -0
- package/dist/esm/Credential.d.ts +136 -0
- package/dist/esm/Credential.d.ts.map +1 -0
- package/dist/esm/Credential.js +190 -0
- package/dist/esm/Credential.js.map +1 -0
- package/dist/esm/CredentialCipher.d.ts +90 -0
- package/dist/esm/CredentialCipher.d.ts.map +1 -0
- package/dist/esm/CredentialCipher.js +56 -0
- package/dist/esm/CredentialCipher.js.map +1 -0
- package/dist/esm/CredentialStore.d.ts +97 -0
- package/dist/esm/CredentialStore.d.ts.map +1 -0
- package/dist/esm/CredentialStore.js +101 -0
- package/dist/esm/CredentialStore.js.map +1 -0
- package/dist/esm/DispatchReader.d.ts +112 -0
- package/dist/esm/DispatchReader.d.ts.map +1 -0
- package/dist/esm/DispatchReader.js +76 -0
- package/dist/esm/DispatchReader.js.map +1 -0
- package/dist/esm/Health.d.ts +333 -0
- package/dist/esm/Health.d.ts.map +1 -0
- package/dist/esm/Health.js +400 -0
- package/dist/esm/Health.js.map +1 -0
- package/dist/esm/JevSessionChecker.d.ts +57 -0
- package/dist/esm/JevSessionChecker.d.ts.map +1 -0
- package/dist/esm/JevSessionChecker.js +108 -0
- package/dist/esm/JevSessionChecker.js.map +1 -0
- package/dist/esm/Lineage.d.ts +131 -0
- package/dist/esm/Lineage.d.ts.map +1 -0
- package/dist/esm/Lineage.js +174 -0
- package/dist/esm/Lineage.js.map +1 -0
- package/dist/esm/Migrations.d.ts +34 -0
- package/dist/esm/Migrations.d.ts.map +1 -0
- package/dist/esm/Migrations.js +53 -0
- package/dist/esm/Migrations.js.map +1 -0
- package/dist/esm/Monitor.d.ts +282 -0
- package/dist/esm/Monitor.d.ts.map +1 -0
- package/dist/esm/Monitor.js +415 -0
- package/dist/esm/Monitor.js.map +1 -0
- package/dist/esm/ScopedToken.d.ts +193 -0
- package/dist/esm/ScopedToken.d.ts.map +1 -0
- package/dist/esm/ScopedToken.js +224 -0
- package/dist/esm/ScopedToken.js.map +1 -0
- package/dist/esm/SqlControlRuntime.d.ts +161 -0
- package/dist/esm/SqlControlRuntime.d.ts.map +1 -0
- package/dist/esm/SqlControlRuntime.js +1756 -0
- package/dist/esm/SqlControlRuntime.js.map +1 -0
- package/dist/esm/SqlCredentialStore.d.ts +43 -0
- package/dist/esm/SqlCredentialStore.d.ts.map +1 -0
- package/dist/esm/SqlCredentialStore.js +97 -0
- package/dist/esm/SqlCredentialStore.js.map +1 -0
- package/dist/esm/Steering.d.ts +69 -0
- package/dist/esm/Steering.d.ts.map +1 -0
- package/dist/esm/Steering.js +89 -0
- package/dist/esm/Steering.js.map +1 -0
- package/dist/esm/SystemFlows.d.ts +223 -0
- package/dist/esm/SystemFlows.d.ts.map +1 -0
- package/dist/esm/SystemFlows.js +198 -0
- package/dist/esm/SystemFlows.js.map +1 -0
- package/dist/esm/WebCryptoCipher.d.ts +49 -0
- package/dist/esm/WebCryptoCipher.d.ts.map +1 -0
- package/dist/esm/WebCryptoCipher.js +123 -0
- package/dist/esm/WebCryptoCipher.js.map +1 -0
- package/dist/esm/WebhookChannel.d.ts +113 -0
- package/dist/esm/WebhookChannel.d.ts.map +1 -0
- package/dist/esm/WebhookChannel.js +109 -0
- package/dist/esm/WebhookChannel.js.map +1 -0
- package/dist/esm/index.d.ts +160 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +160 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/internal/MutationBoundary.d.ts +27 -0
- package/dist/esm/internal/MutationBoundary.d.ts.map +1 -0
- package/dist/esm/internal/MutationBoundary.js +40 -0
- package/dist/esm/internal/MutationBoundary.js.map +1 -0
- package/dist/esm/internal/activeFibers.d.ts +12 -0
- package/dist/esm/internal/activeFibers.d.ts.map +1 -0
- package/dist/esm/internal/activeFibers.js +17 -0
- package/dist/esm/internal/activeFibers.js.map +1 -0
- package/dist/esm/internal/issues.d.ts +28 -0
- package/dist/esm/internal/issues.d.ts.map +1 -0
- package/dist/esm/internal/issues.js +35 -0
- package/dist/esm/internal/issues.js.map +1 -0
- package/dist/esm/internal/planning.d.ts +347 -0
- package/dist/esm/internal/planning.d.ts.map +1 -0
- package/dist/esm/internal/planning.js +199 -0
- package/dist/esm/internal/planning.js.map +1 -0
- package/dist/esm/internal/sqlSchemaErrors.d.ts +18 -0
- package/dist/esm/internal/sqlSchemaErrors.d.ts.map +1 -0
- package/dist/esm/internal/sqlSchemaErrors.js +38 -0
- package/dist/esm/internal/sqlSchemaErrors.js.map +1 -0
- package/dist/esm/migrations/0001_control_tables.d.ts +18 -0
- package/dist/esm/migrations/0001_control_tables.d.ts.map +1 -0
- package/dist/esm/migrations/0001_control_tables.js +96 -0
- package/dist/esm/migrations/0001_control_tables.js.map +1 -0
- package/dist/esm/migrations/0002_run_keys.d.ts +15 -0
- package/dist/esm/migrations/0002_run_keys.d.ts.map +1 -0
- package/dist/esm/migrations/0002_run_keys.js +22 -0
- package/dist/esm/migrations/0002_run_keys.js.map +1 -0
- package/dist/esm/migrations/0003_signal_commands.d.ts +13 -0
- package/dist/esm/migrations/0003_signal_commands.d.ts.map +1 -0
- package/dist/esm/migrations/0003_signal_commands.js +26 -0
- package/dist/esm/migrations/0003_signal_commands.js.map +1 -0
- package/dist/esm/migrations/0004_approval_decisions.d.ts +13 -0
- package/dist/esm/migrations/0004_approval_decisions.d.ts.map +1 -0
- package/dist/esm/migrations/0004_approval_decisions.js +25 -0
- package/dist/esm/migrations/0004_approval_decisions.js.map +1 -0
- package/dist/esm/migrations/0005_signal_principals.d.ts +18 -0
- package/dist/esm/migrations/0005_signal_principals.d.ts.map +1 -0
- package/dist/esm/migrations/0005_signal_principals.js +27 -0
- package/dist/esm/migrations/0005_signal_principals.js.map +1 -0
- package/dist/esm/migrations/0006_run_principals.d.ts +18 -0
- package/dist/esm/migrations/0006_run_principals.d.ts.map +1 -0
- package/dist/esm/migrations/0006_run_principals.js +30 -0
- package/dist/esm/migrations/0006_run_principals.js.map +1 -0
- package/dist/esm/migrations/0007_resume_consent.d.ts +18 -0
- package/dist/esm/migrations/0007_resume_consent.d.ts.map +1 -0
- package/dist/esm/migrations/0007_resume_consent.js +27 -0
- package/dist/esm/migrations/0007_resume_consent.js.map +1 -0
- package/dist/esm/test/TestControl.d.ts +19 -0
- package/dist/esm/test/TestControl.d.ts.map +1 -0
- package/dist/esm/test/TestControl.js +30 -0
- package/dist/esm/test/TestControl.js.map +1 -0
- package/docs/README.md +189 -0
- package/docs/api.md +982 -0
- package/docs/concepts/authority.md +109 -0
- package/docs/concepts/cancellation.md +129 -0
- package/docs/concepts/lineage.md +132 -0
- package/docs/concepts/ownership.md +139 -0
- package/docs/concepts/projections.md +203 -0
- package/docs/concepts/receipts.md +128 -0
- package/docs/guides/approvals.md +284 -0
- package/docs/guides/cancel-and-resume.md +162 -0
- package/docs/guides/durable-storage.md +147 -0
- package/docs/guides/implement-an-executor.md +173 -0
- package/docs/guides/ingest-a-webhook.md +177 -0
- package/docs/guides/list-runs.md +160 -0
- package/docs/guides/monitor-runs.md +176 -0
- package/docs/guides/observe-health.md +147 -0
- package/docs/guides/postgres-tests.md +7 -0
- package/docs/guides/serve-over-rpc.md +220 -0
- package/docs/guides/signal-a-run.md +53 -0
- package/docs/guides/steer-a-run.md +138 -0
- package/docs/guides/store-credentials.md +164 -0
- package/docs/guides/testing.md +139 -0
- package/docs/guides/watch-a-run.md +154 -0
- package/docs/installation.md +106 -0
- package/docs/quickstart.md +163 -0
- package/docs/troubleshooting.md +208 -0
- package/package.json +405 -3
- package/src/ApprovalAuthority.ts +114 -0
- package/src/Cancellation.ts +172 -0
- package/src/Channels.ts +493 -0
- package/src/Control.ts +337 -0
- package/src/ControlClient.ts +319 -0
- package/src/ControlError.ts +378 -0
- package/src/ControlExecutor.ts +490 -0
- package/src/ControlFacts.ts +383 -0
- package/src/ControlLive.ts +2127 -0
- package/src/ControlRpcs.ts +443 -0
- package/src/ControlRuntime.ts +1601 -0
- package/src/ControlSchema.ts +1380 -0
- package/src/ControlServer.ts +182 -0
- package/src/Credential.ts +310 -0
- package/src/CredentialCipher.ts +110 -0
- package/src/CredentialStore.ts +152 -0
- package/src/DispatchReader.ts +122 -0
- package/src/Health.ts +591 -0
- package/src/JevSessionChecker.ts +127 -0
- package/src/Lineage.ts +203 -0
- package/src/Migrations.ts +56 -0
- package/src/Monitor.ts +600 -0
- package/src/ScopedToken.ts +306 -0
- package/src/SqlControlRuntime.ts +2478 -0
- package/src/SqlCredentialStore.ts +148 -0
- package/src/Steering.ts +96 -0
- package/src/SystemFlows.ts +225 -0
- package/src/WebCryptoCipher.ts +169 -0
- package/src/WebhookChannel.ts +166 -0
- package/src/index.ts +188 -0
- package/src/internal/MutationBoundary.ts +46 -0
- package/src/internal/activeFibers.ts +22 -0
- package/src/internal/issues.ts +40 -0
- package/src/internal/planning.ts +262 -0
- package/src/internal/sqlSchemaErrors.ts +37 -0
- package/src/migrations/0001_control_tables.ts +99 -0
- package/src/migrations/0002_run_keys.ts +23 -0
- package/src/migrations/0003_signal_commands.ts +27 -0
- package/src/migrations/0004_approval_decisions.ts +25 -0
- package/src/migrations/0005_signal_principals.ts +27 -0
- package/src/migrations/0006_run_principals.ts +31 -0
- package/src/migrations/0007_resume_consent.ts +28 -0
- package/src/test/TestControl.ts +47 -0
|
@@ -0,0 +1,1293 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var ControlLive_exports = {};
|
|
30
|
+
__export(ControlLive_exports, {
|
|
31
|
+
layer: () => layer
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(ControlLive_exports);
|
|
34
|
+
var Sha256 = __toESM(require("@smthrs/crypto/Sha256"), 1);
|
|
35
|
+
var Fault = __toESM(require("@smthrs/flow/Fault"), 1);
|
|
36
|
+
var import_journal = require("@smthrs/journal");
|
|
37
|
+
var import_notifications = require("@smthrs/notifications");
|
|
38
|
+
var SteerPayload = __toESM(require("@smthrs/notifications/SteerPayload"), 1);
|
|
39
|
+
var import_registry = require("@smthrs/registry");
|
|
40
|
+
var import_Descriptor = require("@smthrs/registry/Descriptor");
|
|
41
|
+
var import_effect = require("effect");
|
|
42
|
+
var Cancellation = __toESM(require("./Cancellation.js"), 1);
|
|
43
|
+
var import_Control = require("./Control.js");
|
|
44
|
+
var import_ControlError = require("./ControlError.js");
|
|
45
|
+
var import_ControlExecutor = require("./ControlExecutor.js");
|
|
46
|
+
var ControlFacts = __toESM(require("./ControlFacts.js"), 1);
|
|
47
|
+
var import_ControlRuntime = require("./ControlRuntime.js");
|
|
48
|
+
var import_ControlSchema = require("./ControlSchema.js");
|
|
49
|
+
var DispatchReader = __toESM(require("./DispatchReader.js"), 1);
|
|
50
|
+
var import_issues = require("./internal/issues.js");
|
|
51
|
+
var MutationBoundary = __toESM(require("./internal/MutationBoundary.js"), 1);
|
|
52
|
+
var import_planning = require("./internal/planning.js");
|
|
53
|
+
var Lineage = __toESM(require("./Lineage.js"), 1);
|
|
54
|
+
var Steering = __toESM(require("./Steering.js"), 1);
|
|
55
|
+
const sourceId = import_journal.JournalEvent.SourceId.make("/control");
|
|
56
|
+
const snapshotPageSize = 1024;
|
|
57
|
+
const snapshotPartitionConcurrency = 8;
|
|
58
|
+
const partitionPageSize = 100;
|
|
59
|
+
const unavailable = (feature) => new import_ControlError.Unavailable({ feature, ticket: "control-runtime-engine-integration" });
|
|
60
|
+
const watchReadFailed = (cause) => cause.code === "journal_closed" ? unavailable("watch") : new import_ControlError.PersistenceError({
|
|
61
|
+
operation: "watch",
|
|
62
|
+
message: `Reading the control journal failed (${cause.code})`,
|
|
63
|
+
cause
|
|
64
|
+
});
|
|
65
|
+
const accepted = (key, runId) => runId === void 0 ? { _tag: "Accepted", receiptId: key } : { _tag: "Accepted", receiptId: key, runId };
|
|
66
|
+
const terminal = (status) => status === "cancelled" || status === "completed" || status === "failed";
|
|
67
|
+
const live = (status) => status === "running" || status === "accepted";
|
|
68
|
+
const terminalOrAccepted = (key, run) => terminal(run.status) ? { _tag: "Terminal", runId: run.runId, status: run.status } : accepted(key, run.runId);
|
|
69
|
+
const withoutStampedPrincipal = (input) => {
|
|
70
|
+
if (input === null || typeof input !== "object") return input;
|
|
71
|
+
const { principal: _principal, ...rest } = input;
|
|
72
|
+
const message = rest["message"];
|
|
73
|
+
if (message === null || typeof message !== "object") return rest;
|
|
74
|
+
const { principal: _messagePrincipal, ...messageRest } = message;
|
|
75
|
+
return { ...rest, message: messageRest };
|
|
76
|
+
};
|
|
77
|
+
const fingerprint = (operation, principal, input) => `control-mutation:v2:${Sha256.digestSync((0, import_planning.canonical)({
|
|
78
|
+
operation,
|
|
79
|
+
actor: { id: principal.id, kind: principal.kind },
|
|
80
|
+
intent: withoutStampedPrincipal(input)
|
|
81
|
+
}))}`;
|
|
82
|
+
const json = (value) => JSON.parse(JSON.stringify(value));
|
|
83
|
+
const declaredTrigger = (decodedInput) => {
|
|
84
|
+
if (decodedInput === null || typeof decodedInput !== "object" || Array.isArray(decodedInput)) return void 0;
|
|
85
|
+
const held = decodedInput["trigger"];
|
|
86
|
+
return held !== null && typeof held === "object" && !Array.isArray(held) ? held : void 0;
|
|
87
|
+
};
|
|
88
|
+
const invalid = (issue) => new import_ControlError.InvalidInput({ issue });
|
|
89
|
+
const AttributedApprovalInput = import_effect.Schema.Struct({
|
|
90
|
+
...import_ControlSchema.ApprovalInputSchema.fields,
|
|
91
|
+
principal: import_effect.Schema.optional(import_ControlSchema.Principal)
|
|
92
|
+
});
|
|
93
|
+
const AttributedReasonedMutationInput = import_effect.Schema.Struct({
|
|
94
|
+
...import_ControlSchema.ReasonedMutationInputSchema.fields,
|
|
95
|
+
principal: import_effect.Schema.optional(import_ControlSchema.Principal)
|
|
96
|
+
});
|
|
97
|
+
const AttributedResumeInput = import_effect.Schema.Struct({
|
|
98
|
+
...import_ControlSchema.ResumeInputSchema.fields,
|
|
99
|
+
principal: import_effect.Schema.optional(import_ControlSchema.Principal)
|
|
100
|
+
});
|
|
101
|
+
const AttributedRunInput = import_effect.Schema.Union([
|
|
102
|
+
import_effect.Schema.Struct({
|
|
103
|
+
...import_ControlSchema.RunInputSchema.members[0].fields,
|
|
104
|
+
principal: import_effect.Schema.optional(import_ControlSchema.Principal),
|
|
105
|
+
reservedRunId: import_effect.Schema.optional(import_effect.Schema.NonEmptyString)
|
|
106
|
+
}),
|
|
107
|
+
import_effect.Schema.Struct({ ...import_ControlSchema.RunInputSchema.members[1].fields, principal: import_effect.Schema.optional(import_ControlSchema.Principal) })
|
|
108
|
+
]);
|
|
109
|
+
const AttributedSignalInput = import_effect.Schema.Struct({
|
|
110
|
+
...import_ControlSchema.SignalInputSchema.fields,
|
|
111
|
+
principal: import_effect.Schema.optional(import_ControlSchema.Principal)
|
|
112
|
+
});
|
|
113
|
+
const validIdempotencyKey = (value) => {
|
|
114
|
+
if (value.length === 0 || value.length > 1024 || value.includes("\0")) return false;
|
|
115
|
+
for (let index = 0; index < value.length; index++) {
|
|
116
|
+
const unit = value.charCodeAt(index);
|
|
117
|
+
if (unit >= 55296 && unit <= 56319) {
|
|
118
|
+
const low = value.charCodeAt(++index);
|
|
119
|
+
if (!(low >= 56320 && low <= 57343)) return false;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (unit >= 56320 && unit <= 57343) return false;
|
|
123
|
+
}
|
|
124
|
+
return true;
|
|
125
|
+
};
|
|
126
|
+
const snapshotMutation = (operation, decode, input) => import_effect.Effect.suspend(() => {
|
|
127
|
+
const admitted = MutationBoundary.admit(input);
|
|
128
|
+
if (!admitted.ok) return import_effect.Effect.fail(invalid(`${operation}: ${admitted.complaint}`));
|
|
129
|
+
return decode(admitted.value).pipe(
|
|
130
|
+
import_effect.Effect.mapError((error) => invalid(`${operation}: invalid mutation at ${(0, import_issues.schemaIssuePath)(error)}`)),
|
|
131
|
+
import_effect.Effect.flatMap(
|
|
132
|
+
(snapshot) => validIdempotencyKey(snapshot.idempotencyKey) ? import_effect.Effect.succeed(snapshot) : import_effect.Effect.fail(invalid(`${operation}.idempotencyKey: must be 1 to 1024 well-formed characters`))
|
|
133
|
+
)
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
const snapshotApproval = (operation, input) => snapshotMutation(operation, import_effect.Schema.decodeUnknownEffect(AttributedApprovalInput), input);
|
|
137
|
+
const snapshotReasonedMutation = (operation, input) => snapshotMutation(operation, import_effect.Schema.decodeUnknownEffect(AttributedReasonedMutationInput), input);
|
|
138
|
+
const snapshotResume = (input) => snapshotMutation("resume", import_effect.Schema.decodeUnknownEffect(AttributedResumeInput), input);
|
|
139
|
+
const snapshotRun = (input) => snapshotMutation("run", import_effect.Schema.decodeUnknownEffect(AttributedRunInput), input);
|
|
140
|
+
const snapshotSignal = (input) => snapshotMutation("signal", import_effect.Schema.decodeUnknownEffect(AttributedSignalInput), input);
|
|
141
|
+
const snapshotSteer = (input) => snapshotMutation("steer", import_effect.Schema.decodeUnknownEffect(import_ControlSchema.SteerInputSchema), input);
|
|
142
|
+
const pageBounds = (cursor, limit) => {
|
|
143
|
+
if (limit !== void 0 && (!Number.isSafeInteger(limit) || limit < 1 || limit > import_ControlSchema.maxPageSize)) {
|
|
144
|
+
return import_effect.Effect.fail(
|
|
145
|
+
invalid(`limit: must be an integer between 1 and ${import_ControlSchema.maxPageSize}, received ${String(limit)}`)
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
if (cursor === void 0) return import_effect.Effect.succeed({ start: 0, size: limit ?? import_ControlSchema.defaultPageSize });
|
|
149
|
+
const start = Number(cursor);
|
|
150
|
+
return Number.isSafeInteger(start) && start >= 0 ? import_effect.Effect.succeed({ start, size: limit ?? import_ControlSchema.defaultPageSize }) : import_effect.Effect.fail(invalid(`cursor: must be a cursor this listing returned, received ${JSON.stringify(cursor)}`));
|
|
151
|
+
};
|
|
152
|
+
const cursorNatural = import_effect.Schema.Number.check(
|
|
153
|
+
import_effect.Schema.isInt(),
|
|
154
|
+
import_effect.Schema.isGreaterThanOrEqualTo(0),
|
|
155
|
+
import_effect.Schema.isLessThanOrEqualTo(Number.MAX_SAFE_INTEGER)
|
|
156
|
+
);
|
|
157
|
+
const runCursor = import_effect.Schema.fromJsonString(import_effect.Schema.Struct({
|
|
158
|
+
version: import_effect.Schema.Literal(1),
|
|
159
|
+
filters: import_effect.Schema.String,
|
|
160
|
+
source: import_effect.Schema.Union([import_effect.Schema.Literal(0), import_effect.Schema.Literal(1)]),
|
|
161
|
+
sequence: cursorNatural,
|
|
162
|
+
createdAt: cursorNatural,
|
|
163
|
+
runId: import_effect.Schema.NonEmptyString
|
|
164
|
+
}));
|
|
165
|
+
const page = (values, bounds) => {
|
|
166
|
+
const items = values.slice(bounds.start, bounds.start + bounds.size);
|
|
167
|
+
const next = bounds.start + items.length;
|
|
168
|
+
return next < values.length ? { items, nextCursor: String(next) } : { items };
|
|
169
|
+
};
|
|
170
|
+
const eventFromEntry = (entry) => ({
|
|
171
|
+
sequence: entry.seq,
|
|
172
|
+
kind: entry.eventType,
|
|
173
|
+
runId: entry.runId,
|
|
174
|
+
occurredAt: entry.emittedAtMs,
|
|
175
|
+
payload: entry.payload
|
|
176
|
+
});
|
|
177
|
+
const layer = import_effect.Layer.effect(
|
|
178
|
+
import_Control.Control,
|
|
179
|
+
import_effect.Effect.gen(function* () {
|
|
180
|
+
const runtime = yield* import_ControlRuntime.ControlRuntime;
|
|
181
|
+
const journal = yield* import_journal.Journal.Journal;
|
|
182
|
+
const notifications = yield* import_notifications.NotificationQueue.NotificationQueue;
|
|
183
|
+
const registry = yield* import_registry.Registry.Registry;
|
|
184
|
+
const executor = yield* import_effect.Effect.serviceOption(import_ControlExecutor.ControlExecutor);
|
|
185
|
+
const dispatch = import_effect.Option.getOrElse(
|
|
186
|
+
yield* import_effect.Effect.serviceOption(DispatchReader.DispatchReader),
|
|
187
|
+
DispatchReader.makeNone
|
|
188
|
+
);
|
|
189
|
+
const observing = import_effect.Option.isSome(executor) && executor.value.readExecution !== void 0;
|
|
190
|
+
const observe = (run) => import_effect.Effect.gen(function* () {
|
|
191
|
+
if (import_effect.Option.isNone(executor) || executor.value.readExecution === void 0) return run;
|
|
192
|
+
const observed = yield* executor.value.readExecution(run.runId);
|
|
193
|
+
if (observed._tag === "Missing") return { ...run, executionObservation: "missing" };
|
|
194
|
+
return {
|
|
195
|
+
...run,
|
|
196
|
+
executionObservation: "observed",
|
|
197
|
+
executionView: observed.executionView,
|
|
198
|
+
status: observed.status,
|
|
199
|
+
waitingReason: observed.waitingReason,
|
|
200
|
+
parentRunId: observed.parentRunId,
|
|
201
|
+
lineageId: observed.lineageId,
|
|
202
|
+
roundOrdinal: observed.roundOrdinal,
|
|
203
|
+
// The executor's answer replaces this plane's copy here as it does
|
|
204
|
+
// for every other observed field. It has to: a control plane over
|
|
205
|
+
// its own coordination database cannot see the executions a flow
|
|
206
|
+
// spawned, so a nested `HumanTask` park is a fact only the executor
|
|
207
|
+
// holds. Where the two share one database the runtime computes the
|
|
208
|
+
// same rows itself and the observation reproduces them.
|
|
209
|
+
pendingWaits: observed.pendingWaits
|
|
210
|
+
};
|
|
211
|
+
});
|
|
212
|
+
const getRun = (runId) => runtime.getRun(runId).pipe(import_effect.Effect.flatMap(observe));
|
|
213
|
+
const refuseCodeDrift = (runId) => runtime.codeDrift(runId).pipe(import_effect.Effect.flatMap((drift) => drift === void 0 ? import_effect.Effect.void : import_effect.Effect.fail(drift)));
|
|
214
|
+
const withCodeDrift = (run) => terminal(run.status) ? import_effect.Effect.succeed(run) : runtime.codeDrift(run.runId).pipe(
|
|
215
|
+
import_effect.Effect.map(
|
|
216
|
+
(drift) => drift === void 0 ? run : {
|
|
217
|
+
...run,
|
|
218
|
+
codeDrift: {
|
|
219
|
+
...drift.recorded === void 0 ? {} : { recorded: drift.recorded },
|
|
220
|
+
...drift.current === void 0 ? {} : { current: drift.current },
|
|
221
|
+
...drift.recordedEngine === void 0 ? {} : { recordedEngine: drift.recordedEngine },
|
|
222
|
+
...drift.currentEngine === void 0 ? {} : { currentEngine: drift.currentEngine }
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
),
|
|
226
|
+
import_effect.Effect.orElseSucceed(() => run)
|
|
227
|
+
);
|
|
228
|
+
const mutationSemaphore = yield* import_effect.Semaphore.make(1);
|
|
229
|
+
const record = (runId, eventType, payload) => (
|
|
230
|
+
// Unfenced: the control plane mutates runs it does not own — that is
|
|
231
|
+
// the point of a control plane — so its event records are
|
|
232
|
+
// first-writer-wins admissions, not owner-fenced lifecycle writes.
|
|
233
|
+
journal.emitDurableUnfenced(
|
|
234
|
+
new import_journal.JournalEvent.Input({
|
|
235
|
+
runId: import_journal.JournalEvent.RunId.make(runId),
|
|
236
|
+
sourceId,
|
|
237
|
+
eventType,
|
|
238
|
+
payload: json(payload)
|
|
239
|
+
})
|
|
240
|
+
).pipe(
|
|
241
|
+
import_effect.Effect.map((receipt) => receipt.seq),
|
|
242
|
+
import_effect.Effect.mapError(
|
|
243
|
+
(cause) => new import_ControlError.PersistenceError({
|
|
244
|
+
operation: eventType,
|
|
245
|
+
message: `Failed to persist ${eventType}`,
|
|
246
|
+
cause
|
|
247
|
+
})
|
|
248
|
+
)
|
|
249
|
+
)
|
|
250
|
+
);
|
|
251
|
+
const emit = (runId, eventType, payload) => import_effect.Effect.asVoid(record(runId, eventType, payload));
|
|
252
|
+
const settleUnlaunched = (runId, cause, fault) => import_effect.Effect.gen(function* () {
|
|
253
|
+
const fence = yield* runtime.claimFence(runId);
|
|
254
|
+
const run = yield* runtime.writeStatus(runId, fence, "failed");
|
|
255
|
+
yield* emit(
|
|
256
|
+
runId,
|
|
257
|
+
"control.run.failed",
|
|
258
|
+
json({ runId, status: "failed", cause: cause.slice(0, 4096), fault, ...ControlFacts.runFact(run) })
|
|
259
|
+
);
|
|
260
|
+
}).pipe(
|
|
261
|
+
journal.transact,
|
|
262
|
+
import_effect.Effect.catchCause(
|
|
263
|
+
(failure) => import_effect.Effect.annotateLogs(
|
|
264
|
+
import_effect.Effect.logWarning("A refused launch could not be settled"),
|
|
265
|
+
{ runId, cause: import_effect.Cause.pretty(failure) }
|
|
266
|
+
)
|
|
267
|
+
)
|
|
268
|
+
);
|
|
269
|
+
const transact = (operation, effect) => mutationSemaphore.withPermits(1)(
|
|
270
|
+
journal.transact(effect).pipe(
|
|
271
|
+
import_effect.Effect.mapError(
|
|
272
|
+
(cause) => cause instanceof import_journal.Journal.JournalError ? new import_ControlError.PersistenceError({
|
|
273
|
+
operation: `${operation}.idempotency`,
|
|
274
|
+
message: `Failed to commit ${operation} and its idempotency receipt atomically`,
|
|
275
|
+
cause
|
|
276
|
+
}) : cause
|
|
277
|
+
)
|
|
278
|
+
)
|
|
279
|
+
);
|
|
280
|
+
const mutate = (operation, key, principal, mutationFingerprint, effect, replay = true, claimRunKey = false) => transact(
|
|
281
|
+
operation,
|
|
282
|
+
import_effect.Effect.gen(function* () {
|
|
283
|
+
const durableKey = (0, import_planning.mutationKey)(operation, key, principal);
|
|
284
|
+
const prior = yield* runtime.lookupMutation(durableKey, mutationFingerprint);
|
|
285
|
+
if (prior !== void 0 && (replay || prior._tag === "Conflict")) {
|
|
286
|
+
return prior._tag === "AlreadyApplied" ? { ...prior, receiptId: key } : prior;
|
|
287
|
+
}
|
|
288
|
+
const claim = claimRunKey ? yield* runtime.claimRunKey(durableKey, mutationFingerprint) : void 0;
|
|
289
|
+
if (claim?._tag === "Raced") return (0, import_planning.alreadyApplied)(key, claim.receipt);
|
|
290
|
+
return yield* import_effect.Effect.gen(function* () {
|
|
291
|
+
const receipt = yield* effect;
|
|
292
|
+
if (receipt._tag === "Parked" && claimRunKey) {
|
|
293
|
+
yield* runtime.releaseRunKey(durableKey);
|
|
294
|
+
} else if (receipt._tag !== "Parked" && prior === void 0) {
|
|
295
|
+
yield* runtime.recordMutation(durableKey, mutationFingerprint, receipt);
|
|
296
|
+
}
|
|
297
|
+
return receipt;
|
|
298
|
+
}).pipe(import_effect.Effect.onExit(
|
|
299
|
+
(exit) => claim?._tag === "Claimed" && import_effect.Exit.isFailure(exit) ? runtime.releaseRunKey(durableKey) : import_effect.Effect.void
|
|
300
|
+
));
|
|
301
|
+
})
|
|
302
|
+
);
|
|
303
|
+
const takeUpResume = (runId, sequence) => import_effect.Option.isNone(executor) ? import_effect.Effect.void : executor.value.resumeRun({ runId }).pipe(
|
|
304
|
+
import_effect.Effect.flatMap((uptake) => uptake === "resuming" ? runtime.clearResume(runId, sequence) : import_effect.Effect.void)
|
|
305
|
+
);
|
|
306
|
+
const hand = (launch) => import_effect.Effect.gen(function* () {
|
|
307
|
+
const acceptance = import_effect.Option.isSome(executor) ? yield* executor.value.launch(launch).pipe(
|
|
308
|
+
import_effect.Effect.tapError((error) => settleUnlaunched(error.runId, error.message, Fault.of(error)))
|
|
309
|
+
) : "pending";
|
|
310
|
+
yield* transact(
|
|
311
|
+
"run.acceptance",
|
|
312
|
+
import_effect.Effect.gen(function* () {
|
|
313
|
+
const current = yield* runtime.getRun(launch.run.runId);
|
|
314
|
+
if (current.status !== "accepted") return;
|
|
315
|
+
const fence = yield* runtime.claimFence(current.runId);
|
|
316
|
+
const run = acceptance === "accepted" ? yield* runtime.writeStatus(current.runId, fence, "running") : yield* runtime.releasePending(current.runId, fence);
|
|
317
|
+
yield* emit(
|
|
318
|
+
run.runId,
|
|
319
|
+
acceptance === "accepted" ? "control.run.running" : "control.run.pending",
|
|
320
|
+
{
|
|
321
|
+
runId: run.runId,
|
|
322
|
+
status: run.status,
|
|
323
|
+
...ControlFacts.runFact(run)
|
|
324
|
+
}
|
|
325
|
+
);
|
|
326
|
+
})
|
|
327
|
+
);
|
|
328
|
+
});
|
|
329
|
+
const relaunchStranded = (runId) => import_effect.Effect.gen(function* () {
|
|
330
|
+
const current = yield* runtime.getRun(runId);
|
|
331
|
+
if (current.status !== "accepted" || current.ownerId === void 0) return;
|
|
332
|
+
const ours = yield* runtime.claimFence(runId).pipe(
|
|
333
|
+
import_effect.Effect.as(true),
|
|
334
|
+
import_effect.Effect.catchTag("/control/ClaimLost", () => import_effect.Effect.succeed(false))
|
|
335
|
+
);
|
|
336
|
+
if (ours) return;
|
|
337
|
+
yield* refuseCodeDrift(runId);
|
|
338
|
+
const claimed = yield* runtime.resume(runId, { scope: "launched" }).pipe(
|
|
339
|
+
import_effect.Effect.catchTag("/control/ClaimLost", () => import_effect.Effect.succeed(void 0))
|
|
340
|
+
);
|
|
341
|
+
if (claimed === void 0 || claimed.status !== "accepted" || claimed.planId === void 0) return;
|
|
342
|
+
yield* hand({ plan: yield* runtime.getPlan(claimed.planId), run: claimed });
|
|
343
|
+
}).pipe(
|
|
344
|
+
// The recorded receipt is the retry's answer either way; a relaunch
|
|
345
|
+
// that fails leaves the run where it was for the next retry.
|
|
346
|
+
import_effect.Effect.catch(
|
|
347
|
+
(failure) => import_effect.Effect.annotateLogs(
|
|
348
|
+
import_effect.Effect.logWarning("A stranded run could not be relaunched"),
|
|
349
|
+
{ runId, cause: failure.message }
|
|
350
|
+
)
|
|
351
|
+
)
|
|
352
|
+
);
|
|
353
|
+
const decide = (decision, submitted) => import_effect.Effect.gen(function* () {
|
|
354
|
+
const input = yield* snapshotApproval(decision, submitted);
|
|
355
|
+
const principal = yield* runtime.stampPrincipal(input.principal);
|
|
356
|
+
return yield* decideAs(decision, input, principal).pipe(
|
|
357
|
+
// Every refusal leaves a durable audit record: who asked, for which
|
|
358
|
+
// target, under which scope. The journal row's time is when. It is
|
|
359
|
+
// written after the refused mutation rolled back, so it is the only
|
|
360
|
+
// row the refusal adds; a record that cannot be written fails the
|
|
361
|
+
// call rather than leaving an unaudited refusal.
|
|
362
|
+
import_effect.Effect.catchTag("/control/Unauthorized", (refusal) => emit(
|
|
363
|
+
input.target._tag === "Plan" ? `plan:${input.target.planId}` : input.target.runId,
|
|
364
|
+
"control.approval.refused",
|
|
365
|
+
json({ decision, principal, scope: input.scope, target: input.target })
|
|
366
|
+
).pipe(import_effect.Effect.andThen(import_effect.Effect.fail(refusal))))
|
|
367
|
+
);
|
|
368
|
+
});
|
|
369
|
+
const decideAs = (decision, input, principal) => import_effect.Effect.gen(function* () {
|
|
370
|
+
yield* runtime.authorizeApproval({ principal, target: input.target, decision, scope: input.scope });
|
|
371
|
+
if (input.target._tag === "Node") {
|
|
372
|
+
const current = yield* getRun(input.target.runId).pipe(
|
|
373
|
+
import_effect.Effect.catchTag("/control/RunNotFound", () => import_effect.Effect.succeed(void 0))
|
|
374
|
+
);
|
|
375
|
+
if (current !== void 0 && terminal(current.status)) {
|
|
376
|
+
const settled = { _tag: "Terminal", runId: current.runId, status: current.status };
|
|
377
|
+
return settled;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
let delegated;
|
|
381
|
+
const receipt = yield* mutate(
|
|
382
|
+
decision,
|
|
383
|
+
input.idempotencyKey,
|
|
384
|
+
principal,
|
|
385
|
+
fingerprint(decision, principal, input),
|
|
386
|
+
import_effect.Effect.gen(function* () {
|
|
387
|
+
if (input.target._tag === "Node") yield* refuseCodeDrift(input.target.runId);
|
|
388
|
+
const token = yield* runtime.lookupApproval(input.target);
|
|
389
|
+
yield* runtime.resolveApproval(token, decision, principal, input.scope);
|
|
390
|
+
if (decision === "approved") {
|
|
391
|
+
yield* runtime.installBulkGrant(token, input.target.envelope, input.scope);
|
|
392
|
+
}
|
|
393
|
+
yield* emit(
|
|
394
|
+
input.target._tag === "Plan" ? `plan:${input.target.planId}` : input.target.runId,
|
|
395
|
+
`control.approval.${decision}`,
|
|
396
|
+
json({
|
|
397
|
+
...ControlFacts.approvalDecisionFact(token.tokenId, input.target),
|
|
398
|
+
target: input.target._tag,
|
|
399
|
+
scope: input.scope,
|
|
400
|
+
envelope: input.target.envelope,
|
|
401
|
+
principal
|
|
402
|
+
})
|
|
403
|
+
);
|
|
404
|
+
if (input.target._tag === "Plan") return accepted(input.idempotencyKey);
|
|
405
|
+
const runId = input.target.runId;
|
|
406
|
+
delegated = yield* runtime.requestResume(runId);
|
|
407
|
+
yield* emit(runId, "control.run.resumed", { runId });
|
|
408
|
+
return accepted(input.idempotencyKey, runId);
|
|
409
|
+
})
|
|
410
|
+
);
|
|
411
|
+
if (input.target._tag === "Node" && delegated !== void 0) {
|
|
412
|
+
yield* takeUpResume(input.target.runId, delegated);
|
|
413
|
+
}
|
|
414
|
+
return receipt;
|
|
415
|
+
});
|
|
416
|
+
const runMutation = (submitted) => import_effect.Effect.gen(function* () {
|
|
417
|
+
const input = yield* snapshotResume(submitted);
|
|
418
|
+
const settled = yield* getRun(input.runId);
|
|
419
|
+
if (terminal(settled.status)) {
|
|
420
|
+
return { _tag: "Terminal", runId: settled.runId, status: settled.status };
|
|
421
|
+
}
|
|
422
|
+
const principal = yield* runtime.stampPrincipal(input.principal);
|
|
423
|
+
return yield* mutate(
|
|
424
|
+
"resume",
|
|
425
|
+
input.idempotencyKey,
|
|
426
|
+
principal,
|
|
427
|
+
fingerprint("resume", principal, input),
|
|
428
|
+
import_effect.Effect.gen(function* () {
|
|
429
|
+
const current = yield* getRun(input.runId);
|
|
430
|
+
if (terminal(current.status)) {
|
|
431
|
+
return { _tag: "Terminal", runId: current.runId, status: current.status };
|
|
432
|
+
}
|
|
433
|
+
if (input.allowCodeDrift !== true) yield* refuseCodeDrift(input.runId);
|
|
434
|
+
let handedTo;
|
|
435
|
+
const claimed = yield* (input.allowCodeDrift === true ? runtime.resumeAdopting(input.runId, { scope: "launched" }) : runtime.resume(input.runId, { scope: "launched" })).pipe(
|
|
436
|
+
import_effect.Effect.catchTag("/control/ClaimLost", (error) => {
|
|
437
|
+
if (error.parkedBy !== void 0) {
|
|
438
|
+
handedTo = error.parkedBy;
|
|
439
|
+
return import_effect.Effect.succeed(void 0);
|
|
440
|
+
}
|
|
441
|
+
if (error.reason !== void 0) return import_effect.Effect.fail(error);
|
|
442
|
+
return runtime.getRun(input.runId).pipe(import_effect.Effect.flatMap(
|
|
443
|
+
(stored) => (
|
|
444
|
+
// A retained fork is parked in control and pending in the
|
|
445
|
+
// engine. Its accepted read overlay is not a live claim.
|
|
446
|
+
// Never replace that engine continuation with control state.
|
|
447
|
+
live(stored.status) || live(current.status) && current.executionView?.current.status !== "pending" ? import_effect.Effect.fail(new import_ControlError.ClaimLost({ runId: input.runId })) : import_effect.Effect.succeed(void 0)
|
|
448
|
+
)
|
|
449
|
+
));
|
|
450
|
+
})
|
|
451
|
+
);
|
|
452
|
+
const sequence = yield* record(
|
|
453
|
+
input.runId,
|
|
454
|
+
"control.run.resume",
|
|
455
|
+
json({
|
|
456
|
+
runId: input.runId,
|
|
457
|
+
status: (claimed ?? current).status,
|
|
458
|
+
...claimed === void 0 ? {} : ControlFacts.runFact(claimed),
|
|
459
|
+
principal,
|
|
460
|
+
...input.reason === void 0 ? {} : { reason: input.reason },
|
|
461
|
+
...handedTo === void 0 ? {} : { handedTo }
|
|
462
|
+
})
|
|
463
|
+
);
|
|
464
|
+
if (handedTo !== void 0) {
|
|
465
|
+
yield* runtime.requestResume(input.runId, { consent: sequence });
|
|
466
|
+
const handedOff = {
|
|
467
|
+
_tag: "Accepted",
|
|
468
|
+
receiptId: input.idempotencyKey,
|
|
469
|
+
runId: input.runId,
|
|
470
|
+
handedTo
|
|
471
|
+
};
|
|
472
|
+
return handedOff;
|
|
473
|
+
}
|
|
474
|
+
return claimed === void 0 ? accepted(input.idempotencyKey, input.runId) : terminalOrAccepted(input.idempotencyKey, claimed);
|
|
475
|
+
})
|
|
476
|
+
);
|
|
477
|
+
});
|
|
478
|
+
const executorRequestCancel = (runId) => import_effect.Option.isNone(executor) ? import_effect.Effect.succeed("unknown") : executor.value.requestCancel({ runId });
|
|
479
|
+
const executorSettleCancelledPark = (runId) => import_effect.Option.isNone(executor) ? import_effect.Effect.void : executor.value.settleCancelledPark({ runId });
|
|
480
|
+
const reconcileTerminal = (runId, status) => import_effect.Effect.gen(function* () {
|
|
481
|
+
const current = yield* runtime.getRun(runId);
|
|
482
|
+
if (terminal(current.status)) return;
|
|
483
|
+
const fence = yield* runtime.claimFence(runId).pipe(
|
|
484
|
+
// A parked coordination row has released its fence. Claim that row
|
|
485
|
+
// before copying the engine's terminal fact; a live peer still wins.
|
|
486
|
+
import_effect.Effect.catchTag("/control/ClaimLost", () => runtime.resume(runId).pipe(import_effect.Effect.andThen(runtime.claimFence(runId))))
|
|
487
|
+
);
|
|
488
|
+
const run = yield* runtime.writeStatus(runId, fence, status);
|
|
489
|
+
yield* emit(runId, `control.run.${status}`, json({ runId, status, ...ControlFacts.runFact(run) }));
|
|
490
|
+
}).pipe(
|
|
491
|
+
journal.transact,
|
|
492
|
+
import_effect.Effect.catchCause(
|
|
493
|
+
(failure) => import_effect.Effect.annotateLogs(
|
|
494
|
+
import_effect.Effect.logWarning("A settled engine row could not be reconciled onto the control row"),
|
|
495
|
+
{ runId, status, cause: import_effect.Cause.pretty(failure) }
|
|
496
|
+
)
|
|
497
|
+
)
|
|
498
|
+
);
|
|
499
|
+
const wake = (run, messageId) => {
|
|
500
|
+
if (run.status !== "parked") return import_effect.Effect.void;
|
|
501
|
+
if (run.waitingReason !== "event" && run.waitingReason !== "released") return import_effect.Effect.void;
|
|
502
|
+
return refuseCodeDrift(run.runId).pipe(
|
|
503
|
+
import_effect.Effect.andThen(runtime.resume(run.runId, { scope: "launched" })),
|
|
504
|
+
import_effect.Effect.flatMap(
|
|
505
|
+
(resumed) => emit(
|
|
506
|
+
run.runId,
|
|
507
|
+
"control.steer.woke",
|
|
508
|
+
{
|
|
509
|
+
runId: run.runId,
|
|
510
|
+
messageId,
|
|
511
|
+
status: resumed.status,
|
|
512
|
+
...ControlFacts.runFact(resumed)
|
|
513
|
+
}
|
|
514
|
+
)
|
|
515
|
+
),
|
|
516
|
+
// A detached live host keeps its fence. Delegate the admitted event
|
|
517
|
+
// wake to it, just as a node decision does, instead of leaving an idle
|
|
518
|
+
// retained module with input it will never get a boundary to drain.
|
|
519
|
+
import_effect.Effect.catchTag(
|
|
520
|
+
"/control/ClaimLost",
|
|
521
|
+
(refusal) => refusal.parkedBy === void 0 ? import_effect.Effect.void : import_effect.Effect.gen(function* () {
|
|
522
|
+
const delegated = yield* runtime.requestResume(run.runId).pipe(
|
|
523
|
+
import_effect.Effect.catchTag("/control/InvalidInput", () => import_effect.Effect.succeed(void 0))
|
|
524
|
+
);
|
|
525
|
+
if (delegated !== void 0) yield* emit(run.runId, "control.run.resumed", { runId: run.runId });
|
|
526
|
+
})
|
|
527
|
+
),
|
|
528
|
+
import_effect.Effect.catchTag("/control/RunNotFound", () => import_effect.Effect.void),
|
|
529
|
+
import_effect.Effect.catchTag(
|
|
530
|
+
"/control/CodeDrift",
|
|
531
|
+
(drift) => import_effect.Effect.annotateLogs(import_effect.Effect.logWarning("A steer did not wake a run whose code changed"), {
|
|
532
|
+
runId: run.runId,
|
|
533
|
+
cause: drift.message
|
|
534
|
+
})
|
|
535
|
+
)
|
|
536
|
+
);
|
|
537
|
+
};
|
|
538
|
+
const withSteering = (runs) => import_effect.Effect.forEach(runs, (run) => notifications.pending(run.runId).pipe(
|
|
539
|
+
import_effect.Effect.map((pending) => ({
|
|
540
|
+
...run,
|
|
541
|
+
steering: {
|
|
542
|
+
pending: pending.filter((notification) => notification.delivery === "steer").length
|
|
543
|
+
}
|
|
544
|
+
})),
|
|
545
|
+
import_effect.Effect.catchTag("/notifications/NotificationError", () => import_effect.Effect.succeed(run)),
|
|
546
|
+
import_effect.Effect.mapError(
|
|
547
|
+
(cause) => new import_ControlError.PersistenceError({
|
|
548
|
+
operation: "control.list.steering",
|
|
549
|
+
message: `Failed to read pending steering for ${run.runId}`,
|
|
550
|
+
cause
|
|
551
|
+
})
|
|
552
|
+
)
|
|
553
|
+
));
|
|
554
|
+
const triggerRuns = (triggerId) => import_effect.Effect.gen(function* () {
|
|
555
|
+
let start = 0;
|
|
556
|
+
while (true) {
|
|
557
|
+
const fires = yield* dispatch.fires({
|
|
558
|
+
_tag: "fires",
|
|
559
|
+
filters: { triggerId },
|
|
560
|
+
limit: import_ControlSchema.maxPageSize,
|
|
561
|
+
...start === 0 ? {} : { cursor: String(start) }
|
|
562
|
+
});
|
|
563
|
+
if (fires.length <= start + import_ControlSchema.maxPageSize) {
|
|
564
|
+
return fires.flatMap((fire) => fire.triggerId === triggerId && fire.runId !== void 0 ? [fire.runId] : []);
|
|
565
|
+
}
|
|
566
|
+
start = Math.max(fires.length, start * 2);
|
|
567
|
+
}
|
|
568
|
+
});
|
|
569
|
+
const readerSees = (reader, runId) => runId.startsWith("plan:") ? import_effect.Effect.succeed(false) : runtime.getRun(runId).pipe(
|
|
570
|
+
import_effect.Effect.map((run) => (0, import_ControlRuntime.launchedByMatches)(run, reader)),
|
|
571
|
+
import_effect.Effect.catchTag("/control/RunNotFound", () => import_effect.Effect.succeed(false))
|
|
572
|
+
);
|
|
573
|
+
const confined = (submitted, mutation) => submitted.reader === void 0 ? mutation : import_effect.Effect.flatMap(
|
|
574
|
+
readerSees(submitted.reader, submitted.runId),
|
|
575
|
+
(visible) => visible ? mutation : import_effect.Effect.fail(new import_ControlError.RunNotFound({ runId: submitted.runId }))
|
|
576
|
+
);
|
|
577
|
+
const list = (request) => import_effect.Effect.gen(function* () {
|
|
578
|
+
if (request._tag === "executions") {
|
|
579
|
+
if (request.reader !== void 0 && !(yield* readerSees(request.reader, request.runId))) {
|
|
580
|
+
return yield* new import_ControlError.RunNotFound({ runId: request.runId });
|
|
581
|
+
}
|
|
582
|
+
yield* runtime.getRun(request.runId);
|
|
583
|
+
yield* import_effect.Schema.decodeUnknownEffect(import_ControlSchema.ListRequest)(request).pipe(
|
|
584
|
+
import_effect.Effect.mapError(() => invalid("executionIds: at most 200 nonempty execution IDs are admitted"))
|
|
585
|
+
);
|
|
586
|
+
const batch = import_effect.Option.isSome(executor) && executor.value.readExecutions !== void 0 ? yield* executor.value.readExecutions(request) : {
|
|
587
|
+
source: null,
|
|
588
|
+
revision: null,
|
|
589
|
+
snapshots: request.executionIds.map((executionId) => ({
|
|
590
|
+
_tag: "Unavailable",
|
|
591
|
+
executionId,
|
|
592
|
+
reason: "unsupported"
|
|
593
|
+
}))
|
|
594
|
+
};
|
|
595
|
+
return { _tag: "executions", source: batch.source, revision: batch.revision, items: batch.snapshots };
|
|
596
|
+
}
|
|
597
|
+
const bounds = yield* pageBounds(request._tag === "runs" ? void 0 : request.cursor, request.limit);
|
|
598
|
+
if (request._tag === "plans") {
|
|
599
|
+
if (request.reader !== void 0) return { _tag: "plans", items: [] };
|
|
600
|
+
const result = yield* runtime.queryPlans({
|
|
601
|
+
flowId: request.filters?.flowId,
|
|
602
|
+
decision: request.filters?.decision,
|
|
603
|
+
after: bounds.start,
|
|
604
|
+
limit: bounds.size
|
|
605
|
+
});
|
|
606
|
+
const items2 = result.plans.map((plan) => ({
|
|
607
|
+
card: plan.card,
|
|
608
|
+
// The JSON form the durable runtime stores, whichever runtime answers.
|
|
609
|
+
input: JSON.parse(JSON.stringify(plan.decodedInput ?? null)),
|
|
610
|
+
decision: plan.decision
|
|
611
|
+
}));
|
|
612
|
+
return result.next === void 0 ? { _tag: "plans", items: items2 } : { _tag: "plans", items: items2, nextCursor: String(result.next) };
|
|
613
|
+
}
|
|
614
|
+
if (request._tag === "flows") {
|
|
615
|
+
const [registered, warnings] = yield* import_effect.Effect.all([registry.list(), registry.warnings()]);
|
|
616
|
+
const available = registered.length > 0 ? registered.map((descriptor) => ({
|
|
617
|
+
flowId: descriptor.name,
|
|
618
|
+
description: descriptor.description,
|
|
619
|
+
...(0, import_Descriptor.inputDocument)(descriptor.input) === void 0 ? {} : { inputSchema: (0, import_Descriptor.inputDocument)(descriptor.input) }
|
|
620
|
+
})) : yield* runtime.listFlows;
|
|
621
|
+
const result = page(available, bounds);
|
|
622
|
+
const diagnostics = warnings.length === 0 ? {} : { warnings };
|
|
623
|
+
return result.nextCursor === void 0 ? { _tag: "flows", items: result.items, ...diagnostics } : { _tag: "flows", items: result.items, ...diagnostics, nextCursor: result.nextCursor };
|
|
624
|
+
}
|
|
625
|
+
if (request._tag === "triggers") {
|
|
626
|
+
if (request.reader !== void 0) return { _tag: "triggers", items: [] };
|
|
627
|
+
let triggers = yield* dispatch.list(request);
|
|
628
|
+
if (request.filters?.triggerId !== void 0) {
|
|
629
|
+
triggers = triggers.filter((trigger) => trigger.triggerId === request.filters?.triggerId);
|
|
630
|
+
}
|
|
631
|
+
if (request.filters?.flowId !== void 0) {
|
|
632
|
+
triggers = triggers.filter((trigger) => trigger.flowId === request.filters?.flowId);
|
|
633
|
+
}
|
|
634
|
+
if (request.filters?.enabled !== void 0) {
|
|
635
|
+
triggers = triggers.filter((trigger) => trigger.enabled === request.filters?.enabled);
|
|
636
|
+
}
|
|
637
|
+
const result = page(triggers, bounds);
|
|
638
|
+
return result.nextCursor === void 0 ? { _tag: "triggers", items: result.items } : { _tag: "triggers", items: result.items, nextCursor: result.nextCursor };
|
|
639
|
+
}
|
|
640
|
+
if (request._tag === "fires") {
|
|
641
|
+
let fires = yield* dispatch.fires(request);
|
|
642
|
+
const reader2 = request.reader;
|
|
643
|
+
if (reader2 !== void 0) {
|
|
644
|
+
const seen = yield* import_effect.Effect.forEach(fires, (fire) => fire.runId === void 0 ? import_effect.Effect.succeed(false) : readerSees(reader2, fire.runId));
|
|
645
|
+
fires = fires.filter(
|
|
646
|
+
(_, index) => seen[index]
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
if (request.filters?.triggerId !== void 0) {
|
|
650
|
+
fires = fires.filter((fire) => fire.triggerId === request.filters?.triggerId);
|
|
651
|
+
}
|
|
652
|
+
if (request.filters?.runId !== void 0) {
|
|
653
|
+
fires = fires.filter((fire) => fire.runId === request.filters?.runId);
|
|
654
|
+
}
|
|
655
|
+
if (request.filters?.outcome !== void 0) {
|
|
656
|
+
fires = fires.filter((fire) => fire.outcome === request.filters?.outcome);
|
|
657
|
+
}
|
|
658
|
+
const result = page(fires, bounds);
|
|
659
|
+
return result.nextCursor === void 0 ? { _tag: "fires", items: result.items } : { _tag: "fires", items: result.items, nextCursor: result.nextCursor };
|
|
660
|
+
}
|
|
661
|
+
const filters = request.filters;
|
|
662
|
+
const reader = request.reader;
|
|
663
|
+
if (reader !== void 0 && filters?.principalId !== void 0 && filters.principalId !== reader.id) {
|
|
664
|
+
return { _tag: "runs", items: [] };
|
|
665
|
+
}
|
|
666
|
+
const launcher = reader !== void 0 ? { id: reader.id, kind: reader.kind } : filters?.principalId === void 0 ? void 0 : { id: filters.principalId };
|
|
667
|
+
const fingerprint2 = JSON.stringify([
|
|
668
|
+
filters?.runId ?? null,
|
|
669
|
+
filters?.flowId ?? null,
|
|
670
|
+
filters?.status ?? null,
|
|
671
|
+
filters?.parentRunId ?? null,
|
|
672
|
+
filters?.lineageId ?? null,
|
|
673
|
+
// Keep legacy cursor fingerprints byte-identical unless opting in.
|
|
674
|
+
...filters?.terminal === void 0 && request.order === void 0 ? [] : [filters?.terminal ?? null, request.order ?? null],
|
|
675
|
+
...filters?.since === void 0 && filters?.until === void 0 && filters?.triggerId === void 0 ? [] : [filters.since ?? null, filters.until ?? null, filters.triggerId ?? null],
|
|
676
|
+
...launcher === void 0 ? [] : [launcher.id, launcher.kind ?? null]
|
|
677
|
+
]);
|
|
678
|
+
const cursor = request.cursor === void 0 ? void 0 : yield* import_effect.Schema.decodeUnknownEffect(runCursor)(
|
|
679
|
+
request.cursor
|
|
680
|
+
).pipe(import_effect.Effect.mapError(() => invalid("cursor: expected a run listing cursor")));
|
|
681
|
+
if (cursor !== void 0 && cursor.filters !== fingerprint2) {
|
|
682
|
+
return yield* invalid("cursor: belongs to different run filters");
|
|
683
|
+
}
|
|
684
|
+
if (filters?.runId !== void 0) {
|
|
685
|
+
let runs = yield* getRun(filters.runId).pipe(
|
|
686
|
+
import_effect.Effect.map((run) => [run]),
|
|
687
|
+
import_effect.Effect.catchTag("/control/RunNotFound", () => import_effect.Effect.succeed([]))
|
|
688
|
+
);
|
|
689
|
+
if (filters.flowId !== void 0) runs = runs.filter((run) => run.flowId === filters.flowId);
|
|
690
|
+
if (filters.status !== void 0) runs = runs.filter((run) => run.status === filters.status);
|
|
691
|
+
if (filters.terminal !== void 0) runs = runs.filter((run) => terminal(run.status) === filters.terminal);
|
|
692
|
+
if (filters.parentRunId !== void 0) runs = runs.filter((run) => run.parentRunId === filters.parentRunId);
|
|
693
|
+
if (filters.lineageId !== void 0) runs = runs.filter((run) => run.lineageId === filters.lineageId);
|
|
694
|
+
if (launcher !== void 0) runs = runs.filter((run) => (0, import_ControlRuntime.launchedByMatches)(run, launcher));
|
|
695
|
+
if (filters.since !== void 0) runs = runs.filter((run) => run.createdAt >= filters.since);
|
|
696
|
+
if (filters.until !== void 0) runs = runs.filter((run) => run.createdAt < filters.until);
|
|
697
|
+
if (filters.triggerId !== void 0) {
|
|
698
|
+
const started = yield* triggerRuns(filters.triggerId);
|
|
699
|
+
runs = runs.filter((run) => started.includes(run.runId));
|
|
700
|
+
}
|
|
701
|
+
return { _tag: "runs", items: yield* withSteering(yield* import_effect.Effect.forEach(runs, withCodeDrift)) };
|
|
702
|
+
}
|
|
703
|
+
const postFiltered = observing && (filters?.status !== void 0 || filters?.terminal !== void 0);
|
|
704
|
+
const { triggerId, principalId: _principalId, ...unowned } = filters ?? {};
|
|
705
|
+
const selected = launcher === void 0 ? unowned : { ...unowned, launchedBy: launcher };
|
|
706
|
+
const narrowed = triggerId === void 0 ? selected : { ...selected, runIds: yield* triggerRuns(triggerId) };
|
|
707
|
+
const sourceFilters = postFiltered ? Object.fromEntries(Object.entries(narrowed).filter(([key]) => key !== "status" && key !== "terminal")) : narrowed;
|
|
708
|
+
const collected = [];
|
|
709
|
+
let sourceCursor = cursor;
|
|
710
|
+
let sourceNext;
|
|
711
|
+
while (true) {
|
|
712
|
+
const result = yield* runtime.queryRuns({
|
|
713
|
+
filters: sourceFilters,
|
|
714
|
+
order: request.order,
|
|
715
|
+
cursor: sourceCursor,
|
|
716
|
+
limit: bounds.size - collected.length
|
|
717
|
+
});
|
|
718
|
+
const observed = yield* import_effect.Effect.forEach(result.items, observe);
|
|
719
|
+
for (const run of observed) {
|
|
720
|
+
if (!postFiltered || (filters?.status === void 0 || run.status === filters.status) && (filters?.terminal === void 0 || terminal(run.status) === filters.terminal)) collected.push(run);
|
|
721
|
+
}
|
|
722
|
+
sourceNext = result.nextCursor;
|
|
723
|
+
if (!postFiltered || sourceNext === void 0 || collected.length >= bounds.size) break;
|
|
724
|
+
sourceCursor = { version: 1, filters: fingerprint2, ...sourceNext };
|
|
725
|
+
}
|
|
726
|
+
const items = yield* withSteering(collected);
|
|
727
|
+
return sourceNext === void 0 ? { _tag: "runs", items } : {
|
|
728
|
+
_tag: "runs",
|
|
729
|
+
items,
|
|
730
|
+
nextCursor: JSON.stringify({ version: 1, filters: fingerprint2, ...sourceNext })
|
|
731
|
+
};
|
|
732
|
+
});
|
|
733
|
+
const streamForRun = (runId, filter) => journal.stream({
|
|
734
|
+
runId: import_journal.JournalEvent.RunId.make(runId),
|
|
735
|
+
...filter.afterSequence === void 0 ? {} : { afterSequence: import_journal.JournalEvent.Seq.make(filter.afterSequence) }
|
|
736
|
+
}).pipe(
|
|
737
|
+
import_effect.Stream.map(eventFromEntry),
|
|
738
|
+
import_effect.Stream.mapError(watchReadFailed)
|
|
739
|
+
);
|
|
740
|
+
const snapshotHighWater = (runId) => journal.transact(
|
|
741
|
+
import_effect.Effect.gen(function* () {
|
|
742
|
+
const first = yield* journal.entries({ runId, limit: 1 });
|
|
743
|
+
const initial = first.entries[0];
|
|
744
|
+
if (initial === void 0) return void 0;
|
|
745
|
+
let lower = initial.seq;
|
|
746
|
+
let step = 1;
|
|
747
|
+
let upper = lower;
|
|
748
|
+
const maximumSequence = Number.MAX_SAFE_INTEGER - 1;
|
|
749
|
+
while (lower < maximumSequence) {
|
|
750
|
+
const probe = Math.min(maximumSequence, lower + step - 1);
|
|
751
|
+
const next = yield* journal.entries({
|
|
752
|
+
runId,
|
|
753
|
+
after: import_journal.JournalEvent.Seq.make(probe),
|
|
754
|
+
limit: 1
|
|
755
|
+
});
|
|
756
|
+
const entry = next.entries[0];
|
|
757
|
+
if (entry === void 0) {
|
|
758
|
+
upper = probe;
|
|
759
|
+
break;
|
|
760
|
+
}
|
|
761
|
+
lower = entry.seq;
|
|
762
|
+
if (lower === maximumSequence) return entry.seq;
|
|
763
|
+
step = Math.min(maximumSequence - lower, step * 2);
|
|
764
|
+
}
|
|
765
|
+
while (lower < upper) {
|
|
766
|
+
const middle = lower + Math.ceil((upper - lower) / 2);
|
|
767
|
+
const next = yield* journal.entries({
|
|
768
|
+
runId,
|
|
769
|
+
after: import_journal.JournalEvent.Seq.make(middle - 1),
|
|
770
|
+
limit: 1
|
|
771
|
+
});
|
|
772
|
+
const entry = next.entries[0];
|
|
773
|
+
if (entry === void 0) {
|
|
774
|
+
upper = middle - 1;
|
|
775
|
+
} else {
|
|
776
|
+
lower = entry.seq;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
return import_journal.JournalEvent.Seq.make(lower);
|
|
780
|
+
})
|
|
781
|
+
).pipe(import_effect.Effect.mapError(watchReadFailed));
|
|
782
|
+
const snapshotForRunAt = (runId, filter, highWater) => {
|
|
783
|
+
const journalRunId = import_journal.JournalEvent.RunId.make(runId);
|
|
784
|
+
const initialAfter = filter.afterSequence === void 0 ? void 0 : import_journal.JournalEvent.Seq.make(filter.afterSequence);
|
|
785
|
+
if (highWater === void 0 || initialAfter !== void 0 && initialAfter >= highWater) {
|
|
786
|
+
return import_effect.Stream.empty;
|
|
787
|
+
}
|
|
788
|
+
return import_effect.Stream.paginate(initialAfter, (after) => journal.entries({
|
|
789
|
+
runId: journalRunId,
|
|
790
|
+
...after === void 0 ? {} : { after },
|
|
791
|
+
limit: snapshotPageSize
|
|
792
|
+
}).pipe(
|
|
793
|
+
import_effect.Effect.map((page2) => {
|
|
794
|
+
const entries2 = page2.entries.filter((entry) => entry.seq <= highWater);
|
|
795
|
+
const last = entries2.at(-1);
|
|
796
|
+
const next = last === void 0 || last.seq >= highWater || !page2.hasMore ? import_effect.Option.none() : import_effect.Option.some(last.seq);
|
|
797
|
+
return [entries2, next];
|
|
798
|
+
}),
|
|
799
|
+
import_effect.Effect.mapError(watchReadFailed)
|
|
800
|
+
)).pipe(import_effect.Stream.map(eventFromEntry));
|
|
801
|
+
};
|
|
802
|
+
const snapshotForRun = (runId, filter) => import_effect.Stream.unwrap(
|
|
803
|
+
import_effect.Effect.map(
|
|
804
|
+
snapshotHighWater(import_journal.JournalEvent.RunId.make(runId)),
|
|
805
|
+
(highWater) => snapshotForRunAt(runId, filter, highWater)
|
|
806
|
+
)
|
|
807
|
+
);
|
|
808
|
+
const inventory = (page2) => {
|
|
809
|
+
const first = {};
|
|
810
|
+
return import_effect.Stream.paginate(
|
|
811
|
+
first,
|
|
812
|
+
(cursor) => import_effect.Effect.map(
|
|
813
|
+
page2({ ...cursor, limit: partitionPageSize }),
|
|
814
|
+
(next) => [
|
|
815
|
+
next.ids,
|
|
816
|
+
next.next === void 0 ? import_effect.Option.none() : import_effect.Option.some({ after: next.next, through: next.through })
|
|
817
|
+
]
|
|
818
|
+
)
|
|
819
|
+
);
|
|
820
|
+
};
|
|
821
|
+
const journalPartitions = import_effect.Stream.concat(
|
|
822
|
+
import_effect.Stream.map(inventory(runtime.pagePlanIds), (planId) => `plan:${planId}`),
|
|
823
|
+
inventory(runtime.pageRunIds)
|
|
824
|
+
);
|
|
825
|
+
const snapshot = (filter) => filter.runId !== void 0 ? snapshotForRun(filter.runId, filter) : import_effect.Stream.flatMap(journalPartitions, (partition) => snapshotForRun(partition, filter), {
|
|
826
|
+
concurrency: snapshotPartitionConcurrency
|
|
827
|
+
});
|
|
828
|
+
const entries = (filter) => filter.follow === false ? snapshot(filter) : filter.runId !== void 0 ? streamForRun(filter.runId, filter) : import_effect.Stream.unwrap(
|
|
829
|
+
import_effect.Effect.gen(function* () {
|
|
830
|
+
const subscription = yield* journal.changes;
|
|
831
|
+
const cutoffs = /* @__PURE__ */ new Map();
|
|
832
|
+
const pin = (partition) => {
|
|
833
|
+
const pinned = import_effect.Deferred.makeUnsafe();
|
|
834
|
+
cutoffs.set(partition, pinned);
|
|
835
|
+
return snapshotHighWater(import_journal.JournalEvent.RunId.make(partition)).pipe(
|
|
836
|
+
import_effect.Effect.onExit((exit) => import_effect.Deferred.done(pinned, exit))
|
|
837
|
+
);
|
|
838
|
+
};
|
|
839
|
+
const walk = journalPartitions.pipe(
|
|
840
|
+
import_effect.Stream.mapEffect(
|
|
841
|
+
(partition) => import_effect.Effect.suspend(
|
|
842
|
+
() => cutoffs.has(partition) ? import_effect.Effect.succeed(import_effect.Option.none()) : import_effect.Effect.map(pin(partition), (highWater) => import_effect.Option.some([partition, highWater]))
|
|
843
|
+
),
|
|
844
|
+
{ concurrency: snapshotPartitionConcurrency }
|
|
845
|
+
),
|
|
846
|
+
import_effect.Stream.filter(import_effect.Option.isSome),
|
|
847
|
+
import_effect.Stream.map((pinned) => pinned.value),
|
|
848
|
+
import_effect.Stream.flatMap(([partition, highWater]) => snapshotForRunAt(partition, filter, highWater), {
|
|
849
|
+
concurrency: snapshotPartitionConcurrency
|
|
850
|
+
})
|
|
851
|
+
);
|
|
852
|
+
const cutoffFor = (partition) => import_effect.Effect.suspend(() => {
|
|
853
|
+
const known = cutoffs.get(partition);
|
|
854
|
+
if (known !== void 0) {
|
|
855
|
+
return import_effect.Effect.map(import_effect.Deferred.await(known), (highWater) => ({ highWater, claimed: false }));
|
|
856
|
+
}
|
|
857
|
+
return import_effect.Effect.map(pin(partition), (highWater) => ({ highWater, claimed: true }));
|
|
858
|
+
});
|
|
859
|
+
const seenByPartition = /* @__PURE__ */ new Map();
|
|
860
|
+
const gapCheck = (partition, expected, arrived) => journal.entries({
|
|
861
|
+
runId: import_journal.JournalEvent.RunId.make(partition),
|
|
862
|
+
...expected === void 0 ? {} : { after: import_journal.JournalEvent.Seq.make(expected) },
|
|
863
|
+
limit: 1
|
|
864
|
+
}).pipe(
|
|
865
|
+
import_effect.Effect.mapError(watchReadFailed),
|
|
866
|
+
import_effect.Effect.flatMap((page2) => {
|
|
867
|
+
const missed = page2.entries[0];
|
|
868
|
+
if (missed === void 0 || missed.seq >= arrived) return import_effect.Effect.void;
|
|
869
|
+
return import_effect.Effect.fail(
|
|
870
|
+
new import_ControlError.PersistenceError({
|
|
871
|
+
operation: "watch",
|
|
872
|
+
message: `the live tail lost journal entries for ${partition}: sequence ${missed.seq} was committed but never delivered to this watcher`
|
|
873
|
+
})
|
|
874
|
+
);
|
|
875
|
+
})
|
|
876
|
+
);
|
|
877
|
+
const trackTail = (entry, cutoff) => {
|
|
878
|
+
const partition = String(entry.runId);
|
|
879
|
+
const expected = seenByPartition.get(partition) ?? cutoff;
|
|
880
|
+
if (expected !== void 0 && entry.seq <= expected) return import_effect.Effect.succeed(import_effect.Option.none());
|
|
881
|
+
seenByPartition.set(partition, entry.seq);
|
|
882
|
+
return expected !== void 0 && entry.seq === expected + 1 ? import_effect.Effect.succeed(import_effect.Option.some(entry)) : import_effect.Effect.map(gapCheck(partition, expected, entry.seq), () => import_effect.Option.some(entry));
|
|
883
|
+
};
|
|
884
|
+
const tail = import_effect.Stream.fromSubscription(subscription).pipe(
|
|
885
|
+
// Every committed entry names its partition. The first one from a
|
|
886
|
+
// partition nobody pinned yet pins it here and is preceded by that
|
|
887
|
+
// partition's snapshot, which also recovers any earlier notice the
|
|
888
|
+
// sliding buffer dropped.
|
|
889
|
+
import_effect.Stream.flatMap(
|
|
890
|
+
(entry) => import_effect.Stream.unwrap(import_effect.Effect.gen(function* () {
|
|
891
|
+
const partition = String(entry.runId);
|
|
892
|
+
const { highWater, claimed } = yield* cutoffFor(partition);
|
|
893
|
+
const history = claimed ? snapshotForRunAt(partition, filter, highWater) : import_effect.Stream.empty;
|
|
894
|
+
if (highWater !== void 0 && entry.seq <= highWater) return history;
|
|
895
|
+
const tracked = yield* trackTail(entry, highWater);
|
|
896
|
+
return import_effect.Option.isSome(tracked) ? import_effect.Stream.concat(history, import_effect.Stream.succeed(eventFromEntry(tracked.value))) : history;
|
|
897
|
+
}))
|
|
898
|
+
)
|
|
899
|
+
);
|
|
900
|
+
return import_effect.Stream.merge(walk, tail);
|
|
901
|
+
})
|
|
902
|
+
);
|
|
903
|
+
const watch = (filter) => {
|
|
904
|
+
const { reader, ...unrestricted } = filter;
|
|
905
|
+
if (reader === void 0) return watchAll(unrestricted);
|
|
906
|
+
if (filter.runId !== void 0) {
|
|
907
|
+
const runId = filter.runId;
|
|
908
|
+
return import_effect.Stream.unwrap(import_effect.Effect.map(
|
|
909
|
+
readerSees(reader, runId),
|
|
910
|
+
(visible) => visible ? watchAll(unrestricted) : import_effect.Stream.fail(new import_ControlError.RunNotFound({ runId }))
|
|
911
|
+
));
|
|
912
|
+
}
|
|
913
|
+
const decided = /* @__PURE__ */ new Map();
|
|
914
|
+
return watchAll(unrestricted).pipe(
|
|
915
|
+
// A lost-tail failure names the partition and sequence it missed,
|
|
916
|
+
// which may be another principal's; a restricted reader learns only
|
|
917
|
+
// that its stream ended incomplete.
|
|
918
|
+
import_effect.Stream.mapError(
|
|
919
|
+
(error) => error._tag === "/control/PersistenceError" ? new import_ControlError.PersistenceError({ operation: "watch", message: "the watch lost journal entries and ended" }) : error
|
|
920
|
+
),
|
|
921
|
+
import_effect.Stream.filterEffect((event) => {
|
|
922
|
+
const partition = event.runId;
|
|
923
|
+
const known = decided.get(partition);
|
|
924
|
+
if (known !== void 0) return import_effect.Effect.succeed(known);
|
|
925
|
+
return import_effect.Effect.tap(
|
|
926
|
+
readerSees(reader, partition),
|
|
927
|
+
(visible) => import_effect.Effect.sync(() => decided.set(partition, visible))
|
|
928
|
+
);
|
|
929
|
+
})
|
|
930
|
+
);
|
|
931
|
+
};
|
|
932
|
+
const watchAll = (filter) => {
|
|
933
|
+
if (filter.afterSequence !== void 0 && filter.runId === void 0) {
|
|
934
|
+
return import_effect.Stream.fail(invalid("afterSequence: a watch cursor resumes one run, so it requires runId"));
|
|
935
|
+
}
|
|
936
|
+
const cursor = filter.afterCursor;
|
|
937
|
+
if (cursor !== void 0) {
|
|
938
|
+
if (filter.runId === void 0) {
|
|
939
|
+
return import_effect.Stream.fail(invalid("afterCursor: a watch cursor resumes one run, so it requires runId"));
|
|
940
|
+
}
|
|
941
|
+
if (filter.afterSequence !== void 0) {
|
|
942
|
+
return import_effect.Stream.fail(invalid("afterCursor: cannot be combined with afterSequence"));
|
|
943
|
+
}
|
|
944
|
+
if (!import_effect.Schema.is(import_ControlSchema.WatchCursor)(cursor)) {
|
|
945
|
+
return import_effect.Stream.fail(invalid("afterCursor: sequence and offset must be nonnegative safe journal integers"));
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
const afterSequence = cursor === void 0 ? filter.afterSequence : cursor.offset === void 0 ? cursor.sequence : cursor.sequence === 0 ? void 0 : cursor.sequence - 1;
|
|
949
|
+
return entries({
|
|
950
|
+
...filter,
|
|
951
|
+
afterSequence
|
|
952
|
+
}).pipe(
|
|
953
|
+
import_effect.Stream.map((event) => {
|
|
954
|
+
const lineage = Lineage.derive(event);
|
|
955
|
+
const expanded = [event, ...lineage === void 0 ? [] : [lineage], ...Steering.derive(event)];
|
|
956
|
+
const checkpointed = expanded.map((member, offset) => ({
|
|
957
|
+
...member,
|
|
958
|
+
cursor: offset === expanded.length - 1 ? { sequence: event.sequence } : { sequence: event.sequence, offset }
|
|
959
|
+
}));
|
|
960
|
+
return cursor?.offset !== void 0 && event.sequence === cursor.sequence ? checkpointed.slice(cursor.offset + 1) : checkpointed;
|
|
961
|
+
}),
|
|
962
|
+
import_effect.Stream.flattenIterable
|
|
963
|
+
);
|
|
964
|
+
};
|
|
965
|
+
const service = {
|
|
966
|
+
plan: import_effect.Effect.fn("Control.plan")(
|
|
967
|
+
(input) => mutationSemaphore.withPermits(1)(
|
|
968
|
+
journal.transact(import_effect.Effect.gen(function* () {
|
|
969
|
+
const { card, created } = yield* runtime.plan(input);
|
|
970
|
+
const runId = import_journal.JournalEvent.RunId.make(`plan:${card.planId}`);
|
|
971
|
+
if (!created) {
|
|
972
|
+
let after;
|
|
973
|
+
while (true) {
|
|
974
|
+
const page2 = yield* journal.entries({
|
|
975
|
+
runId,
|
|
976
|
+
...after === void 0 ? {} : { after },
|
|
977
|
+
limit: snapshotPageSize
|
|
978
|
+
});
|
|
979
|
+
if (page2.entries.some(
|
|
980
|
+
(entry) => entry.sourceId === sourceId && entry.eventType === "control.plan.created"
|
|
981
|
+
)) {
|
|
982
|
+
return card;
|
|
983
|
+
}
|
|
984
|
+
if (!page2.hasMore) break;
|
|
985
|
+
after = page2.entries[page2.entries.length - 1].seq;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
yield* emit(runId, "control.plan.created", {
|
|
989
|
+
planId: card.planId,
|
|
990
|
+
flowId: card.flowId,
|
|
991
|
+
digest: card.digest
|
|
992
|
+
});
|
|
993
|
+
return card;
|
|
994
|
+
})).pipe(
|
|
995
|
+
import_effect.Effect.mapError(
|
|
996
|
+
(cause) => cause instanceof import_journal.Journal.JournalError ? new import_ControlError.PersistenceError({
|
|
997
|
+
operation: "plan",
|
|
998
|
+
message: "Failed to commit plan and its creation entry atomically",
|
|
999
|
+
cause
|
|
1000
|
+
}) : cause
|
|
1001
|
+
)
|
|
1002
|
+
)
|
|
1003
|
+
).pipe(import_effect.Effect.flatMap(
|
|
1004
|
+
(card) => registry.warnings().pipe(import_effect.Effect.map((all) => {
|
|
1005
|
+
const warnings = all.filter((warning) => warning.name === input.flowId);
|
|
1006
|
+
return warnings.length === 0 ? card : { warnings, ...card };
|
|
1007
|
+
}))
|
|
1008
|
+
))
|
|
1009
|
+
),
|
|
1010
|
+
run: import_effect.Effect.fn("Control.run")(
|
|
1011
|
+
(submitted) => import_effect.Effect.gen(function* () {
|
|
1012
|
+
const input = yield* snapshotRun(submitted);
|
|
1013
|
+
if (input._tag === "Resume") return yield* runMutation(input);
|
|
1014
|
+
const principal = yield* runtime.stampPrincipal(input.principal);
|
|
1015
|
+
let admitted;
|
|
1016
|
+
const receipt = yield* mutate(
|
|
1017
|
+
"run",
|
|
1018
|
+
input.idempotencyKey,
|
|
1019
|
+
principal,
|
|
1020
|
+
fingerprint("run", principal, input),
|
|
1021
|
+
import_effect.Effect.gen(function* () {
|
|
1022
|
+
const launched = yield* runtime.launch(
|
|
1023
|
+
input.planId,
|
|
1024
|
+
input.digest,
|
|
1025
|
+
input.envelope,
|
|
1026
|
+
principal,
|
|
1027
|
+
input.reservedRunId
|
|
1028
|
+
);
|
|
1029
|
+
if (launched._tag === "Parked") {
|
|
1030
|
+
return { ...launched.receipt, receiptId: input.idempotencyKey };
|
|
1031
|
+
}
|
|
1032
|
+
const plan = yield* runtime.getPlan(input.planId);
|
|
1033
|
+
const trigger = declaredTrigger(plan.decodedInput);
|
|
1034
|
+
yield* emit(
|
|
1035
|
+
launched.run.runId,
|
|
1036
|
+
"control.run.accepted",
|
|
1037
|
+
{
|
|
1038
|
+
runId: launched.run.runId,
|
|
1039
|
+
planId: input.planId,
|
|
1040
|
+
digest: input.digest,
|
|
1041
|
+
status: launched.run.status,
|
|
1042
|
+
...trigger === void 0 ? {} : { trigger },
|
|
1043
|
+
...ControlFacts.runFact(launched.run, "created")
|
|
1044
|
+
}
|
|
1045
|
+
);
|
|
1046
|
+
admitted = { plan, run: launched.run };
|
|
1047
|
+
return {
|
|
1048
|
+
_tag: "Accepted",
|
|
1049
|
+
receiptId: input.idempotencyKey,
|
|
1050
|
+
runId: launched.run.runId
|
|
1051
|
+
};
|
|
1052
|
+
}),
|
|
1053
|
+
true,
|
|
1054
|
+
true
|
|
1055
|
+
);
|
|
1056
|
+
if (receipt._tag === "Accepted" && admitted !== void 0) yield* hand(admitted);
|
|
1057
|
+
else if (receipt._tag === "AlreadyApplied" && receipt.runId !== void 0) {
|
|
1058
|
+
yield* relaunchStranded(receipt.runId);
|
|
1059
|
+
}
|
|
1060
|
+
return receipt;
|
|
1061
|
+
})
|
|
1062
|
+
),
|
|
1063
|
+
approve: import_effect.Effect.fn("Control.approve")((input) => decide("approved", input)),
|
|
1064
|
+
deny: import_effect.Effect.fn("Control.deny")((input) => decide("denied", input)),
|
|
1065
|
+
steer: import_effect.Effect.fn("Control.steer")(
|
|
1066
|
+
(submitted) => confined(
|
|
1067
|
+
submitted,
|
|
1068
|
+
import_effect.Effect.flatMap(snapshotSteer(submitted), (input) => mutate(
|
|
1069
|
+
"steer",
|
|
1070
|
+
input.idempotencyKey,
|
|
1071
|
+
input.message.principal,
|
|
1072
|
+
fingerprint("steer", input.message.principal, input),
|
|
1073
|
+
import_effect.Effect.gen(function* () {
|
|
1074
|
+
if (input.message.runId !== input.runId) {
|
|
1075
|
+
return yield* import_effect.Effect.fail(
|
|
1076
|
+
invalid(
|
|
1077
|
+
`message.runId: must be ${JSON.stringify(input.runId)}, received ${JSON.stringify(input.message.runId)}`
|
|
1078
|
+
)
|
|
1079
|
+
);
|
|
1080
|
+
}
|
|
1081
|
+
const run = yield* getRun(input.runId);
|
|
1082
|
+
if (terminal(run.status)) return { _tag: "Terminal", runId: run.runId, status: run.status };
|
|
1083
|
+
const item = (0, import_ControlSchema.steerItem)(input.message);
|
|
1084
|
+
const admission = yield* notifications.admit(input.runId, {
|
|
1085
|
+
_tag: "human-steer",
|
|
1086
|
+
id: input.message.messageId,
|
|
1087
|
+
delivery: "steer",
|
|
1088
|
+
targetLineageId: input.runId,
|
|
1089
|
+
provenance: {
|
|
1090
|
+
sourceRunId: input.runId,
|
|
1091
|
+
sourceLineageId: input.runId,
|
|
1092
|
+
sourceTurn: 0,
|
|
1093
|
+
sourceActor: `${input.message.principal.kind}:${input.message.principal.id}`,
|
|
1094
|
+
...input.message.attribution === void 0 ? {} : { attribution: input.message.attribution }
|
|
1095
|
+
},
|
|
1096
|
+
payload: SteerPayload.encode(item)
|
|
1097
|
+
}, input.version).pipe(
|
|
1098
|
+
import_effect.Effect.mapError(
|
|
1099
|
+
(cause) => cause instanceof import_notifications.NotificationQueue.NotificationError ? cause : new import_ControlError.PersistenceError({
|
|
1100
|
+
operation: "control.steer.notification",
|
|
1101
|
+
message: "Failed to admit steering notification",
|
|
1102
|
+
cause
|
|
1103
|
+
})
|
|
1104
|
+
)
|
|
1105
|
+
);
|
|
1106
|
+
const inputReceipt = () => {
|
|
1107
|
+
if (input.version === void 0 || item.kind !== "Message") {
|
|
1108
|
+
return accepted(input.idempotencyKey, input.runId);
|
|
1109
|
+
}
|
|
1110
|
+
const payload = admission.consumedNotification?.payload;
|
|
1111
|
+
return {
|
|
1112
|
+
_tag: "Accepted",
|
|
1113
|
+
receiptId: input.idempotencyKey,
|
|
1114
|
+
runId: input.runId,
|
|
1115
|
+
inputConsumed: admission.consumed === true,
|
|
1116
|
+
inputBody: payload?.body ?? item.body
|
|
1117
|
+
};
|
|
1118
|
+
};
|
|
1119
|
+
if (input.version !== void 0 && (admission.consumed || admission.duplicate)) {
|
|
1120
|
+
return inputReceipt();
|
|
1121
|
+
}
|
|
1122
|
+
if (admission.decision === "rejected-full") {
|
|
1123
|
+
return yield* import_effect.Effect.fail(
|
|
1124
|
+
new import_notifications.NotificationQueue.NotificationError({
|
|
1125
|
+
code: "notification_full",
|
|
1126
|
+
notificationId: input.message.messageId,
|
|
1127
|
+
message: "Steering queue is full; retry after pending notifications are delivered"
|
|
1128
|
+
})
|
|
1129
|
+
);
|
|
1130
|
+
}
|
|
1131
|
+
yield* emit(input.runId, Steering.enqueuedEventType, {
|
|
1132
|
+
runId: input.runId,
|
|
1133
|
+
messageId: input.message.messageId,
|
|
1134
|
+
kind: item.kind,
|
|
1135
|
+
createdAt: input.message.createdAt
|
|
1136
|
+
});
|
|
1137
|
+
yield* wake(run, input.message.messageId);
|
|
1138
|
+
return inputReceipt();
|
|
1139
|
+
})
|
|
1140
|
+
))
|
|
1141
|
+
)
|
|
1142
|
+
),
|
|
1143
|
+
signal: import_effect.Effect.fn("Control.signal")(
|
|
1144
|
+
(submitted) => confined(
|
|
1145
|
+
submitted,
|
|
1146
|
+
import_effect.Effect.flatMap(snapshotSignal(submitted), (input) => import_effect.Effect.gen(function* () {
|
|
1147
|
+
const principal = yield* runtime.stampPrincipal(input.principal);
|
|
1148
|
+
const key = fingerprint("signal", principal, input);
|
|
1149
|
+
const durableKey = (0, import_planning.mutationKey)("signal", input.idempotencyKey, principal);
|
|
1150
|
+
const receipt = yield* mutate(
|
|
1151
|
+
"signal",
|
|
1152
|
+
input.idempotencyKey,
|
|
1153
|
+
principal,
|
|
1154
|
+
key,
|
|
1155
|
+
import_effect.Effect.gen(function* () {
|
|
1156
|
+
const current = yield* getRun(input.runId);
|
|
1157
|
+
if (terminal(current.status)) {
|
|
1158
|
+
return { _tag: "Terminal", runId: current.runId, status: current.status };
|
|
1159
|
+
}
|
|
1160
|
+
yield* runtime.admitSignal(durableKey, input.runId, input.signal, principal);
|
|
1161
|
+
yield* emit(input.runId, "control.signal.admitted", {
|
|
1162
|
+
commandId: durableKey,
|
|
1163
|
+
runId: input.runId,
|
|
1164
|
+
name: input.signal.name
|
|
1165
|
+
});
|
|
1166
|
+
return accepted(input.idempotencyKey, input.runId);
|
|
1167
|
+
}),
|
|
1168
|
+
true,
|
|
1169
|
+
true
|
|
1170
|
+
);
|
|
1171
|
+
if (receipt._tag !== "Accepted" && receipt._tag !== "AlreadyApplied") return receipt;
|
|
1172
|
+
const command = yield* runtime.signalCommand(durableKey);
|
|
1173
|
+
if (command === void 0 || command.state === "delivered" || command.state === "terminal") return receipt;
|
|
1174
|
+
if (command.state === "rejected") {
|
|
1175
|
+
return yield* new import_ControlError.NoMatchingWait({ runId: input.runId, waitName: input.signal.name });
|
|
1176
|
+
}
|
|
1177
|
+
const delivery = import_effect.Option.isNone(executor) ? "unknown" : yield* executor.value.deliverSignal({ ...command });
|
|
1178
|
+
if (delivery === "no-match") {
|
|
1179
|
+
yield* runtime.settleSignal(durableKey, "rejected");
|
|
1180
|
+
return yield* new import_ControlError.NoMatchingWait({ runId: input.runId, waitName: input.signal.name });
|
|
1181
|
+
}
|
|
1182
|
+
if (delivery === "refused") {
|
|
1183
|
+
yield* runtime.settleSignal(durableKey, "rejected");
|
|
1184
|
+
return yield* new import_ControlError.Unauthorized({
|
|
1185
|
+
message: `This caller has no authority to answer "${input.signal.name}" on run ${input.runId}`
|
|
1186
|
+
});
|
|
1187
|
+
}
|
|
1188
|
+
if (delivery === "delivered") {
|
|
1189
|
+
yield* runtime.settleSignal(durableKey, "delivered");
|
|
1190
|
+
}
|
|
1191
|
+
return receipt;
|
|
1192
|
+
}))
|
|
1193
|
+
)
|
|
1194
|
+
),
|
|
1195
|
+
cancel: import_effect.Effect.fn("Control.cancel")(
|
|
1196
|
+
(submitted) => confined(
|
|
1197
|
+
submitted,
|
|
1198
|
+
import_effect.Effect.flatMap(
|
|
1199
|
+
snapshotReasonedMutation("cancel", submitted),
|
|
1200
|
+
(input) => import_effect.Effect.flatMap(runtime.stampPrincipal(input.principal), (principal) => mutate(
|
|
1201
|
+
"cancel",
|
|
1202
|
+
input.idempotencyKey,
|
|
1203
|
+
principal,
|
|
1204
|
+
fingerprint("cancel", principal, input),
|
|
1205
|
+
import_effect.Effect.gen(function* () {
|
|
1206
|
+
const current = yield* getRun(input.runId);
|
|
1207
|
+
if (terminal(current.status)) {
|
|
1208
|
+
yield* reconcileTerminal(input.runId, current.status);
|
|
1209
|
+
return { _tag: "Terminal", runId: current.runId, status: current.status };
|
|
1210
|
+
}
|
|
1211
|
+
const record2 = yield* executorRequestCancel(input.runId);
|
|
1212
|
+
if (typeof record2 !== "string") {
|
|
1213
|
+
yield* reconcileTerminal(input.runId, record2.status);
|
|
1214
|
+
return { _tag: "Terminal", runId: input.runId, status: record2.status };
|
|
1215
|
+
}
|
|
1216
|
+
const prior = yield* runtime.lookupMutation(
|
|
1217
|
+
(0, import_planning.mutationKey)("cancel", input.idempotencyKey, principal),
|
|
1218
|
+
fingerprint("cancel", principal, input)
|
|
1219
|
+
);
|
|
1220
|
+
if (record2 !== "already-requested" && prior === void 0) {
|
|
1221
|
+
yield* emit(
|
|
1222
|
+
input.runId,
|
|
1223
|
+
Cancellation.requestedEventType,
|
|
1224
|
+
json({
|
|
1225
|
+
runId: input.runId,
|
|
1226
|
+
source: "control",
|
|
1227
|
+
principal,
|
|
1228
|
+
...input.reason === void 0 ? {} : { reason: input.reason }
|
|
1229
|
+
})
|
|
1230
|
+
);
|
|
1231
|
+
}
|
|
1232
|
+
return accepted(input.idempotencyKey, input.runId);
|
|
1233
|
+
}),
|
|
1234
|
+
false
|
|
1235
|
+
).pipe(
|
|
1236
|
+
import_effect.Effect.flatMap(
|
|
1237
|
+
(receipt) => import_effect.Effect.gen(function* () {
|
|
1238
|
+
if (receipt._tag !== "Accepted") return receipt;
|
|
1239
|
+
const settle = (effect) => transact(
|
|
1240
|
+
"cancel",
|
|
1241
|
+
import_effect.Effect.gen(function* () {
|
|
1242
|
+
const run2 = yield* effect;
|
|
1243
|
+
yield* emit(
|
|
1244
|
+
input.runId,
|
|
1245
|
+
`control.run.${run2.status}`,
|
|
1246
|
+
{
|
|
1247
|
+
runId: input.runId,
|
|
1248
|
+
status: run2.status,
|
|
1249
|
+
...ControlFacts.runFact(run2)
|
|
1250
|
+
}
|
|
1251
|
+
);
|
|
1252
|
+
return run2;
|
|
1253
|
+
})
|
|
1254
|
+
);
|
|
1255
|
+
const run = yield* runtime.interrupt(input.runId, settle).pipe(
|
|
1256
|
+
import_effect.Effect.catchTag("/control/ClaimLost", () => import_effect.Effect.gen(function* () {
|
|
1257
|
+
const current = yield* getRun(input.runId);
|
|
1258
|
+
if (terminal(current.status)) return current;
|
|
1259
|
+
if (live(current.status) && current.ownerId !== void 0) return void 0;
|
|
1260
|
+
return yield* runtime.resume(input.runId).pipe(
|
|
1261
|
+
import_effect.Effect.andThen(runtime.interrupt(input.runId, settle)),
|
|
1262
|
+
import_effect.Effect.catchTag("/control/ClaimLost", () => import_effect.Effect.succeed(void 0))
|
|
1263
|
+
);
|
|
1264
|
+
}))
|
|
1265
|
+
);
|
|
1266
|
+
return run === void 0 ? receipt : terminalOrAccepted(input.idempotencyKey, run);
|
|
1267
|
+
})
|
|
1268
|
+
),
|
|
1269
|
+
// Both rows, before the process that asked goes away. The engine row
|
|
1270
|
+
// carries the request the moment the mutation commits, but nothing
|
|
1271
|
+
// drives a parked run, so the row stayed `suspended` until some
|
|
1272
|
+
// later long-lived engine happened to sweep it: `gc` collected the
|
|
1273
|
+
// run in `control.db` and skipped it in `engine.db` for fifteen
|
|
1274
|
+
// seconds and six commands in the release validation.
|
|
1275
|
+
import_effect.Effect.tap(
|
|
1276
|
+
() => import_effect.Effect.gen(function* () {
|
|
1277
|
+
yield* executorSettleCancelledPark(input.runId);
|
|
1278
|
+
const current = yield* getRun(input.runId);
|
|
1279
|
+
if (terminal(current.status)) yield* reconcileTerminal(input.runId, current.status);
|
|
1280
|
+
})
|
|
1281
|
+
)
|
|
1282
|
+
))
|
|
1283
|
+
)
|
|
1284
|
+
)
|
|
1285
|
+
),
|
|
1286
|
+
resume: import_effect.Effect.fn("Control.resume")((input) => confined(input, runMutation(input))),
|
|
1287
|
+
list,
|
|
1288
|
+
watch
|
|
1289
|
+
};
|
|
1290
|
+
return import_Control.Control.of(service);
|
|
1291
|
+
})
|
|
1292
|
+
);
|
|
1293
|
+
//# sourceMappingURL=ControlLive.js.map
|