@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,139 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Test against the control plane"
|
|
3
|
+
description: "Use the deterministic in-memory stack, swap one collaborator at a time, exercise the pure projections with no stack at all, and hold your own runtime to the contract both shipped ones satisfy."
|
|
4
|
+
sidebar:
|
|
5
|
+
order: 13
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Nothing about the control plane is hard to test, because every nondeterministic
|
|
9
|
+
input is a service: the clock, the runtime, the journal, the notification
|
|
10
|
+
queue, and the executor. A test swaps the service, not the verb.
|
|
11
|
+
|
|
12
|
+
## Start with the whole stack
|
|
13
|
+
|
|
14
|
+
`TestControl.layer` bundles the four collaborators `ControlLive` requires,
|
|
15
|
+
already deterministic:
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { Control } from "@smthrs/control/Control"
|
|
19
|
+
import type * as ControlRuntime from "@smthrs/control/ControlRuntime"
|
|
20
|
+
import * as TestControl from "@smthrs/control/test/TestControl"
|
|
21
|
+
import * as Effect from "effect/Effect"
|
|
22
|
+
|
|
23
|
+
const Deploy: ControlRuntime.MemoryFlow = {
|
|
24
|
+
flowId: "ops/Deploy",
|
|
25
|
+
description: "Deploys one build",
|
|
26
|
+
deployClass: true,
|
|
27
|
+
envelope: { capabilities: [], flows: [], budget: {} }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const stack = TestControl.layer({ flows: [Deploy], now: () => 0 })
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
It provides `Control` together with every collaborator it built:
|
|
34
|
+
`ControlRuntime`, the in-memory journal bundle, a notification queue over that
|
|
35
|
+
journal, an executor, and an empty registry. Ids are derived from counters, so
|
|
36
|
+
`plan-1`, `run-1`, and `fence-1` are stable across runs.
|
|
37
|
+
|
|
38
|
+
`MemoryOptions` is the whole configuration surface:
|
|
39
|
+
|
|
40
|
+
| Option | Effect |
|
|
41
|
+
| ----------- | ------------------------------------------------------------------------- |
|
|
42
|
+
| `flows` | What the plane may plan. Defaults to the plannable reserved system flows. |
|
|
43
|
+
| `now` | The clock every timestamp reads. Pass `() => 0` for stable output. |
|
|
44
|
+
| `principal` | The identity stamped when a caller names none. |
|
|
45
|
+
|
|
46
|
+
## Swap the executor
|
|
47
|
+
|
|
48
|
+
`TestControl.layer` takes an executor as its second argument, so a test states
|
|
49
|
+
exactly what the engine did:
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import * as ControlExecutor from "@smthrs/control/ControlExecutor"
|
|
53
|
+
|
|
54
|
+
const launched: Array<string> = []
|
|
55
|
+
|
|
56
|
+
const executor = ControlExecutor.makeNoop({
|
|
57
|
+
launch: ({ run }) =>
|
|
58
|
+
Effect.sync(() => {
|
|
59
|
+
launched.push(run.runId)
|
|
60
|
+
return "accepted" as const
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
const stack = TestControl.layer({ flows: [Deploy], now: () => 0 }, executor)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Each answer in the executor's vocabulary is a distinct composition worth a
|
|
68
|
+
test: `pending` releases the row, `accepted` promotes it to `running`, a
|
|
69
|
+
`LaunchFailed` settles it as `failed`, and a `CancelTerminal` reconciles the
|
|
70
|
+
control row onto the engine's status. See
|
|
71
|
+
[Connect an execution engine](./implement-an-executor.md).
|
|
72
|
+
|
|
73
|
+
The default is `ControlExecutor.makeNoop()`, which answers the honest absence
|
|
74
|
+
for every method, and _no executor at all_ is a different composition again:
|
|
75
|
+
`ControlLive` reads the port optionally, so a plane that starts nothing is a
|
|
76
|
+
supported shape rather than a broken one.
|
|
77
|
+
|
|
78
|
+
## Test the projections with no stack
|
|
79
|
+
|
|
80
|
+
`classify`, `remedyFor`, `originOf`, `derive`, `expand`, `attribute`, and
|
|
81
|
+
`steerItem` are pure functions of their arguments. Enumerate them directly:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import * as Monitor from "@smthrs/control/Monitor"
|
|
85
|
+
|
|
86
|
+
expect(Monitor.classify({
|
|
87
|
+
summary: { runId: "run-1", flowId: "ops/Deploy", status: "running", createdAt: 0, updatedAt: 0 },
|
|
88
|
+
events: [{ sequence: 1, kind: Monitor.attemptStartedEventType, runId: "run-1", occurredAt: 0, payload: {} }],
|
|
89
|
+
beatsWithoutProgress: 3,
|
|
90
|
+
stallBeats: 3
|
|
91
|
+
})).toBe("wedged-node")
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Splitting `beatsWithoutProgress` from `stallBeats` is what lets one pure
|
|
95
|
+
function serve a monitor that beats every second and one that beats every hour,
|
|
96
|
+
and it is what lets a test reach a stall without waiting for one.
|
|
97
|
+
|
|
98
|
+
## Assert on durable evidence
|
|
99
|
+
|
|
100
|
+
The plane's promises are visible in the journal, so assert there rather than on
|
|
101
|
+
internal state:
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
const kinds = yield * control.watch({ runId, follow: false }).pipe(
|
|
105
|
+
Stream.map((event) => event.kind),
|
|
106
|
+
Stream.runCollect
|
|
107
|
+
)
|
|
108
|
+
expect([...kinds]).toEqual(["control.run.accepted", "control.run.pending"])
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Use `follow: false`. It ends; the live stream does not.
|
|
112
|
+
|
|
113
|
+
## Hold your own runtime to the contract
|
|
114
|
+
|
|
115
|
+
`ControlRuntime.layerMemory` and `SqlControlRuntime.layer` are both held to one
|
|
116
|
+
[shared contract suite](https://github.com/smithersai/smithers/blob/main/packages/smithers/control/test/ControlContract.ts).
|
|
117
|
+
It is not part of the published tarball, so a third implementation copies it
|
|
118
|
+
from the repository and runs it against its own layer. That is what makes
|
|
119
|
+
"behaves like the memory runtime" a checkable claim rather than a hope.
|
|
120
|
+
|
|
121
|
+
## Assert on the refusals too
|
|
122
|
+
|
|
123
|
+
Several behaviors are refusals, and they carry the sentence an operator reads:
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
const error = yield * Effect.flip(control.list({ _tag: "runs", limit: 0 }))
|
|
127
|
+
expect(error.issue).toBe("limit: must be an integer between 1 and 500, received 0")
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`ControlClient.isControlError` narrows an unknown value to the declared union,
|
|
131
|
+
derived from the same schema the errors are declared in.
|
|
132
|
+
|
|
133
|
+
## Where to go next
|
|
134
|
+
|
|
135
|
+
- [Quickstart](../quickstart.md): the smallest complete program on this stack.
|
|
136
|
+
- [Troubleshooting](../troubleshooting.md): the refusals worth a test of their
|
|
137
|
+
own.
|
|
138
|
+
- [Testing flows on smithers.sh](/docs/guides/testing-flows/): the same habit,
|
|
139
|
+
one layer down.
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Watch a run's events"
|
|
3
|
+
description: "Read a finite snapshot or follow a live stream of control events, resume at a cursor without seeing an entry twice, and read the lineage and steer-delivery deltas the plane derives."
|
|
4
|
+
sidebar:
|
|
5
|
+
order: 3
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
`watch` streams `ControlEvent` values projected from committed journal entries.
|
|
9
|
+
It is a read: nothing you do with the stream changes a run.
|
|
10
|
+
|
|
11
|
+
## Take a finite snapshot
|
|
12
|
+
|
|
13
|
+
`follow: false` asks for what is durable when the request is handled. The
|
|
14
|
+
stream ends, which is what makes it assertable in a test or usable in a
|
|
15
|
+
one-shot report:
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { Control } from "@smthrs/control/Control"
|
|
19
|
+
import * as Effect from "effect/Effect"
|
|
20
|
+
import * as Stream from "effect/Stream"
|
|
21
|
+
|
|
22
|
+
const kinds = Effect.gen(function*() {
|
|
23
|
+
const control = yield* Control
|
|
24
|
+
return yield* control.watch({ runId: "run-17", follow: false }).pipe(
|
|
25
|
+
Stream.map((event) => event.kind),
|
|
26
|
+
Stream.runCollect
|
|
27
|
+
)
|
|
28
|
+
})
|
|
29
|
+
// [ "control.run.accepted", "control.run.running", "flows.engine.attempt-started", ... ]
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Follow a live stream
|
|
33
|
+
|
|
34
|
+
Omit `follow` and the stream stays open. This is what a UI subscribes to:
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
const tail = control.watch({ runId: "run-17" }).pipe(
|
|
38
|
+
Stream.runForEach((event) => Effect.log(`${event.sequence} ${event.kind}`))
|
|
39
|
+
)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
A subscriber that arrives late still receives the entries it missed. The
|
|
43
|
+
projection pins a high-water sequence per partition, reads everything at or
|
|
44
|
+
below it from a finite snapshot, and takes everything above it from the
|
|
45
|
+
buffered tail. It is a handoff rather than a deduplicated overlap, so an
|
|
46
|
+
arbitrarily long history cannot make an old entry reappear.
|
|
47
|
+
|
|
48
|
+
## Resume at a cursor
|
|
49
|
+
|
|
50
|
+
Store `event.cursor` after processing each event, then pass it back unchanged
|
|
51
|
+
as `afterCursor` with the same `runId`:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
const resumed = control.watch({ runId: "run-17", afterCursor: lastSeen })
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
A cursor is `{ sequence: number; offset?: number }`. `sequence` identifies the
|
|
58
|
+
source journal entry. A present `offset` is the zero-based index of the last
|
|
59
|
+
consumed member of that entry's expansion. An absent offset means the entire
|
|
60
|
+
entry was consumed. The last member always carries this completed-entry cursor,
|
|
61
|
+
so the next watch starts after the source row without rereading it.
|
|
62
|
+
|
|
63
|
+
For a promotion at sequence 12 that delivers two messages, the emitted cursors
|
|
64
|
+
are `{ sequence: 12, offset: 0 }` for the source,
|
|
65
|
+
`{ sequence: 12, offset: 1 }` for the first delivery, and `{ sequence: 12 }`
|
|
66
|
+
for the second. Reconnecting after the source still yields both deliveries.
|
|
67
|
+
This works for finite snapshots and live streams while the source row remains
|
|
68
|
+
in the journal. Commit the checkpoint with your event processing to avoid
|
|
69
|
+
reprocessing an event after a consumer crash.
|
|
70
|
+
|
|
71
|
+
`afterSequence` remains available to skip a fully processed source entry and
|
|
72
|
+
all its derived events. It cannot checkpoint progress inside an expansion.
|
|
73
|
+
Do not combine it with `afterCursor`.
|
|
74
|
+
|
|
75
|
+
Both cursor forms require `runId`. Sequences are partition-local:
|
|
76
|
+
|
|
77
|
+
```text
|
|
78
|
+
InvalidInput: afterCursor: a watch cursor resumes one run, so it requires runId
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Raw `Lineage` and `Steering` projections and older providers can omit `cursor`.
|
|
82
|
+
`ControlLive.watch` assigns a cursor to every emitted event.
|
|
83
|
+
|
|
84
|
+
## Watch everything
|
|
85
|
+
|
|
86
|
+
Omit `runId` and the stream merges every partition the plane knows: each run,
|
|
87
|
+
and each plan under `plan:<planId>`. Eight partition snapshots are read at a
|
|
88
|
+
time, and the live tail runs beside them so snapshot work never starves it.
|
|
89
|
+
|
|
90
|
+
The plane lists partitions in insertion order, 100 ids per inventory query,
|
|
91
|
+
and reads the next page only when the snapshot needs it. Each page is one
|
|
92
|
+
indexed seek on the table's row key, so a page costs the same at any table size:
|
|
93
|
+
|
|
94
|
+
| Resource | Bound |
|
|
95
|
+
| ------------------------ | ------------------------------------------ |
|
|
96
|
+
| Rows per inventory query | 101 (one page plus the continuation key) |
|
|
97
|
+
| Rows scanned per query | the rows returned; no table scan or sort |
|
|
98
|
+
| Inventory queries | `2 + ceil(plans / 100) + ceil(runs / 100)` |
|
|
99
|
+
| Run summaries decoded | 0 |
|
|
100
|
+
| Follow-mode state | one pinned sequence per partition seen |
|
|
101
|
+
|
|
102
|
+
The first page of each inventory pins its newest entry, and the walk stops
|
|
103
|
+
there. A finite watch (`follow: false`) therefore ends even while runs keep
|
|
104
|
+
arriving. A partition that exists when the watch starts is read exactly once;
|
|
105
|
+
one created during the walk is not listed. A followed watch still delivers its
|
|
106
|
+
entries, because the first tail entry for a partition that nothing has pinned
|
|
107
|
+
pins it and reads its history first.
|
|
108
|
+
|
|
109
|
+
An unscoped watch is the right shape for a dashboard. For a run you can name,
|
|
110
|
+
scope it: the scoped watch is one partition read and it is the only form that
|
|
111
|
+
can resume.
|
|
112
|
+
|
|
113
|
+
## Read the derived deltas
|
|
114
|
+
|
|
115
|
+
Two kinds are computed rather than recorded, and arrive beside the entry they
|
|
116
|
+
were derived from:
|
|
117
|
+
|
|
118
|
+
| Kind | Payload | Means |
|
|
119
|
+
| ------------------------- | ----------------------------------------------------------- | -------------------------------------------- |
|
|
120
|
+
| `control.run.lineage` | `{ runId, parentRunId, lineageId?, roundOrdinal?, origin }` | A run was spawned, forked, or handed off to. |
|
|
121
|
+
| `control.steer.delivered` | `{ runId, messageId, boundary }` | A turn boundary took your steer. |
|
|
122
|
+
|
|
123
|
+
Both projections are exported, so a client reading the journal directly reaches
|
|
124
|
+
the same conclusions the server does:
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
import * as Lineage from "@smthrs/control/Lineage"
|
|
128
|
+
import * as Steering from "@smthrs/control/Steering"
|
|
129
|
+
|
|
130
|
+
const expanded = [...Lineage.expand(event), ...Steering.derive(event)]
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
`expand` returns the entry plus any delta it discloses; `derive` returns the
|
|
134
|
+
delta alone. See [Journal projections](../concepts/projections.md) for why
|
|
135
|
+
delivery is derived rather than written.
|
|
136
|
+
|
|
137
|
+
## Failures
|
|
138
|
+
|
|
139
|
+
Every member of `ControlError` can reach a watch stream. In practice you will
|
|
140
|
+
meet three:
|
|
141
|
+
|
|
142
|
+
- `InvalidInput` for an unscoped or malformed cursor, or both cursor forms together.
|
|
143
|
+
- `PersistenceError` with operation `watch` when a journal read fails. Its
|
|
144
|
+
`cause` is the journal's own error.
|
|
145
|
+
- `Unavailable` with feature `watch` when the composition has no open journal.
|
|
146
|
+
|
|
147
|
+
A watch of a run that does not exist is not an error. The partition is empty.
|
|
148
|
+
|
|
149
|
+
## Where to go next
|
|
150
|
+
|
|
151
|
+
- [Journal projections](../concepts/projections.md): partitions, the handoff,
|
|
152
|
+
and the full list of kinds the plane writes.
|
|
153
|
+
- [Find runs and page through them](./list-runs.md): the point-in-time view.
|
|
154
|
+
- [`smthrs runs logs`](/cli/runs): the operator surface over this verb.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Installation"
|
|
3
|
+
description: "Install @smthrs/control, its runtime requirements, its import forms, and the collaborator packages an in-memory, durable, or remote composition adds."
|
|
4
|
+
sidebar:
|
|
5
|
+
order: 1
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Install the package
|
|
9
|
+
|
|
10
|
+
Not on npm yet; see [Installation](https://github.com/smithersai/smithers/blob/main/packages/smithers/flows/flow/docs/installation.md#use-the-libraries).
|
|
11
|
+
|
|
12
|
+
The package requires Node.js 26.4.0 or later and ships as both ESM and
|
|
13
|
+
CommonJS with TypeScript declarations. Its runtime dependencies install with
|
|
14
|
+
it: [`effect`](https://effect.website) and the `@smthrs/*` packages the plane
|
|
15
|
+
composes.
|
|
16
|
+
|
|
17
|
+
The package imports no `node:*` module. Identity comes from
|
|
18
|
+
`globalThis.crypto`, encryption comes from Web Crypto, and persistence speaks
|
|
19
|
+
the driver-neutral SQL contract, so the same modules run in Node.js and in a
|
|
20
|
+
browser that supplies a SQL driver. That is a statement about the imports, not
|
|
21
|
+
a tested guarantee: nothing here is exercised in a browser, so verify your own
|
|
22
|
+
bundle before you depend on it.
|
|
23
|
+
|
|
24
|
+
## Import forms
|
|
25
|
+
|
|
26
|
+
The root entry point re-exports every module as a namespace:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { Control, ControlLive, Monitor, SqlControlRuntime } from "@smthrs/control"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Each module is also importable from its own subpath, which is the form the
|
|
33
|
+
[API reference](./api.md) uses:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import * as ControlExecutor from "@smthrs/control/ControlExecutor"
|
|
37
|
+
import * as ControlSchema from "@smthrs/control/ControlSchema"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The deterministic test stack has its own subpath:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import * as TestControl from "@smthrs/control/test/TestControl"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Two subpath families are not public and are blocked in the export map:
|
|
47
|
+
`@smthrs/control/internal/*` and `@smthrs/control/migrations/*`, along with
|
|
48
|
+
every nested `*/index`. `@smthrs/control/package.json` is exported.
|
|
49
|
+
|
|
50
|
+
## What a composition adds
|
|
51
|
+
|
|
52
|
+
`ControlLive.layer` requires four collaborators, and a host provides all four:
|
|
53
|
+
|
|
54
|
+
| Requirement | Package | What it does here |
|
|
55
|
+
| ------------------- | --------------------------------------------- | ---------------------------------------------------------------------------------- |
|
|
56
|
+
| `ControlRuntime` | this package | Stores plans, tokens, grants, idempotency records, and run rows. |
|
|
57
|
+
| `Journal` | [`@smthrs/journal`](/api/journal) | Records every decision beside the state change it caused, and backs `watch`. |
|
|
58
|
+
| `NotificationQueue` | [`@smthrs/notifications`](/api/notifications) | Carries a steer to the turn boundary that delivers it, and counts what is pending. |
|
|
59
|
+
| `Registry` | [`@smthrs/registry`](/api/registry) | Answers `list({ _tag: "flows" })` with the flows this host discovered. |
|
|
60
|
+
|
|
61
|
+
`ControlExecutor` is optional. A composition that provides none observes and
|
|
62
|
+
records but starts nothing, which is the correct shape for a monitor or a
|
|
63
|
+
read-only dashboard.
|
|
64
|
+
|
|
65
|
+
### An in-memory composition
|
|
66
|
+
|
|
67
|
+
`TestControl.layer` bundles all four collaborators with the deterministic
|
|
68
|
+
runtime. Its journal uses a real in-memory SQLite database, so the
|
|
69
|
+
[Quickstart](./quickstart.md) adds the optional Node driver:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pnpm add effect@4.0.0-rc.115 @effect/sql-sqlite-node@4.0.0-rc.115
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### A durable composition
|
|
76
|
+
|
|
77
|
+
- [`@smthrs/database`](/api/database) supplies the SQL client and the
|
|
78
|
+
`DurableWriter` every control write serializes through.
|
|
79
|
+
- [`@smthrs/run-store`](/api/run-store) supplies the fenced run store
|
|
80
|
+
`SqlControlRuntime` maps the control lifecycle onto.
|
|
81
|
+
|
|
82
|
+
See [Store control state in a database](./guides/durable-storage.md) for the
|
|
83
|
+
layer stack and the migration order.
|
|
84
|
+
|
|
85
|
+
### A remote composition
|
|
86
|
+
|
|
87
|
+
The RPC boundary uses Effect's own HTTP, WebSocket, and RPC modules, which ship
|
|
88
|
+
inside `effect`. A Node host adds the platform bindings and a serialization
|
|
89
|
+
format:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
pnpm add @effect/platform-node@4.0.0-rc.115 @effect/platform-node-shared@4.0.0-rc.115
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
See [Serve the control plane over RPC](./guides/serve-over-rpc.md).
|
|
96
|
+
|
|
97
|
+
### Credential storage
|
|
98
|
+
|
|
99
|
+
The credential boundary needs a store and a cipher. `CredentialStore.layerMemory`
|
|
100
|
+
and `WebCryptoCipher.layer` need nothing beyond this package;
|
|
101
|
+
`SqlCredentialStore.layer` needs the same database packages a durable
|
|
102
|
+
composition adds. See [Store and resolve a credential](./guides/store-credentials.md).
|
|
103
|
+
|
|
104
|
+
## Next step
|
|
105
|
+
|
|
106
|
+
Run one plan through approval and launch in the [Quickstart](./quickstart.md).
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Quickstart"
|
|
3
|
+
description: "Plan a flow, watch the launch park for approval, approve it, launch it, list the run, and replay the journal, in one in-memory program with no database and no engine."
|
|
4
|
+
sidebar:
|
|
5
|
+
order: 2
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
This quickstart drives one plan through the whole gate: a plan card, a refused
|
|
9
|
+
launch, an approval, an accepted launch, a listing, and a replay of the run's
|
|
10
|
+
journal. The `Control` service is the production one. Only its collaborators
|
|
11
|
+
are in memory, so the program is deterministic, needs no database, and starts
|
|
12
|
+
no real work.
|
|
13
|
+
|
|
14
|
+
By the end you will have seen the two answers that make the control plane
|
|
15
|
+
usable from a script: a `Receipt` that says what happened, and a `ControlEvent`
|
|
16
|
+
stream that says it again from durable evidence.
|
|
17
|
+
|
|
18
|
+
## Prerequisites
|
|
19
|
+
|
|
20
|
+
- Node.js 26.4.0 or later.
|
|
21
|
+
- A package with the dependency installed. Not on npm yet; see [Installation](https://github.com/smithersai/smithers/blob/main/packages/smithers/flows/flow/docs/installation.md#use-the-libraries).
|
|
22
|
+
|
|
23
|
+
## Declare the flow the plane may plan
|
|
24
|
+
|
|
25
|
+
A control plane plans what its runtime knows about. `MemoryFlow` is that
|
|
26
|
+
entry: an id, a description, whether the flow is deploy class, and the
|
|
27
|
+
capability envelope an approval binds to.
|
|
28
|
+
|
|
29
|
+
Create `quickstart.ts`:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import type * as ControlRuntime from "@smthrs/control/ControlRuntime"
|
|
33
|
+
|
|
34
|
+
/** One flow this plane may be asked to plan. */
|
|
35
|
+
const Deploy: ControlRuntime.MemoryFlow = {
|
|
36
|
+
flowId: "quickstart/Deploy",
|
|
37
|
+
description: "Deploys one build",
|
|
38
|
+
deployClass: true,
|
|
39
|
+
envelope: {
|
|
40
|
+
capabilities: ["process:spawn"],
|
|
41
|
+
flows: [],
|
|
42
|
+
budget: { milliseconds: 60_000 }
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The envelope is the authority a reviewer is being asked to grant. It is part of
|
|
48
|
+
the plan digest, so an approval taken on this envelope cannot authorize a
|
|
49
|
+
wider one later.
|
|
50
|
+
|
|
51
|
+
## Plan, launch, approve, launch again
|
|
52
|
+
|
|
53
|
+
`plan` returns a `PlanCard`: the flow, a canonical summary of the input, the
|
|
54
|
+
envelope, the keyed node graph, and a digest over all of it. The card starts
|
|
55
|
+
undecided, so the first `run` parks:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
import { Control } from "@smthrs/control/Control"
|
|
59
|
+
import * as Effect from "effect/Effect"
|
|
60
|
+
import * as Stream from "effect/Stream"
|
|
61
|
+
|
|
62
|
+
const program = Effect.gen(function*() {
|
|
63
|
+
const control = yield* Control
|
|
64
|
+
|
|
65
|
+
const card = yield* control.plan({
|
|
66
|
+
flowId: "quickstart/Deploy",
|
|
67
|
+
input: { build: "v1.4.0" }
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
/** The exact plan, digest, and envelope every launch attempt resubmits. */
|
|
71
|
+
const launch = {
|
|
72
|
+
_tag: "Plan" as const,
|
|
73
|
+
planId: card.planId,
|
|
74
|
+
digest: card.digest,
|
|
75
|
+
envelope: card.envelope
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Nothing is approved yet, so this starts nothing and says why.
|
|
79
|
+
const parked = yield* control.run({ ...launch, idempotencyKey: "deploy:v1.4.0" })
|
|
80
|
+
|
|
81
|
+
// The card carries the exact payload an approval is taken on, including a
|
|
82
|
+
// default idempotency key, so a reviewer resubmits it unchanged.
|
|
83
|
+
yield* control.approve(card.approval)
|
|
84
|
+
|
|
85
|
+
// The same call, the same key. Now it launches.
|
|
86
|
+
const accepted = yield* control.run({ ...launch, idempotencyKey: "deploy:v1.4.0" })
|
|
87
|
+
|
|
88
|
+
// And once more, to show what a retry is worth.
|
|
89
|
+
const replayed = yield* control.run({ ...launch, idempotencyKey: "deploy:v1.4.0" })
|
|
90
|
+
|
|
91
|
+
const listed = yield* control.list({ _tag: "runs", filters: {} })
|
|
92
|
+
const runs = listed._tag === "runs" ? listed.items : []
|
|
93
|
+
|
|
94
|
+
const events = yield* control.watch({ runId: runs[0]!.runId, follow: false }).pipe(
|
|
95
|
+
Stream.map((event) => event.kind),
|
|
96
|
+
Stream.runCollect
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
return { parked, accepted, replayed, runs, events: [...events] }
|
|
100
|
+
})
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Provide the in-memory stack and run it
|
|
104
|
+
|
|
105
|
+
`TestControl.layer` bundles the four collaborators `ControlLive` requires: the
|
|
106
|
+
deterministic runtime, an in-memory journal, a notification queue over that
|
|
107
|
+
journal, and an empty registry. Its executor accepts nothing, which is exactly
|
|
108
|
+
the composition a host that only records has.
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
import * as TestControl from "@smthrs/control/test/TestControl"
|
|
112
|
+
|
|
113
|
+
console.log(
|
|
114
|
+
await Effect.runPromise(
|
|
115
|
+
program.pipe(Effect.provide(TestControl.layer({ flows: [Deploy], now: () => 0 })))
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Run the file with your TypeScript runner. The receipts, the listing, and the
|
|
121
|
+
journal replay come back like this:
|
|
122
|
+
|
|
123
|
+
```text
|
|
124
|
+
{
|
|
125
|
+
parked: { _tag: 'Parked', receiptId: 'deploy:v1.4.0', planId: 'plan-1', status: 'waiting-approval' },
|
|
126
|
+
accepted: { _tag: 'Accepted', receiptId: 'deploy:v1.4.0', runId: 'run-1' },
|
|
127
|
+
replayed: { _tag: 'AlreadyApplied', receiptId: 'deploy:v1.4.0', runId: 'run-1' },
|
|
128
|
+
runs: [ { runId: 'run-1', flowId: 'quickstart/Deploy', status: 'accepted', ... } ],
|
|
129
|
+
events: [ 'control.run.accepted', 'control.run.pending' ]
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## What just happened
|
|
134
|
+
|
|
135
|
+
Four things worth naming, because each is a promise the plane keeps everywhere:
|
|
136
|
+
|
|
137
|
+
- **A launch is not a start.** An undecided plan answers `Parked` with the
|
|
138
|
+
status it is waiting in. Nothing was created, so nothing has to be cleaned
|
|
139
|
+
up. See [Gate work behind an approval](./guides/approvals.md).
|
|
140
|
+
- **The same key means the same mutation, once.** The launch and the retry
|
|
141
|
+
carry one `idempotencyKey`, and the retry answers `AlreadyApplied` with the
|
|
142
|
+
run the first call created. A parked receipt is deliberately not recorded, so
|
|
143
|
+
the key was still free when the plan became approvable. See
|
|
144
|
+
[Receipts and idempotency](./concepts/receipts.md).
|
|
145
|
+
- **An approval is bound to what was reviewed.** `card.approval` carries the
|
|
146
|
+
target, the digest, the envelope, and a default key. Submit a different
|
|
147
|
+
digest or a different envelope and the decision is refused rather than
|
|
148
|
+
re-aimed.
|
|
149
|
+
- **Every decision left evidence.** `watch` replayed the run's journal from
|
|
150
|
+
durable rows. `control.run.pending` is there because this composition's
|
|
151
|
+
executor declined the launch, so the plane released the run rather than
|
|
152
|
+
claiming to drive it. See [Journal projections](./concepts/projections.md).
|
|
153
|
+
|
|
154
|
+
## Next steps
|
|
155
|
+
|
|
156
|
+
- [Connect an execution engine](./guides/implement-an-executor.md): make
|
|
157
|
+
`control.run` start something real.
|
|
158
|
+
- [Store control state in a database](./guides/durable-storage.md): keep the
|
|
159
|
+
plans, tokens, and runs across a restart.
|
|
160
|
+
- [Serve the control plane over RPC](./guides/serve-over-rpc.md): hand this
|
|
161
|
+
same program a client instead of a layer.
|
|
162
|
+
- [Authority, not execution](./concepts/authority.md): the model the rest of
|
|
163
|
+
the package is built on.
|