@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,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan construction shared by every `ControlRuntime` implementation.
|
|
3
|
+
*
|
|
4
|
+
* The plan digest is what an approval is bound to, so the memory runtime and
|
|
5
|
+
* the durable runtime must compute it the same way or an approval taken on one
|
|
6
|
+
* would not validate on the other. Keeping the construction here is what makes
|
|
7
|
+
* that a compile-time fact rather than a convention.
|
|
8
|
+
*
|
|
9
|
+
* @since 0.1.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { canonicalize } from "@smthrs/canonical"
|
|
13
|
+
import { Sha256 } from "@smthrs/crypto"
|
|
14
|
+
import * as Sha256Digest from "@smthrs/crypto/Sha256"
|
|
15
|
+
import type * as PersistedPlan from "@smthrs/plan/Plan"
|
|
16
|
+
import { Effect, Schema } from "effect"
|
|
17
|
+
import type { ApprovalTarget, PlanInput } from "../Control.ts"
|
|
18
|
+
import { CodeDrift } from "../ControlError.ts"
|
|
19
|
+
import type {
|
|
20
|
+
Envelope,
|
|
21
|
+
FlowId,
|
|
22
|
+
IdempotencyKey,
|
|
23
|
+
PlanCard,
|
|
24
|
+
PlanGraph,
|
|
25
|
+
PlanNode,
|
|
26
|
+
Principal,
|
|
27
|
+
Receipt,
|
|
28
|
+
RunId,
|
|
29
|
+
RunSummary
|
|
30
|
+
} from "../ControlSchema.ts"
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The drift between the code a run recorded and the code that would resume
|
|
34
|
+
* it, or `undefined` when there is none. A run that recorded no digest (a flow
|
|
35
|
+
* without one, or a row older than the field) is not checked for flow drift,
|
|
36
|
+
* and one that recorded no engine version is not checked for engine drift.
|
|
37
|
+
*
|
|
38
|
+
* @since 1.0.0
|
|
39
|
+
* @private
|
|
40
|
+
*/
|
|
41
|
+
export const codeDriftOf = (
|
|
42
|
+
run: RunSummary,
|
|
43
|
+
flow: { readonly executionDigest?: string | undefined } | undefined,
|
|
44
|
+
engineVersion: string | undefined
|
|
45
|
+
): CodeDrift | undefined => {
|
|
46
|
+
const flowDrift = run.executionDigest !== undefined && flow?.executionDigest !== run.executionDigest
|
|
47
|
+
const engineDrift = run.engineVersion !== undefined && engineVersion !== undefined &&
|
|
48
|
+
engineVersion !== run.engineVersion
|
|
49
|
+
if (!flowDrift && !engineDrift) return undefined
|
|
50
|
+
return new CodeDrift({
|
|
51
|
+
runId: run.runId,
|
|
52
|
+
flowId: run.flowId,
|
|
53
|
+
...(flowDrift ? { recorded: run.executionDigest } : {}),
|
|
54
|
+
...(flowDrift && flow?.executionDigest !== undefined ? { current: flow.executionDigest } : {}),
|
|
55
|
+
...(engineDrift ? { recordedEngine: run.engineVersion, currentEngine: engineVersion } : {})
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The code identity a resume the operator allowed to drift records on the run,
|
|
61
|
+
* so later checks compare against the code it now runs.
|
|
62
|
+
*
|
|
63
|
+
* @since 1.0.0
|
|
64
|
+
* @private
|
|
65
|
+
*/
|
|
66
|
+
export const adoptedCode = (
|
|
67
|
+
run: RunSummary,
|
|
68
|
+
flow: { readonly executionDigest?: string | undefined } | undefined,
|
|
69
|
+
engineVersion: string | undefined
|
|
70
|
+
): Pick<RunSummary, "executionDigest" | "engineVersion"> => ({
|
|
71
|
+
executionDigest: flow?.executionDigest,
|
|
72
|
+
engineVersion: engineVersion ?? run.engineVersion
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The envelope a flow with no declared capabilities carries.
|
|
77
|
+
*
|
|
78
|
+
* @since 0.1.0
|
|
79
|
+
* @private
|
|
80
|
+
*/
|
|
81
|
+
export const emptyEnvelope: Envelope = {
|
|
82
|
+
capabilities: [],
|
|
83
|
+
flows: [],
|
|
84
|
+
budget: {}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Canonical bytes for a value.
|
|
89
|
+
* Throws on a non-serializable value; every caller either wraps it in
|
|
90
|
+
* `Effect.try` or has already validated the value.
|
|
91
|
+
*
|
|
92
|
+
* @since 0.1.0
|
|
93
|
+
* @private
|
|
94
|
+
*/
|
|
95
|
+
export const canonical = (value: unknown): string => canonicalize(value)
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The durable key a caller's idempotency key is stored under.
|
|
99
|
+
*
|
|
100
|
+
* Namespaced by the effective actor: the submitted principal, or the runtime's
|
|
101
|
+
* configured fallback when the caller named none. The principal's clock is
|
|
102
|
+
* omitted, so an equal retry lands on the same key.
|
|
103
|
+
*
|
|
104
|
+
* @since 0.1.0
|
|
105
|
+
* @private
|
|
106
|
+
*/
|
|
107
|
+
export const mutationKey = (
|
|
108
|
+
operation: string,
|
|
109
|
+
key: IdempotencyKey,
|
|
110
|
+
principal: Pick<Principal, "id" | "kind">
|
|
111
|
+
): string =>
|
|
112
|
+
`${operation}:actor:${Sha256Digest.digestSync(canonical({ id: principal.id, kind: principal.kind }))}:${key}`
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* The content digest of a value's canonical bytes.
|
|
116
|
+
*
|
|
117
|
+
* @since 0.1.0
|
|
118
|
+
* @private
|
|
119
|
+
*/
|
|
120
|
+
export const digest = (value: unknown) => Schema.decodeUnknownEffect(Sha256)(canonical(value)).pipe(Effect.orDie)
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Envelope equality by canonical bytes, not by reference or key order.
|
|
124
|
+
*
|
|
125
|
+
* @since 0.1.0
|
|
126
|
+
* @private
|
|
127
|
+
*/
|
|
128
|
+
export const sameEnvelope = (left: Envelope, right: Envelope): boolean => canonical(left) === canonical(right)
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* A flow's envelope with a planner's budget fields laid over its declared ones.
|
|
132
|
+
*
|
|
133
|
+
* @since 1.0.0-rc.1
|
|
134
|
+
* @private
|
|
135
|
+
*/
|
|
136
|
+
export const budgeted = (envelope: Envelope, budget: Envelope["budget"] | undefined): Envelope =>
|
|
137
|
+
budget === undefined ? envelope : { ...envelope, budget: { ...envelope.budget, ...budget } }
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* What one plan request asks for, for idempotency: a key replayed with another
|
|
141
|
+
* budget is another plan.
|
|
142
|
+
*
|
|
143
|
+
* @since 1.0.0-rc.1
|
|
144
|
+
* @private
|
|
145
|
+
*/
|
|
146
|
+
export const planFingerprint = (input: PlanInput): string =>
|
|
147
|
+
canonical({
|
|
148
|
+
flowId: input.flowId,
|
|
149
|
+
input: input.input,
|
|
150
|
+
...(input.budget === undefined ? {} : { budget: input.budget })
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* An accepted receipt, carrying a run id when one exists.
|
|
155
|
+
*
|
|
156
|
+
* @since 0.1.0
|
|
157
|
+
* @private
|
|
158
|
+
*/
|
|
159
|
+
export const accepted = (receiptId: string, runId?: RunId): Receipt => {
|
|
160
|
+
/* v8 ignore next 3 -- both runtimes call this from `launch`, which has the run it just started; the parameter stays optional for the receipt's shape rather than for a caller */
|
|
161
|
+
return runId === undefined
|
|
162
|
+
? { _tag: "Accepted", receiptId }
|
|
163
|
+
: { _tag: "Accepted", receiptId, runId }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* The receipt a replayed mutation returns, derived from the recorded one.
|
|
168
|
+
*
|
|
169
|
+
* @since 0.1.0
|
|
170
|
+
* @private
|
|
171
|
+
*/
|
|
172
|
+
export const alreadyApplied = (key: IdempotencyKey, receipt: Receipt): Receipt => {
|
|
173
|
+
const receiptId = receipt._tag === "Accepted" || receipt._tag === "AlreadyApplied" ? receipt.receiptId : key
|
|
174
|
+
const runId = receipt._tag === "Accepted" || receipt._tag === "AlreadyApplied" || receipt._tag === "Terminal"
|
|
175
|
+
? receipt.runId
|
|
176
|
+
: undefined
|
|
177
|
+
const input = receipt._tag === "Accepted" || receipt._tag === "AlreadyApplied"
|
|
178
|
+
? {
|
|
179
|
+
...(receipt.inputConsumed === undefined ? {} : { inputConsumed: receipt.inputConsumed }),
|
|
180
|
+
...(receipt.inputBody === undefined ? {} : { inputBody: receipt.inputBody })
|
|
181
|
+
}
|
|
182
|
+
: {}
|
|
183
|
+
return runId === undefined
|
|
184
|
+
? { _tag: "AlreadyApplied", receiptId, ...input }
|
|
185
|
+
: { _tag: "AlreadyApplied", receiptId, runId, ...input }
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* The plan inputs a card is derived from.
|
|
190
|
+
*
|
|
191
|
+
* @since 0.1.0
|
|
192
|
+
* @private
|
|
193
|
+
*/
|
|
194
|
+
export interface PlanSource {
|
|
195
|
+
readonly planId: string
|
|
196
|
+
readonly flowId: FlowId
|
|
197
|
+
readonly decodedInput: unknown
|
|
198
|
+
readonly envelope: Envelope
|
|
199
|
+
readonly deployClass: boolean
|
|
200
|
+
readonly executionDigest?: string | undefined
|
|
201
|
+
/** The persisted plan value and cache verdicts produced by the host. */
|
|
202
|
+
readonly handoff?: {
|
|
203
|
+
readonly plan: PersistedPlan.Plan
|
|
204
|
+
readonly statuses?: Readonly<Record<string, PlanNode["status"]>> | undefined
|
|
205
|
+
readonly graph?: PlanGraph | undefined
|
|
206
|
+
} | undefined
|
|
207
|
+
readonly idempotencyKey?: IdempotencyKey | undefined
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Builds the immutable plan card and the approval target bound to its digest.
|
|
212
|
+
*
|
|
213
|
+
* @since 0.1.0
|
|
214
|
+
* @private
|
|
215
|
+
*/
|
|
216
|
+
export const planCard = (source: PlanSource) =>
|
|
217
|
+
Effect.gen(function*() {
|
|
218
|
+
const plan = source.handoff?.plan
|
|
219
|
+
const nodes: ReadonlyArray<PlanNode> = plan === undefined
|
|
220
|
+
? []
|
|
221
|
+
: plan.nodes.map((node) => ({
|
|
222
|
+
...node,
|
|
223
|
+
status: source.handoff?.statuses?.[node.id] ?? "run"
|
|
224
|
+
}))
|
|
225
|
+
const planDigest = yield* digest({
|
|
226
|
+
flowId: source.flowId,
|
|
227
|
+
input: source.decodedInput,
|
|
228
|
+
envelope: source.envelope,
|
|
229
|
+
deployClass: source.deployClass,
|
|
230
|
+
...(source.executionDigest === undefined ? {} : { executionDigest: source.executionDigest }),
|
|
231
|
+
// The persisted plan digest covers keys, edges, effects, conflicts,
|
|
232
|
+
// priorities, and generations. Hashing only node keys loses executable
|
|
233
|
+
// graph changes whose content keys legitimately stay stable.
|
|
234
|
+
persistedPlan: plan?.digest ?? null
|
|
235
|
+
})
|
|
236
|
+
const target: ApprovalTarget = {
|
|
237
|
+
_tag: "Plan",
|
|
238
|
+
planId: source.planId,
|
|
239
|
+
digest: planDigest,
|
|
240
|
+
envelope: source.envelope
|
|
241
|
+
}
|
|
242
|
+
return {
|
|
243
|
+
planId: source.planId,
|
|
244
|
+
flowId: source.flowId,
|
|
245
|
+
digest: planDigest,
|
|
246
|
+
inputSummary: canonical(source.decodedInput),
|
|
247
|
+
envelope: source.envelope,
|
|
248
|
+
deployClass: source.deployClass,
|
|
249
|
+
...(source.executionDigest === undefined ? {} : { executionDigest: source.executionDigest }),
|
|
250
|
+
...(plan === undefined ? {} : { plan }),
|
|
251
|
+
nodes,
|
|
252
|
+
// Outside `digest` above on purpose: the edges are how a reader draws
|
|
253
|
+
// the plan, not what the plan will do, so gaining them must not
|
|
254
|
+
// invalidate an approval taken before the host reported them.
|
|
255
|
+
...(source.handoff?.graph === undefined ? {} : { graph: source.handoff.graph }),
|
|
256
|
+
approval: {
|
|
257
|
+
target,
|
|
258
|
+
scope: "run" as const,
|
|
259
|
+
idempotencyKey: source.idempotencyKey ?? `approve:${source.planId}`
|
|
260
|
+
}
|
|
261
|
+
} satisfies PlanCard
|
|
262
|
+
})
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exact optional-schema failure classification across SQL driver wrappers.
|
|
3
|
+
* @since 1.0.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Driver messages stay separate so wrapper context cannot identify a different failure.
|
|
8
|
+
* The depth bound terminates cyclic driver causes.
|
|
9
|
+
* @since 1.0.0
|
|
10
|
+
* @private
|
|
11
|
+
*/
|
|
12
|
+
export const causeMessages = (value: unknown, depth = 4): ReadonlyArray<string> => {
|
|
13
|
+
if (typeof value === "string") return [value]
|
|
14
|
+
if (depth === 0 || typeof value !== "object" || value === null) return []
|
|
15
|
+
const fields = value as { readonly message?: unknown; readonly cause?: unknown; readonly reason?: unknown }
|
|
16
|
+
return [
|
|
17
|
+
...(typeof fields.message === "string" ? [fields.message] : []),
|
|
18
|
+
...causeMessages(fields.reason, depth - 1),
|
|
19
|
+
...causeMessages(fields.cause, depth - 1)
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Only the relation named by a driver's missing-table diagnostic may be absent.
|
|
25
|
+
* @since 1.0.0
|
|
26
|
+
* @private
|
|
27
|
+
*/
|
|
28
|
+
export const missingTable = (table: string) => (cause: unknown): boolean =>
|
|
29
|
+
causeMessages(cause).some((message) => {
|
|
30
|
+
const relation = /^no such table: (\S+)$/.exec(message)?.[1]
|
|
31
|
+
?? /^relation "([^"]+)" does not exist$/.exec(message)?.[1]
|
|
32
|
+
// These queries name unqualified relations. A qualified name can refer
|
|
33
|
+
// to a different schema's dependency, so do not discard its qualifier.
|
|
34
|
+
if (relation !== undefined) return relation === table
|
|
35
|
+
// MySQL always names the database in its table diagnostic.
|
|
36
|
+
return /^Table '[^.']+\.([^']+)' doesn't exist$/.exec(message)?.[1] === table
|
|
37
|
+
})
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Initial durable control-plane schema.
|
|
3
|
+
*
|
|
4
|
+
* @since 0.1.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as Dialect from "@smthrs/database/Dialect"
|
|
8
|
+
import * as Effect from "effect/Effect"
|
|
9
|
+
import * as SqlClient from "effect/unstable/sql/SqlClient"
|
|
10
|
+
|
|
11
|
+
const statements = (sql: SqlClient.SqlClient) =>
|
|
12
|
+
[
|
|
13
|
+
sql`CREATE TABLE IF NOT EXISTS control_plans (
|
|
14
|
+
${Dialect.rowId(sql)}
|
|
15
|
+
plan_id TEXT PRIMARY KEY,
|
|
16
|
+
card_json TEXT NOT NULL,
|
|
17
|
+
decoded_input_json TEXT NOT NULL,
|
|
18
|
+
decision TEXT NOT NULL
|
|
19
|
+
)`,
|
|
20
|
+
sql`CREATE TABLE IF NOT EXISTS control_plan_keys (
|
|
21
|
+
idempotency_key TEXT PRIMARY KEY,
|
|
22
|
+
fingerprint TEXT NOT NULL,
|
|
23
|
+
plan_id TEXT NOT NULL
|
|
24
|
+
)`,
|
|
25
|
+
sql`CREATE TABLE IF NOT EXISTS control_tokens (
|
|
26
|
+
target_tag TEXT NOT NULL,
|
|
27
|
+
run_id TEXT NOT NULL,
|
|
28
|
+
target_id TEXT NOT NULL,
|
|
29
|
+
token_id TEXT NOT NULL,
|
|
30
|
+
target_json TEXT NOT NULL,
|
|
31
|
+
resolved ${Dialect.integer(sql)} NOT NULL,
|
|
32
|
+
decision_principal_json TEXT,
|
|
33
|
+
PRIMARY KEY (target_tag, run_id, target_id),
|
|
34
|
+
CHECK (target_tag IN ('Plan', 'Node')),
|
|
35
|
+
CHECK ((target_tag = 'Plan' AND run_id = '') OR (target_tag = 'Node' AND length(run_id) > 0)),
|
|
36
|
+
CHECK (length(target_id) > 0 AND token_id = target_id)
|
|
37
|
+
)`,
|
|
38
|
+
sql`CREATE TABLE IF NOT EXISTS control_grants (
|
|
39
|
+
target_tag TEXT NOT NULL,
|
|
40
|
+
run_id TEXT NOT NULL,
|
|
41
|
+
target_id TEXT NOT NULL,
|
|
42
|
+
token_id TEXT NOT NULL,
|
|
43
|
+
envelope_json TEXT NOT NULL,
|
|
44
|
+
scope TEXT NOT NULL,
|
|
45
|
+
installed_at_ms ${Dialect.integer(sql)} NOT NULL,
|
|
46
|
+
PRIMARY KEY (target_tag, run_id, target_id),
|
|
47
|
+
CHECK (target_tag IN ('Plan', 'Node')),
|
|
48
|
+
CHECK ((target_tag = 'Plan' AND run_id = '') OR (target_tag = 'Node' AND length(run_id) > 0)),
|
|
49
|
+
CHECK (length(target_id) > 0 AND token_id = target_id)
|
|
50
|
+
)`,
|
|
51
|
+
sql`CREATE TABLE IF NOT EXISTS control_mutations (
|
|
52
|
+
mutation_key TEXT PRIMARY KEY,
|
|
53
|
+
fingerprint TEXT NOT NULL,
|
|
54
|
+
receipt_json TEXT NOT NULL
|
|
55
|
+
)`,
|
|
56
|
+
sql`CREATE TABLE IF NOT EXISTS control_runs (
|
|
57
|
+
run_id TEXT PRIMARY KEY,
|
|
58
|
+
created_seq ${Dialect.integer(sql)} NOT NULL
|
|
59
|
+
)`,
|
|
60
|
+
sql`CREATE TABLE IF NOT EXISTS control_run_resumes (
|
|
61
|
+
run_id TEXT PRIMARY KEY,
|
|
62
|
+
requested_seq ${Dialect.integer(sql)} NOT NULL,
|
|
63
|
+
requested_at_ms ${Dialect.integer(sql)} NOT NULL
|
|
64
|
+
)`,
|
|
65
|
+
sql`CREATE TABLE IF NOT EXISTS control_run_messages (
|
|
66
|
+
seq ${Dialect.identity(sql)},
|
|
67
|
+
run_id TEXT NOT NULL,
|
|
68
|
+
kind TEXT NOT NULL,
|
|
69
|
+
payload_json TEXT NOT NULL
|
|
70
|
+
)`,
|
|
71
|
+
sql`CREATE TABLE IF NOT EXISTS control_sequences (
|
|
72
|
+
name TEXT PRIMARY KEY,
|
|
73
|
+
value ${Dialect.integer(sql)} NOT NULL
|
|
74
|
+
)`,
|
|
75
|
+
sql`CREATE TABLE IF NOT EXISTS control_credentials (
|
|
76
|
+
id TEXT PRIMARY KEY,
|
|
77
|
+
name TEXT NOT NULL,
|
|
78
|
+
ciphertext TEXT NOT NULL,
|
|
79
|
+
nonce TEXT NOT NULL,
|
|
80
|
+
version ${Dialect.integer(sql)} NOT NULL,
|
|
81
|
+
updated_at_ms ${Dialect.integer(sql)} NOT NULL
|
|
82
|
+
)`
|
|
83
|
+
] as const
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Creates every table owned by `@smthrs/control`.
|
|
87
|
+
*
|
|
88
|
+
* `IF NOT EXISTS` lets the recorded migration and each standalone adapter's
|
|
89
|
+
* bootstrap share this exact schema without competing over table creation.
|
|
90
|
+
*
|
|
91
|
+
* @category migrations
|
|
92
|
+
* @since 0.1.0
|
|
93
|
+
*/
|
|
94
|
+
export const initial: Effect.Effect<void, unknown, SqlClient.SqlClient> = Effect.gen(function*() {
|
|
95
|
+
const sql = yield* SqlClient.SqlClient
|
|
96
|
+
for (const statement of statements(sql)) {
|
|
97
|
+
yield* statement
|
|
98
|
+
}
|
|
99
|
+
})
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable run idempotency claims, including upgrades of existing databases.
|
|
3
|
+
*
|
|
4
|
+
* @since 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as Effect from "effect/Effect"
|
|
8
|
+
import * as SqlClient from "effect/unstable/sql/SqlClient"
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Adds the run-key claim table without changing existing control data.
|
|
12
|
+
*
|
|
13
|
+
* @category migrations
|
|
14
|
+
* @since 1.0.0
|
|
15
|
+
*/
|
|
16
|
+
export const runKeys = Effect.gen(function*() {
|
|
17
|
+
const sql = yield* SqlClient.SqlClient
|
|
18
|
+
yield* sql`CREATE TABLE IF NOT EXISTS control_run_keys (
|
|
19
|
+
idempotency_key TEXT PRIMARY KEY,
|
|
20
|
+
fingerprint TEXT NOT NULL,
|
|
21
|
+
claimant TEXT NOT NULL
|
|
22
|
+
)`
|
|
23
|
+
})
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable signal admission and immutable wait bindings.
|
|
3
|
+
* @since 1.0.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as Dialect from "@smthrs/database/Dialect"
|
|
7
|
+
import * as Effect from "effect/Effect"
|
|
8
|
+
import * as SqlClient from "effect/unstable/sql/SqlClient"
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Adds the signal inbox without replaying legacy messages that lack delivery identity.
|
|
12
|
+
* @category migrations
|
|
13
|
+
* @since 1.0.0
|
|
14
|
+
*/
|
|
15
|
+
export const signalCommands = Effect.gen(function*() {
|
|
16
|
+
const sql = yield* SqlClient.SqlClient
|
|
17
|
+
yield* sql`CREATE TABLE IF NOT EXISTS control_signal_commands (
|
|
18
|
+
seq ${Dialect.identity(sql)},
|
|
19
|
+
command_id TEXT NOT NULL UNIQUE,
|
|
20
|
+
run_id TEXT NOT NULL,
|
|
21
|
+
payload_json TEXT NOT NULL,
|
|
22
|
+
wait_token TEXT,
|
|
23
|
+
state TEXT NOT NULL DEFAULT 'pending' CHECK (state IN ('pending', 'delivered', 'rejected', 'terminal'))
|
|
24
|
+
)`
|
|
25
|
+
yield* sql`CREATE UNIQUE INDEX IF NOT EXISTS control_signal_commands_wait ON control_signal_commands(wait_token) WHERE wait_token IS NOT NULL`
|
|
26
|
+
yield* sql`CREATE INDEX IF NOT EXISTS control_signal_commands_pending ON control_signal_commands(state, seq)`
|
|
27
|
+
})
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preserve the actual decision without guessing erased legacy answers.
|
|
3
|
+
* @since 1.0.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as Dialect from "@smthrs/database/Dialect"
|
|
7
|
+
import * as Effect from "effect/Effect"
|
|
8
|
+
import * as SqlClient from "effect/unstable/sql/SqlClient"
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Idempotent for both recorded migrations and standalone runtime bootstrap.
|
|
12
|
+
* @category migrations
|
|
13
|
+
* @since 1.0.0
|
|
14
|
+
*/
|
|
15
|
+
export const approvalDecisions = Effect.gen(function*() {
|
|
16
|
+
const sql = yield* SqlClient.SqlClient
|
|
17
|
+
yield* sql.withTransaction(Effect.gen(function*() {
|
|
18
|
+
const columns = yield* Dialect.columns(sql, "control_tokens")
|
|
19
|
+
if (columns.some((column) => column.name === "decision_json")) return
|
|
20
|
+
// NULL is intentional: old terminal tokens erased their answer. Their
|
|
21
|
+
// decoder refuses recovery instead of inferring approval from a grant.
|
|
22
|
+
// An undecided token still has its unambiguous resolved=0 representation.
|
|
23
|
+
yield* sql`ALTER TABLE control_tokens ADD COLUMN decision_json TEXT`
|
|
24
|
+
}))
|
|
25
|
+
})
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Record who admitted each signal command.
|
|
3
|
+
* @since 1.0.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as Dialect from "@smthrs/database/Dialect"
|
|
7
|
+
import * as Effect from "effect/Effect"
|
|
8
|
+
import * as SqlClient from "effect/unstable/sql/SqlClient"
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Adds the admitting principal to the signal inbox.
|
|
12
|
+
*
|
|
13
|
+
* NULL is intentional for existing rows: a command admitted before this column
|
|
14
|
+
* has no recorded identity, so it cannot answer a human wait, which requires
|
|
15
|
+
* `ApprovalAuthority` to authorize the admitting principal.
|
|
16
|
+
*
|
|
17
|
+
* @category migrations
|
|
18
|
+
* @since 1.0.0
|
|
19
|
+
*/
|
|
20
|
+
export const signalPrincipals = Effect.gen(function*() {
|
|
21
|
+
const sql = yield* SqlClient.SqlClient
|
|
22
|
+
yield* sql.withTransaction(Effect.gen(function*() {
|
|
23
|
+
const columns = yield* Dialect.columns(sql, "control_signal_commands")
|
|
24
|
+
if (columns.some((column) => column.name === "principal_json")) return
|
|
25
|
+
yield* sql`ALTER TABLE control_signal_commands ADD COLUMN principal_json TEXT`
|
|
26
|
+
}))
|
|
27
|
+
})
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Record who launched each run.
|
|
3
|
+
* @since 1.0.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as Dialect from "@smthrs/database/Dialect"
|
|
7
|
+
import * as Effect from "effect/Effect"
|
|
8
|
+
import * as SqlClient from "effect/unstable/sql/SqlClient"
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Adds the launching principal to the launch index.
|
|
12
|
+
*
|
|
13
|
+
* NULL is intentional for existing rows: a run launched before these columns
|
|
14
|
+
* has no recorded launcher, so only a reader that sees every run lists or
|
|
15
|
+
* watches it.
|
|
16
|
+
*
|
|
17
|
+
* @category migrations
|
|
18
|
+
* @since 1.0.0
|
|
19
|
+
*/
|
|
20
|
+
export const runPrincipals = Effect.gen(function*() {
|
|
21
|
+
const sql = yield* SqlClient.SqlClient
|
|
22
|
+
yield* sql.withTransaction(Effect.gen(function*() {
|
|
23
|
+
const columns = yield* Dialect.columns(sql, "control_runs")
|
|
24
|
+
if (!columns.some((column) => column.name === "principal_id")) {
|
|
25
|
+
yield* sql`ALTER TABLE control_runs ADD COLUMN principal_id TEXT`
|
|
26
|
+
}
|
|
27
|
+
if (!columns.some((column) => column.name === "principal_kind")) {
|
|
28
|
+
yield* sql`ALTER TABLE control_runs ADD COLUMN principal_kind TEXT`
|
|
29
|
+
}
|
|
30
|
+
}))
|
|
31
|
+
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Record the operator consent a resume delegation carries.
|
|
3
|
+
* @since 1.0.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as Dialect from "@smthrs/database/Dialect"
|
|
7
|
+
import * as Effect from "effect/Effect"
|
|
8
|
+
import * as SqlClient from "effect/unstable/sql/SqlClient"
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Adds the journal sequence of an operator's explicit resume to the pending
|
|
12
|
+
* resume delegations.
|
|
13
|
+
*
|
|
14
|
+
* NULL is intentional for existing rows: a delegation recorded before this
|
|
15
|
+
* column is an approval or a wake, which is background intent.
|
|
16
|
+
*
|
|
17
|
+
* @category migrations
|
|
18
|
+
* @since 1.0.0
|
|
19
|
+
*/
|
|
20
|
+
export const resumeConsent = Effect.gen(function*() {
|
|
21
|
+
const sql = yield* SqlClient.SqlClient
|
|
22
|
+
yield* sql.withTransaction(Effect.gen(function*() {
|
|
23
|
+
const columns = yield* Dialect.columns(sql, "control_run_resumes")
|
|
24
|
+
if (!columns.some((column) => column.name === "consent_seq")) {
|
|
25
|
+
yield* sql`ALTER TABLE control_run_resumes ADD COLUMN consent_seq ${Dialect.integer(sql)}`
|
|
26
|
+
}
|
|
27
|
+
}))
|
|
28
|
+
})
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic in-memory Control stack for package and consumer tests.
|
|
3
|
+
*
|
|
4
|
+
* @since 0.1.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as TestJournal from "@smthrs/journal/test/TestJournal"
|
|
8
|
+
import { NotificationQueue } from "@smthrs/notifications"
|
|
9
|
+
import { Registry } from "@smthrs/registry"
|
|
10
|
+
import { Crypto, Effect, Layer } from "effect"
|
|
11
|
+
import * as ControlExecutor from "../ControlExecutor.ts"
|
|
12
|
+
import * as ControlLive from "../ControlLive.ts"
|
|
13
|
+
import * as ControlRuntime from "../ControlRuntime.ts"
|
|
14
|
+
|
|
15
|
+
const browserCrypto = Layer.succeed(
|
|
16
|
+
Crypto.Crypto,
|
|
17
|
+
Crypto.make({
|
|
18
|
+
/* v8 ignore next -- `Crypto.make` requires a random-byte primitive, and the deterministic runtime this bundle composes derives every identifier from a counter, so only `digest` is ever called here */
|
|
19
|
+
randomBytes: (size) => globalThis.crypto.getRandomValues(new Uint8Array(size)),
|
|
20
|
+
digest: (algorithm, data) =>
|
|
21
|
+
Effect.promise(() => globalThis.crypto.subtle.digest(algorithm, data.slice().buffer)).pipe(
|
|
22
|
+
Effect.map((buffer) => new Uint8Array(buffer))
|
|
23
|
+
)
|
|
24
|
+
})
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Provides Control, ControlRuntime, the in-memory journal bundle, and an empty
|
|
29
|
+
* registry. Runtime flow metadata falls back to the reserved system catalog.
|
|
30
|
+
*
|
|
31
|
+
* @category layers
|
|
32
|
+
* @since 0.1.0
|
|
33
|
+
*/
|
|
34
|
+
export const layer = (
|
|
35
|
+
options?: ControlRuntime.MemoryOptions,
|
|
36
|
+
executor: ControlExecutor.Service = ControlExecutor.makeNoop()
|
|
37
|
+
) => {
|
|
38
|
+
const journal = TestJournal.layer()
|
|
39
|
+
const dependencies = Layer.mergeAll(
|
|
40
|
+
ControlRuntime.layerMemory(options).pipe(Layer.provide(browserCrypto)),
|
|
41
|
+
journal,
|
|
42
|
+
NotificationQueue.layer.pipe(Layer.provide(journal)),
|
|
43
|
+
ControlExecutor.layer(executor),
|
|
44
|
+
Registry.layerNoop()
|
|
45
|
+
)
|
|
46
|
+
return Layer.provideMerge(ControlLive.layer, dependencies)
|
|
47
|
+
}
|