@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
package/src/Control.ts
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport-independent control-plane vtable.
|
|
3
|
+
*
|
|
4
|
+
* @since 0.1.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { NotificationError } from "@smthrs/notifications/NotificationQueue"
|
|
8
|
+
import { Context, Effect, Layer, Stream } from "effect"
|
|
9
|
+
import type {
|
|
10
|
+
AlreadyResolved,
|
|
11
|
+
ClaimLost,
|
|
12
|
+
CodeDrift,
|
|
13
|
+
ControlError,
|
|
14
|
+
EnvelopeMismatch,
|
|
15
|
+
FlowNotFound,
|
|
16
|
+
InvalidInput,
|
|
17
|
+
LaunchFailed,
|
|
18
|
+
NoMatchingWait,
|
|
19
|
+
PersistenceError,
|
|
20
|
+
PlanDenied,
|
|
21
|
+
PlanDigestMismatch,
|
|
22
|
+
PlanNotFound,
|
|
23
|
+
RunNotFound,
|
|
24
|
+
TransportError,
|
|
25
|
+
Unauthorized,
|
|
26
|
+
Unavailable
|
|
27
|
+
} from "./ControlError.ts"
|
|
28
|
+
import { Unavailable as UnavailableError } from "./ControlError.ts"
|
|
29
|
+
import type {
|
|
30
|
+
ApprovalPayload,
|
|
31
|
+
ControlEvent,
|
|
32
|
+
Envelope,
|
|
33
|
+
FlowId,
|
|
34
|
+
IdempotencyKey,
|
|
35
|
+
ListRequest,
|
|
36
|
+
ListResponse,
|
|
37
|
+
PlanCard,
|
|
38
|
+
Principal,
|
|
39
|
+
Receipt,
|
|
40
|
+
RunId,
|
|
41
|
+
RunInputSchema,
|
|
42
|
+
SignalInputSchema,
|
|
43
|
+
SteerInputSchema,
|
|
44
|
+
WatchFilter
|
|
45
|
+
} from "./ControlSchema.ts"
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Raw input submitted to planning. Decoding is owned by `ControlRuntime`.
|
|
49
|
+
*
|
|
50
|
+
* This local contract accepts `unknown` so the selected flow can decode its
|
|
51
|
+
* own input. `ControlSchema.PlanInputSchema` is the narrower JSON wire
|
|
52
|
+
* contract used by RPC clients.
|
|
53
|
+
*
|
|
54
|
+
* @category models
|
|
55
|
+
* @since 0.1.0
|
|
56
|
+
*/
|
|
57
|
+
export interface PlanInput {
|
|
58
|
+
readonly flowId: FlowId
|
|
59
|
+
readonly input: unknown
|
|
60
|
+
readonly idempotencyKey?: IdempotencyKey | undefined
|
|
61
|
+
/** Budget fields that replace the flow's declared ones on this plan's envelope. */
|
|
62
|
+
readonly budget?: Envelope["budget"] | undefined
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Starts an approved plan or joins/resumes an existing run.
|
|
67
|
+
*
|
|
68
|
+
* @category models
|
|
69
|
+
* @since 0.1.0
|
|
70
|
+
*/
|
|
71
|
+
export type RunInput = typeof RunInputSchema.Type & {
|
|
72
|
+
/** Trusted runtime caller reserves the execution identity; absent from public RPC. */
|
|
73
|
+
readonly reservedRunId?: RunId | undefined
|
|
74
|
+
/** Authenticated actor used only to scope durable idempotency. */
|
|
75
|
+
readonly principal?: Principal | undefined
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @category models
|
|
80
|
+
* @since 0.1.0
|
|
81
|
+
*/
|
|
82
|
+
export type { ApprovalTarget } from "./ControlSchema.ts"
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* A listing request, restricted to one reader's runs when `reader` is set.
|
|
86
|
+
*
|
|
87
|
+
* The server sets `reader` to the authenticated principal unless its
|
|
88
|
+
* `RunVisibility` lets that principal see every run; `ControlSchema.ListRequest`
|
|
89
|
+
* excludes it from the wire, so a remote client cannot widen or borrow a view.
|
|
90
|
+
* A restricted reader sees only the runs its principal launched, and the fires
|
|
91
|
+
* that started them; an in-process caller that names no reader sees every run.
|
|
92
|
+
*
|
|
93
|
+
* @category models
|
|
94
|
+
* @since 1.0.0
|
|
95
|
+
*/
|
|
96
|
+
export type ListInput = ListRequest & {
|
|
97
|
+
readonly reader?: Principal | undefined
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* A watch filter, restricted to one reader's runs when `reader` is set.
|
|
102
|
+
*
|
|
103
|
+
* A restricted reader receives the events of the runs its principal launched
|
|
104
|
+
* and nothing from a plan or from a run it did not launch. See {@link ListInput}.
|
|
105
|
+
*
|
|
106
|
+
* @category models
|
|
107
|
+
* @since 1.0.0
|
|
108
|
+
*/
|
|
109
|
+
export type WatchInput = WatchFilter & {
|
|
110
|
+
readonly reader?: Principal | undefined
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Full approval decision submitted to the authenticated server boundary.
|
|
115
|
+
*
|
|
116
|
+
* The local service may receive a runtime-stamped principal.
|
|
117
|
+
* `ControlSchema.ApprovalInputSchema` excludes it from the wire so a remote
|
|
118
|
+
* client cannot claim another identity.
|
|
119
|
+
*
|
|
120
|
+
* @category models
|
|
121
|
+
* @since 0.1.0
|
|
122
|
+
*/
|
|
123
|
+
export interface ApprovalInput extends ApprovalPayload {
|
|
124
|
+
readonly principal?: Principal | undefined
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Steering mutation arguments.
|
|
129
|
+
*
|
|
130
|
+
* @category models
|
|
131
|
+
* @since 0.1.0
|
|
132
|
+
*/
|
|
133
|
+
export type SteerInput = typeof SteerInputSchema.Type & {
|
|
134
|
+
/**
|
|
135
|
+
* Set by the server to the authenticated principal unless its
|
|
136
|
+
* `RunVisibility` lets that principal see every run. A run the reader may
|
|
137
|
+
* not see fails `RunNotFound`, exactly as a missing run does. Not on the
|
|
138
|
+
* wire; an in-process caller that names no reader mutates any run.
|
|
139
|
+
*/
|
|
140
|
+
readonly reader?: Principal | undefined
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Signal mutation arguments.
|
|
145
|
+
*
|
|
146
|
+
* @category models
|
|
147
|
+
* @since 0.1.0
|
|
148
|
+
*/
|
|
149
|
+
export type SignalInput = typeof SignalInputSchema.Type & {
|
|
150
|
+
/** Authenticated actor used only to scope durable idempotency. */
|
|
151
|
+
readonly principal?: Principal | undefined
|
|
152
|
+
/**
|
|
153
|
+
* Set by the server to the authenticated principal unless its
|
|
154
|
+
* `RunVisibility` lets that principal see every run. A run the reader may
|
|
155
|
+
* not see fails `RunNotFound`, exactly as a missing run does. Not on the
|
|
156
|
+
* wire; an in-process caller that names no reader mutates any run.
|
|
157
|
+
*/
|
|
158
|
+
readonly reader?: Principal | undefined
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Run lifecycle mutation arguments.
|
|
163
|
+
*
|
|
164
|
+
* The local contract includes the runtime-stamped principal.
|
|
165
|
+
* `ControlSchema.ReasonedMutationInputSchema` carries only the caller's reason
|
|
166
|
+
* over RPC.
|
|
167
|
+
*
|
|
168
|
+
* @category models
|
|
169
|
+
* @since 0.1.0
|
|
170
|
+
*/
|
|
171
|
+
export interface RunMutationInput {
|
|
172
|
+
readonly runId: RunId
|
|
173
|
+
readonly idempotencyKey: IdempotencyKey
|
|
174
|
+
/**
|
|
175
|
+
* Why the run is being cancelled or resumed.
|
|
176
|
+
*
|
|
177
|
+
* Free text, recorded on the journal entry the mutation writes and projected
|
|
178
|
+
* back onto `RunSummary.cancellation`. An operator reading a cancelled run a
|
|
179
|
+
* week later asks "why", and a control plane that never carried the answer
|
|
180
|
+
* cannot produce one afterwards.
|
|
181
|
+
*/
|
|
182
|
+
readonly reason?: string | undefined
|
|
183
|
+
/**
|
|
184
|
+
* Who is asking. Stamped by the runtime, which supplies its own principal
|
|
185
|
+
* when the caller names none.
|
|
186
|
+
*/
|
|
187
|
+
readonly principal?: Principal | undefined
|
|
188
|
+
/**
|
|
189
|
+
* Resume only: run the flow's current code even when its execution digest
|
|
190
|
+
* differs from the one the run started with. Without it such a resume fails
|
|
191
|
+
* `CodeDrift` and leaves the run where it was.
|
|
192
|
+
*/
|
|
193
|
+
readonly allowCodeDrift?: boolean | undefined
|
|
194
|
+
/**
|
|
195
|
+
* Set by the server to the authenticated principal unless its
|
|
196
|
+
* `RunVisibility` lets that principal see every run. A run the reader may
|
|
197
|
+
* not see fails `RunNotFound`, exactly as a missing run does. Not on the
|
|
198
|
+
* wire; an in-process caller that names no reader mutates any run.
|
|
199
|
+
*/
|
|
200
|
+
readonly reader?: Principal | undefined
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Transport-independent control operations.
|
|
205
|
+
*
|
|
206
|
+
* Every operation admits transport and authentication failures so the same
|
|
207
|
+
* service key can be provided by either a local implementation or an RPC client.
|
|
208
|
+
*
|
|
209
|
+
* @category models
|
|
210
|
+
* @since 0.1.0
|
|
211
|
+
*/
|
|
212
|
+
export interface Service {
|
|
213
|
+
readonly plan: (
|
|
214
|
+
input: PlanInput
|
|
215
|
+
) => Effect.Effect<
|
|
216
|
+
PlanCard,
|
|
217
|
+
FlowNotFound | InvalidInput | PersistenceError | Unavailable | TransportError | Unauthorized
|
|
218
|
+
>
|
|
219
|
+
readonly run: (
|
|
220
|
+
input: RunInput
|
|
221
|
+
) => Effect.Effect<
|
|
222
|
+
Receipt,
|
|
223
|
+
| RunNotFound
|
|
224
|
+
| PlanNotFound
|
|
225
|
+
| PlanDenied
|
|
226
|
+
| PlanDigestMismatch
|
|
227
|
+
| EnvelopeMismatch
|
|
228
|
+
| ClaimLost
|
|
229
|
+
| CodeDrift
|
|
230
|
+
| InvalidInput
|
|
231
|
+
| LaunchFailed
|
|
232
|
+
| PersistenceError
|
|
233
|
+
| Unavailable
|
|
234
|
+
| TransportError
|
|
235
|
+
| Unauthorized
|
|
236
|
+
>
|
|
237
|
+
readonly approve: (
|
|
238
|
+
input: ApprovalInput
|
|
239
|
+
) => Effect.Effect<
|
|
240
|
+
Receipt,
|
|
241
|
+
| PlanDigestMismatch
|
|
242
|
+
| EnvelopeMismatch
|
|
243
|
+
| AlreadyResolved
|
|
244
|
+
| PlanNotFound
|
|
245
|
+
| RunNotFound
|
|
246
|
+
| CodeDrift
|
|
247
|
+
| InvalidInput
|
|
248
|
+
| Unauthorized
|
|
249
|
+
| PersistenceError
|
|
250
|
+
| Unavailable
|
|
251
|
+
| TransportError
|
|
252
|
+
>
|
|
253
|
+
readonly deny: (
|
|
254
|
+
input: ApprovalInput
|
|
255
|
+
) => Effect.Effect<
|
|
256
|
+
Receipt,
|
|
257
|
+
| PlanDigestMismatch
|
|
258
|
+
| EnvelopeMismatch
|
|
259
|
+
| AlreadyResolved
|
|
260
|
+
| PlanNotFound
|
|
261
|
+
| RunNotFound
|
|
262
|
+
| CodeDrift
|
|
263
|
+
| InvalidInput
|
|
264
|
+
| Unauthorized
|
|
265
|
+
| PersistenceError
|
|
266
|
+
| Unavailable
|
|
267
|
+
| TransportError
|
|
268
|
+
>
|
|
269
|
+
readonly steer: (
|
|
270
|
+
input: SteerInput
|
|
271
|
+
) => Effect.Effect<
|
|
272
|
+
Receipt,
|
|
273
|
+
RunNotFound | InvalidInput | PersistenceError | Unavailable | TransportError | Unauthorized | NotificationError
|
|
274
|
+
>
|
|
275
|
+
readonly signal: (
|
|
276
|
+
input: SignalInput
|
|
277
|
+
) => Effect.Effect<
|
|
278
|
+
Receipt,
|
|
279
|
+
RunNotFound | NoMatchingWait | InvalidInput | PersistenceError | Unavailable | TransportError | Unauthorized
|
|
280
|
+
>
|
|
281
|
+
readonly cancel: (
|
|
282
|
+
input: RunMutationInput
|
|
283
|
+
) => Effect.Effect<
|
|
284
|
+
Receipt,
|
|
285
|
+
RunNotFound | ClaimLost | InvalidInput | PersistenceError | Unavailable | TransportError | Unauthorized
|
|
286
|
+
>
|
|
287
|
+
readonly resume: (
|
|
288
|
+
input: RunMutationInput
|
|
289
|
+
) => Effect.Effect<
|
|
290
|
+
Receipt,
|
|
291
|
+
RunNotFound | ClaimLost | CodeDrift | InvalidInput | PersistenceError | Unavailable | TransportError | Unauthorized
|
|
292
|
+
>
|
|
293
|
+
readonly list: (input: ListInput) => Effect.Effect<ListResponse, ControlError>
|
|
294
|
+
/** Checkpoint `event.cursor` after processing each event and resume with `afterCursor`. */
|
|
295
|
+
readonly watch: (filter: WatchInput) => Stream.Stream<ControlEvent, ControlError>
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Service key for the authoritative control-plane vtable.
|
|
300
|
+
*
|
|
301
|
+
* @category services
|
|
302
|
+
* @since 0.1.0
|
|
303
|
+
*/
|
|
304
|
+
export class Control extends Context.Service<Control, Service>()("/control/Control") {}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Constructs a control service from an implementation record.
|
|
308
|
+
*
|
|
309
|
+
* @category constructors
|
|
310
|
+
* @since 0.1.0
|
|
311
|
+
*/
|
|
312
|
+
export const make = (implementation: Service): Service => Control.of(implementation)
|
|
313
|
+
|
|
314
|
+
const unavailable = (feature: string): Unavailable =>
|
|
315
|
+
new UnavailableError({ feature, ticket: "control-runtime-engine-integration" })
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Provides an unavailable control implementation for optional integrations.
|
|
319
|
+
*
|
|
320
|
+
* @category layers
|
|
321
|
+
* @since 0.1.0
|
|
322
|
+
*/
|
|
323
|
+
export const layerNoop: Layer.Layer<Control> = Layer.succeed(
|
|
324
|
+
Control,
|
|
325
|
+
make({
|
|
326
|
+
plan: Effect.fn("Control.plan")(() => Effect.fail(unavailable("plan"))),
|
|
327
|
+
run: Effect.fn("Control.run")(() => Effect.fail(unavailable("run"))),
|
|
328
|
+
approve: Effect.fn("Control.approve")(() => Effect.fail(unavailable("approve"))),
|
|
329
|
+
deny: Effect.fn("Control.deny")(() => Effect.fail(unavailable("deny"))),
|
|
330
|
+
steer: Effect.fn("Control.steer")(() => Effect.fail(unavailable("steer"))),
|
|
331
|
+
signal: Effect.fn("Control.signal")(() => Effect.fail(unavailable("signal"))),
|
|
332
|
+
cancel: Effect.fn("Control.cancel")(() => Effect.fail(unavailable("cancel"))),
|
|
333
|
+
resume: Effect.fn("Control.resume")(() => Effect.fail(unavailable("resume"))),
|
|
334
|
+
list: Effect.fn("Control.list")(() => Effect.fail(unavailable("list"))),
|
|
335
|
+
watch: () => Stream.fail(unavailable("watch"))
|
|
336
|
+
})
|
|
337
|
+
)
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RPC client layer projected back into the control service interface.
|
|
3
|
+
*
|
|
4
|
+
* @since 0.1.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Cause, Effect, Layer, Result, Schema, Stream } from "effect"
|
|
8
|
+
import { HttpClient, HttpClientRequest } from "effect/unstable/http"
|
|
9
|
+
import { RpcClient, RpcClientError } from "effect/unstable/rpc"
|
|
10
|
+
import { Control, make } from "./Control.ts"
|
|
11
|
+
import { type ControlError, ControlErrorSchema, TransportError, Unauthorized } from "./ControlError.ts"
|
|
12
|
+
import { ControlRpcs } from "./ControlRpcs.ts"
|
|
13
|
+
import {
|
|
14
|
+
ApprovalInputSchema,
|
|
15
|
+
CancelInputSchema,
|
|
16
|
+
ListRequest,
|
|
17
|
+
PlanInputSchema,
|
|
18
|
+
ResumeInputSchema,
|
|
19
|
+
RunInputSchema,
|
|
20
|
+
SignalInputSchema,
|
|
21
|
+
SteerInputSchema,
|
|
22
|
+
WatchFilter
|
|
23
|
+
} from "./ControlSchema.ts"
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Whether a value is one of the control plane's declared failures, as opposed
|
|
27
|
+
* to a defect that escaped some other layer.
|
|
28
|
+
*
|
|
29
|
+
* Derived from `ControlError.ControlErrorSchema` rather than restated, so a new
|
|
30
|
+
* error class cannot be a member of the union and a stranger to the client.
|
|
31
|
+
*
|
|
32
|
+
* @category refinements
|
|
33
|
+
* @since 0.1.0
|
|
34
|
+
*/
|
|
35
|
+
export const isControlError = Schema.is(ControlErrorSchema)
|
|
36
|
+
|
|
37
|
+
interface TransportClassification {
|
|
38
|
+
readonly message: string
|
|
39
|
+
readonly retryable: boolean
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const requestEncoding: TransportClassification = {
|
|
43
|
+
message: "The control request could not be encoded.",
|
|
44
|
+
retryable: false
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const responseDecoding: TransportClassification = {
|
|
48
|
+
message: "The control response could not be decoded.",
|
|
49
|
+
retryable: false
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const connectionFailure: TransportClassification = {
|
|
53
|
+
message: "The control server could not be reached.",
|
|
54
|
+
retryable: true
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const clientHttpFailure: TransportClassification = {
|
|
58
|
+
message: "The control server rejected the HTTP request.",
|
|
59
|
+
retryable: false
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const serverHttpFailure: TransportClassification = {
|
|
63
|
+
message: "The control server failed while handling the HTTP request.",
|
|
64
|
+
retryable: true
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const invalidClientUrl: TransportClassification = {
|
|
68
|
+
message: "The control server URL is invalid.",
|
|
69
|
+
retryable: false
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const unknownClientFailure: TransportClassification = {
|
|
73
|
+
message: "The control RPC client failed.",
|
|
74
|
+
retryable: false
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const transportError = (cause: unknown, classification: TransportClassification): TransportError =>
|
|
78
|
+
new TransportError({
|
|
79
|
+
...classification,
|
|
80
|
+
cause
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
// A `StatusCodeError` reported by the RPC client always carries the concrete
|
|
84
|
+
// error, and that error always carries its response. The three refusals below
|
|
85
|
+
// are what keeps a hand-built wrapper without one from being read as a 5xx and
|
|
86
|
+
// retried forever; none of them is reachable from the client's own transports.
|
|
87
|
+
const statusFrom = (cause: unknown): number | undefined => {
|
|
88
|
+
/* v8 ignore next -- a StatusCodeError cause always carries its response */
|
|
89
|
+
if (typeof cause !== "object" || cause === null || !("response" in cause)) return undefined
|
|
90
|
+
const response = cause.response
|
|
91
|
+
/* v8 ignore next -- as above: the response is an object carrying a status */
|
|
92
|
+
if (typeof response !== "object" || response === null || !("status" in response)) return undefined
|
|
93
|
+
/* v8 ignore next -- as above: the status is a number */
|
|
94
|
+
return typeof response.status === "number" ? response.status : undefined
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
type HttpFailureKind = Extract<RpcClientError.RpcClientError["reason"], { readonly _tag: "HttpError" }>["kind"]
|
|
98
|
+
type TransportReason = RpcClientError.RpcClientError["reason"]["_tag"]
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* What each HTTP failure means for a retry.
|
|
102
|
+
*
|
|
103
|
+
* `StatusCodeError` is absent on purpose: it is the one kind whose answer
|
|
104
|
+
* depends on the response, not on the kind.
|
|
105
|
+
*/
|
|
106
|
+
const httpClassification: Record<Exclude<HttpFailureKind, "StatusCodeError">, TransportClassification> = {
|
|
107
|
+
TransportError: connectionFailure,
|
|
108
|
+
EncodeError: requestEncoding,
|
|
109
|
+
InvalidUrlError: invalidClientUrl,
|
|
110
|
+
DecodeError: responseDecoding,
|
|
111
|
+
EmptyBodyError: responseDecoding
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* What each non-HTTP transport failure means for a retry.
|
|
116
|
+
*
|
|
117
|
+
* A table rather than a switch, because the mapping is data: the compiler
|
|
118
|
+
* still requires every reason the RPC client can report to have an answer, and
|
|
119
|
+
* a reader can check the whole policy at a glance.
|
|
120
|
+
*/
|
|
121
|
+
const reasonClassification: Record<Exclude<TransportReason, "HttpError">, TransportClassification> = {
|
|
122
|
+
RpcClientDefect: responseDecoding,
|
|
123
|
+
SocketReadError: connectionFailure,
|
|
124
|
+
SocketWriteError: connectionFailure,
|
|
125
|
+
SocketOpenError: connectionFailure,
|
|
126
|
+
// The socket cannot perform a requested protocol upgrade; retries of the
|
|
127
|
+
// same unsupported operation cannot change that host capability.
|
|
128
|
+
SocketUpgradeError: unknownClientFailure,
|
|
129
|
+
SocketCloseError: connectionFailure,
|
|
130
|
+
// A worker transport is not composable with this client, which speaks HTTP
|
|
131
|
+
// and WebSocket. The entries exist so the table stays exhaustive.
|
|
132
|
+
WorkerSpawnError: unknownClientFailure,
|
|
133
|
+
WorkerSendError: unknownClientFailure,
|
|
134
|
+
WorkerReceiveError: unknownClientFailure,
|
|
135
|
+
WorkerUnknownError: unknownClientFailure
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const classifyRpcClientError = (error: RpcClientError.RpcClientError): TransportClassification => {
|
|
139
|
+
const reason = error.reason
|
|
140
|
+
if (reason._tag !== "HttpError") return reasonClassification[reason._tag]
|
|
141
|
+
if (reason.kind !== "StatusCodeError") return httpClassification[reason.kind]
|
|
142
|
+
// Effect's serializable HttpError keeps the concrete StatusCodeError in
|
|
143
|
+
// `cause`. The wrapper has no separate status field, so a hand-built wrapper
|
|
144
|
+
// without that cause cannot safely be classified as retryable and falls back
|
|
145
|
+
// to the client class.
|
|
146
|
+
const status = statusFrom(reason.cause)
|
|
147
|
+
return status !== undefined && status >= 500 && status <= 599
|
|
148
|
+
? serverHttpFailure
|
|
149
|
+
: clientHttpFailure
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const isRpcClientError = Schema.is(RpcClientError.RpcClientError)
|
|
153
|
+
|
|
154
|
+
const classify = (error: unknown): TransportClassification => {
|
|
155
|
+
if (Schema.isSchemaError(error)) return responseDecoding
|
|
156
|
+
if (isRpcClientError(error)) return classifyRpcClientError(error)
|
|
157
|
+
return unknownClientFailure
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Preserve the operation-specific control failures from the RPC schema while
|
|
161
|
+
// replacing transport and decoding failures with the public TransportError.
|
|
162
|
+
const normalizedFailure = <E>(cause: Cause.Cause<E>): Effect.Effect<never, (E & ControlError) | TransportError> => {
|
|
163
|
+
const failure = Cause.findError(cause)
|
|
164
|
+
if (Result.isSuccess(failure)) {
|
|
165
|
+
return Effect.fail(
|
|
166
|
+
isControlError(failure.success)
|
|
167
|
+
? failure.success
|
|
168
|
+
: transportError(failure.success, classify(failure.success))
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
const defect = Cause.findDefect(cause)
|
|
172
|
+
// A cause with neither a failure nor a defect is an interruption, and an
|
|
173
|
+
// operator's own cancellation is not the server failing. It is re-raised
|
|
174
|
+
// exactly as it arrived rather than described as a transport failure.
|
|
175
|
+
/* v8 ignore next 2 -- the fiber propagates an interrupt-only cause without reaching this operator */
|
|
176
|
+
return Result.isSuccess(defect)
|
|
177
|
+
? Effect.fail(transportError(defect.success, classify(defect.success)))
|
|
178
|
+
: Effect.failCause(cause as Cause.Cause<(E & ControlError) | TransportError>)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const normalize = <A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, (E & ControlError) | TransportError, R> =>
|
|
182
|
+
Effect.catchCause(effect, normalizedFailure)
|
|
183
|
+
|
|
184
|
+
type RequestEncoder = (input: unknown) => Effect.Effect<unknown, Schema.SchemaError>
|
|
185
|
+
|
|
186
|
+
const encoder = (schema: Schema.Top): RequestEncoder =>
|
|
187
|
+
// Every request schema in this module is service-free. `Schema.Top` erases
|
|
188
|
+
// that fact to `unknown`, so restore it at this one construction boundary.
|
|
189
|
+
Schema.encodeUnknownEffect(Schema.toCodecJson(schema)) as RequestEncoder
|
|
190
|
+
|
|
191
|
+
const planEncoder = encoder(PlanInputSchema)
|
|
192
|
+
const runEncoder = encoder(RunInputSchema)
|
|
193
|
+
const approvalEncoder = encoder(ApprovalInputSchema)
|
|
194
|
+
const steerEncoder = encoder(SteerInputSchema)
|
|
195
|
+
const signalEncoder = encoder(SignalInputSchema)
|
|
196
|
+
const cancelEncoder = encoder(CancelInputSchema)
|
|
197
|
+
const resumeEncoder = encoder(ResumeInputSchema)
|
|
198
|
+
const listEncoder = encoder(ListRequest)
|
|
199
|
+
const watchEncoder = encoder(WatchFilter)
|
|
200
|
+
|
|
201
|
+
const normalizeRequest = <A, E, R>(
|
|
202
|
+
encode: RequestEncoder,
|
|
203
|
+
input: unknown,
|
|
204
|
+
request: () => Effect.Effect<A, E, R>
|
|
205
|
+
): Effect.Effect<A, (E & ControlError) | TransportError, R> =>
|
|
206
|
+
encode(input).pipe(
|
|
207
|
+
Effect.mapError((cause) => transportError(cause, requestEncoding)),
|
|
208
|
+
Effect.andThen(Effect.suspend(() => normalize(request())))
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
const normalizeStream = <A, E, R>(
|
|
212
|
+
stream: Stream.Stream<A, E, R>
|
|
213
|
+
): Stream.Stream<A, (E & ControlError) | TransportError, R> =>
|
|
214
|
+
Stream.catchCause(stream, (cause) => Stream.fromEffect(normalizedFailure(cause)))
|
|
215
|
+
|
|
216
|
+
const normalizeStreamRequest = <A, E, R>(
|
|
217
|
+
encode: RequestEncoder,
|
|
218
|
+
input: unknown,
|
|
219
|
+
request: () => Stream.Stream<A, E, R>
|
|
220
|
+
): Stream.Stream<A, (E & ControlError) | TransportError, R> =>
|
|
221
|
+
Stream.unwrap(
|
|
222
|
+
encode(input).pipe(
|
|
223
|
+
Effect.mapError((cause) => transportError(cause, requestEncoding)),
|
|
224
|
+
Effect.map(() => normalizeStream(request()))
|
|
225
|
+
)
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Client transport configuration. Unary procedures use HTTP at this URL;
|
|
230
|
+
* `watch` uses the abstract WebSocket supplied by the platform layer.
|
|
231
|
+
*
|
|
232
|
+
* `credential` reaches the HTTP protocol only. It is attached as a bearer
|
|
233
|
+
* token on each HTTP RPC request; the `watch` stream rides a WebSocket whose
|
|
234
|
+
* upgrade request a browser cannot add headers to, so the socket carries no
|
|
235
|
+
* credential at all in a browser deployment. A deployment that authenticates
|
|
236
|
+
* control traffic must therefore front the socket (terminate auth at a proxy
|
|
237
|
+
* that injects the credential on the upgrade) or serve `watch` over the HTTP
|
|
238
|
+
* protocol instead.
|
|
239
|
+
*
|
|
240
|
+
* @category models
|
|
241
|
+
* @since 0.1.0
|
|
242
|
+
*/
|
|
243
|
+
export interface ClientConfig {
|
|
244
|
+
readonly url: string
|
|
245
|
+
/**
|
|
246
|
+
* Bearer token attached to every HTTP RPC request when present.
|
|
247
|
+
*
|
|
248
|
+
* Not sent on the `watch` WebSocket: see {@link ClientConfig}.
|
|
249
|
+
*/
|
|
250
|
+
readonly credential?: string | undefined
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Provides `Control` through an RPC client while preserving the local vtable.
|
|
255
|
+
*
|
|
256
|
+
* @category layers
|
|
257
|
+
* @since 0.1.0
|
|
258
|
+
*/
|
|
259
|
+
export const layer = (config: ClientConfig) => {
|
|
260
|
+
const http = RpcClient.layerProtocolHttp({
|
|
261
|
+
url: config.url,
|
|
262
|
+
transformClient: (client) => {
|
|
263
|
+
// The RPC protocol already admits HttpClientError, but transformClient's
|
|
264
|
+
// generic signature cannot express that filtering adds the same error
|
|
265
|
+
// its input client already carries.
|
|
266
|
+
const checked = HttpClient.filterStatusOk(client) as typeof client
|
|
267
|
+
return config.credential === undefined
|
|
268
|
+
? checked
|
|
269
|
+
: HttpClient.mapRequest(checked, HttpClientRequest.bearerToken(config.credential))
|
|
270
|
+
}
|
|
271
|
+
})
|
|
272
|
+
const websocket = RpcClient.layerProtocolSocket()
|
|
273
|
+
return Layer.effect(
|
|
274
|
+
Control,
|
|
275
|
+
Effect.gen(function*() {
|
|
276
|
+
// Built into the layer's own scope: `Effect.provide(layer)` would close
|
|
277
|
+
// each protocol as soon as the client was constructed.
|
|
278
|
+
const httpServices = yield* Layer.build(http)
|
|
279
|
+
const websocketServices = yield* Layer.build(websocket)
|
|
280
|
+
const unary = yield* RpcClient.make(ControlRpcs).pipe(Effect.provide(httpServices))
|
|
281
|
+
const streaming = yield* RpcClient.make(ControlRpcs).pipe(Effect.provide(websocketServices))
|
|
282
|
+
return make({
|
|
283
|
+
plan: Effect.fn("Control.plan")((input) => normalizeRequest(planEncoder, input, () => unary.Plan(input))),
|
|
284
|
+
run: Effect.fn("Control.run")((input) => normalizeRequest(runEncoder, input, () => unary.Run(input))),
|
|
285
|
+
// RPC authenticates with this client's credential and does not carry
|
|
286
|
+
// local attribution. Refuse here before an agent becomes that operator.
|
|
287
|
+
approve: Effect.fn("Control.approve")((input) =>
|
|
288
|
+
normalizeRequest(approvalEncoder, input, () =>
|
|
289
|
+
input.principal?.kind === "agent"
|
|
290
|
+
? Effect.fail(new Unauthorized({ message: "Approval decisions are operator-only" }))
|
|
291
|
+
: unary.Approve(input))
|
|
292
|
+
),
|
|
293
|
+
deny: Effect.fn("Control.deny")((input) =>
|
|
294
|
+
normalizeRequest(approvalEncoder, input, () =>
|
|
295
|
+
input.principal?.kind === "agent"
|
|
296
|
+
? Effect.fail(new Unauthorized({ message: "Approval decisions are operator-only" }))
|
|
297
|
+
: unary.Deny(input))
|
|
298
|
+
),
|
|
299
|
+
steer: Effect.fn("Control.steer")((input) => normalizeRequest(steerEncoder, input, () => unary.Steer(input))),
|
|
300
|
+
signal: Effect.fn("Control.signal")((input) =>
|
|
301
|
+
normalizeRequest(signalEncoder, input, () => unary.Signal(input))
|
|
302
|
+
),
|
|
303
|
+
cancel: Effect.fn("Control.cancel")((input) =>
|
|
304
|
+
normalizeRequest(cancelEncoder, input, () => unary.Cancel(input))
|
|
305
|
+
),
|
|
306
|
+
resume: Effect.fn("Control.resume")((input) =>
|
|
307
|
+
normalizeRequest(resumeEncoder, input, () => unary.Resume(input))
|
|
308
|
+
),
|
|
309
|
+
list: Effect.fn("Control.list")((input) => normalizeRequest(listEncoder, input, () => unary.List(input))),
|
|
310
|
+
// `watch` is the only procedure on the socket protocol, and the
|
|
311
|
+
// socket is why `credential` cannot protect it: a browser's WebSocket
|
|
312
|
+
// upgrade takes no headers, so the bearer token that authenticates
|
|
313
|
+
// every unary call above never reaches this stream. Credentialed
|
|
314
|
+
// deployments front the socket or use the HTTP protocol for watch.
|
|
315
|
+
watch: (input) => normalizeStreamRequest(watchEncoder, input, () => streaming.Watch(input))
|
|
316
|
+
})
|
|
317
|
+
})
|
|
318
|
+
)
|
|
319
|
+
}
|