@smthrs/control 0.0.0-stage → 1.0.0-rc.3
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 +1280 -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 +740 -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 +1521 -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 +1585 -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 +807 -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 +5 -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 +2113 -0
- package/src/ControlRpcs.ts +443 -0
- package/src/ControlRuntime.ts +1597 -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 +2476 -0
- package/src/SqlCredentialStore.ts +148 -0
- package/src/Steering.ts +96 -0
- package/src/SystemFlows.ts +225 -0
- package/src/WebCryptoCipher.ts +169 -0
- package/src/WebhookChannel.ts +166 -0
- package/src/index.ts +188 -0
- package/src/internal/MutationBoundary.ts +46 -0
- package/src/internal/activeFibers.ts +22 -0
- package/src/internal/issues.ts +40 -0
- package/src/internal/planning.ts +262 -0
- package/src/internal/sqlSchemaErrors.ts +37 -0
- package/src/migrations/0001_control_tables.ts +99 -0
- package/src/migrations/0002_run_keys.ts +23 -0
- package/src/migrations/0003_signal_commands.ts +27 -0
- package/src/migrations/0004_approval_decisions.ts +25 -0
- package/src/migrations/0005_signal_principals.ts +27 -0
- package/src/migrations/0006_run_principals.ts +31 -0
- package/src/migrations/0007_resume_consent.ts +28 -0
- package/src/test/TestControl.ts +47 -0
package/src/Health.ts
ADDED
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
/** Observational health contracts shared by durable runs and native sessions.
|
|
2
|
+
* @since 1.0.0
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as Sha256 from "@smthrs/crypto/Sha256"
|
|
6
|
+
import * as Fault from "@smthrs/flow/Fault"
|
|
7
|
+
import type * as Evaluator from "@smthrs/model/Evaluator"
|
|
8
|
+
import { Cause, Effect, Metric, Schema } from "effect"
|
|
9
|
+
import type { ControlEvent, RunSummary } from "./ControlSchema.ts"
|
|
10
|
+
import { makeJevSessionChecker } from "./JevSessionChecker.ts"
|
|
11
|
+
|
|
12
|
+
/** Authoritative subject lifecycle.
|
|
13
|
+
* @category schemas
|
|
14
|
+
* @since 1.0.0
|
|
15
|
+
*/
|
|
16
|
+
export const SubjectState = Schema.Literals([
|
|
17
|
+
"accepted",
|
|
18
|
+
"running",
|
|
19
|
+
"parked",
|
|
20
|
+
"waiting-approval",
|
|
21
|
+
"completed",
|
|
22
|
+
"failed",
|
|
23
|
+
"cancelled",
|
|
24
|
+
"spawning",
|
|
25
|
+
"exited"
|
|
26
|
+
])
|
|
27
|
+
/** Authoritative subject lifecycle.
|
|
28
|
+
* @category models
|
|
29
|
+
* @since 1.0.0
|
|
30
|
+
*/
|
|
31
|
+
export type SubjectState = typeof SubjectState.Type
|
|
32
|
+
/** Health vocabulary shared with Monitor.
|
|
33
|
+
* @category schemas
|
|
34
|
+
* @since 1.0.0
|
|
35
|
+
*/
|
|
36
|
+
export const HealthState = Schema.Literals([
|
|
37
|
+
"healthy",
|
|
38
|
+
"stalled",
|
|
39
|
+
"wedged-node",
|
|
40
|
+
"runaway-loop",
|
|
41
|
+
"awaiting-human",
|
|
42
|
+
"failing",
|
|
43
|
+
"unknown"
|
|
44
|
+
])
|
|
45
|
+
/** Derived subject health.
|
|
46
|
+
* @category models
|
|
47
|
+
* @since 1.0.0
|
|
48
|
+
*/
|
|
49
|
+
export type HealthState = typeof HealthState.Type
|
|
50
|
+
/** Only explicit semantic checks establish activity.
|
|
51
|
+
* @category schemas
|
|
52
|
+
* @since 1.0.0
|
|
53
|
+
*/
|
|
54
|
+
export const Activity = Schema.Literals(["working", "idle", "needs-input", "unknown"])
|
|
55
|
+
/** Semantic activity.
|
|
56
|
+
* @category models
|
|
57
|
+
* @since 1.0.0
|
|
58
|
+
*/
|
|
59
|
+
export type Activity = typeof Activity.Type
|
|
60
|
+
/** Attention never confers permission to act. `needs-resume` is a run parked over
|
|
61
|
+
* executions its owner released: nothing restarts them until `smthrs runs resume`.
|
|
62
|
+
* @category schemas
|
|
63
|
+
* @since 1.0.0
|
|
64
|
+
*/
|
|
65
|
+
export const Attention = Schema.Literals(["none", "awaiting-approval", "needs-input", "needs-resume", "unhealthy"])
|
|
66
|
+
/** Human attention.
|
|
67
|
+
* @category models
|
|
68
|
+
* @since 1.0.0
|
|
69
|
+
*/
|
|
70
|
+
export type Attention = typeof Attention.Type
|
|
71
|
+
/** Whether a successful reading still describes this subject.
|
|
72
|
+
* @category schemas
|
|
73
|
+
* @since 1.0.0
|
|
74
|
+
*/
|
|
75
|
+
export const Freshness = Schema.Literals(["fresh", "stale", "unobserved"])
|
|
76
|
+
/** Reading freshness.
|
|
77
|
+
* @category models
|
|
78
|
+
* @since 1.0.0
|
|
79
|
+
*/
|
|
80
|
+
export type Freshness = typeof Freshness.Type
|
|
81
|
+
/** Public reasons; arbitrary exception text and terminal bytes never cross this boundary.
|
|
82
|
+
* @category schemas
|
|
83
|
+
* @since 1.0.0
|
|
84
|
+
*/
|
|
85
|
+
export const ReasonCode = Schema.Literals([
|
|
86
|
+
"ok",
|
|
87
|
+
"no-progress",
|
|
88
|
+
"awaiting-reply",
|
|
89
|
+
"prompt-detected",
|
|
90
|
+
"quota-wait",
|
|
91
|
+
"timer-wait",
|
|
92
|
+
"event-wait",
|
|
93
|
+
"released",
|
|
94
|
+
"unreachable",
|
|
95
|
+
"probe-timeout",
|
|
96
|
+
"probe-error",
|
|
97
|
+
"owner-changed"
|
|
98
|
+
])
|
|
99
|
+
/** Public reason code.
|
|
100
|
+
* @category models
|
|
101
|
+
* @since 1.0.0
|
|
102
|
+
*/
|
|
103
|
+
export type ReasonCode = typeof ReasonCode.Type
|
|
104
|
+
/** Validated checker output, containing no authority or ordering fields.
|
|
105
|
+
* @category schemas
|
|
106
|
+
* @since 1.0.0
|
|
107
|
+
*/
|
|
108
|
+
export const ProbeReport = Schema.Struct({ activity: Activity, reason: Schema.optional(ReasonCode) })
|
|
109
|
+
/** Validated checker output.
|
|
110
|
+
* @category models
|
|
111
|
+
* @since 1.0.0
|
|
112
|
+
*/
|
|
113
|
+
export type ProbeReport = typeof ProbeReport.Type
|
|
114
|
+
const Counter = Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0)))
|
|
115
|
+
|
|
116
|
+
/** A host-stamped observation; its journal sequence is supplied by the durable writer.
|
|
117
|
+
* @category schemas
|
|
118
|
+
* @since 1.0.0
|
|
119
|
+
*/
|
|
120
|
+
export const HealthObservation = Schema.Struct({
|
|
121
|
+
subjectId: Schema.String,
|
|
122
|
+
state: SubjectState,
|
|
123
|
+
checkerId: Schema.String,
|
|
124
|
+
monitorId: Schema.String,
|
|
125
|
+
incarnation: Schema.String,
|
|
126
|
+
evidenceSeq: Counter,
|
|
127
|
+
observedAt: Counter,
|
|
128
|
+
expiresAt: Counter,
|
|
129
|
+
durationMs: Counter,
|
|
130
|
+
baseHealth: Schema.optional(HealthState),
|
|
131
|
+
outcome: Schema.Literals(["ok", "timeout", "error", "interrupted", "discarded"]),
|
|
132
|
+
report: Schema.optional(ProbeReport),
|
|
133
|
+
reason: Schema.optional(ReasonCode)
|
|
134
|
+
})
|
|
135
|
+
/** A host-stamped observation.
|
|
136
|
+
* @category models
|
|
137
|
+
* @since 1.0.0
|
|
138
|
+
*/
|
|
139
|
+
export type HealthObservation = typeof HealthObservation.Type
|
|
140
|
+
/** One observation with its authoritative publication order.
|
|
141
|
+
* @category models
|
|
142
|
+
* @since 1.0.0
|
|
143
|
+
*/
|
|
144
|
+
export interface RecordedObservation {
|
|
145
|
+
readonly observation: HealthObservation
|
|
146
|
+
readonly sequence: number
|
|
147
|
+
}
|
|
148
|
+
/** The additive wire contract rendered by every status surface.
|
|
149
|
+
* @category schemas
|
|
150
|
+
* @since 1.0.0
|
|
151
|
+
*/
|
|
152
|
+
export const StatusRollup = Schema.Struct({
|
|
153
|
+
subjectId: Schema.String,
|
|
154
|
+
state: SubjectState,
|
|
155
|
+
activity: Activity,
|
|
156
|
+
health: HealthState,
|
|
157
|
+
attention: Attention,
|
|
158
|
+
freshness: Freshness,
|
|
159
|
+
reason: Schema.optional(ReasonCode),
|
|
160
|
+
provenance: Schema.optional(Schema.Struct({
|
|
161
|
+
checkerId: Schema.String,
|
|
162
|
+
monitorId: Schema.String,
|
|
163
|
+
observedAt: Counter,
|
|
164
|
+
expiresAt: Counter,
|
|
165
|
+
evidenceSeq: Counter,
|
|
166
|
+
incarnation: Schema.String,
|
|
167
|
+
version: Counter
|
|
168
|
+
})),
|
|
169
|
+
updatedAt: Counter
|
|
170
|
+
})
|
|
171
|
+
/** The additive wire contract.
|
|
172
|
+
* @category models
|
|
173
|
+
* @since 1.0.0
|
|
174
|
+
*/
|
|
175
|
+
export type StatusRollup = typeof StatusRollup.Type
|
|
176
|
+
/** Durable event type for observational health.
|
|
177
|
+
* @category constants
|
|
178
|
+
* @since 1.0.0
|
|
179
|
+
*/
|
|
180
|
+
export const statusObservedEventType = "control.status.observed"
|
|
181
|
+
/** Opaque identity of the authoritative run ownership and lifecycle point being observed.
|
|
182
|
+
* Equality is meaningful; lexical or numeric ordering is not.
|
|
183
|
+
* @category getters
|
|
184
|
+
* @since 1.0.0
|
|
185
|
+
*/
|
|
186
|
+
export const runIncarnation = (run: RunSummary): string =>
|
|
187
|
+
Sha256.digestSync(JSON.stringify([
|
|
188
|
+
run.runId,
|
|
189
|
+
run.createdAt,
|
|
190
|
+
run.updatedAt,
|
|
191
|
+
run.ownerId ?? null,
|
|
192
|
+
run.parkedBy ?? null,
|
|
193
|
+
run.status,
|
|
194
|
+
run.waitingReason ?? null
|
|
195
|
+
]))
|
|
196
|
+
|
|
197
|
+
/** Read-only evidence, deliberately excluding mutation ports and terminal text by default.
|
|
198
|
+
* Checkers are trusted host TypeScript, not a sandbox: captured ambient authority remains their author's responsibility.
|
|
199
|
+
* @category models
|
|
200
|
+
* @since 1.0.0
|
|
201
|
+
*/
|
|
202
|
+
export interface ProbeContext {
|
|
203
|
+
readonly subjectId: string
|
|
204
|
+
readonly state: SubjectState
|
|
205
|
+
readonly events: ReadonlyArray<ControlEvent>
|
|
206
|
+
readonly summary?: RunSummary | undefined
|
|
207
|
+
readonly session?: {
|
|
208
|
+
readonly alive: boolean
|
|
209
|
+
readonly exitCode: number | null
|
|
210
|
+
readonly outputCursor: number
|
|
211
|
+
readonly outputTail?: string | undefined
|
|
212
|
+
} | undefined
|
|
213
|
+
readonly sinceCursor: number
|
|
214
|
+
}
|
|
215
|
+
/** Bounded polling, expiry and failure backoff.
|
|
216
|
+
* @category models
|
|
217
|
+
* @since 1.0.0
|
|
218
|
+
*/
|
|
219
|
+
export interface CheckPolicy {
|
|
220
|
+
readonly intervalMs: number
|
|
221
|
+
readonly timeoutMs: number
|
|
222
|
+
readonly ttlMs: number
|
|
223
|
+
/** Grace for silent work before the flow host considers missing progress unhealthy. */
|
|
224
|
+
readonly stallAfterMs: number
|
|
225
|
+
readonly backoff: { readonly initialMs: number; readonly maxMs: number; readonly factor: number }
|
|
226
|
+
}
|
|
227
|
+
/** Trusted host checker; supply service requirements around its Effect before registration.
|
|
228
|
+
* @category models
|
|
229
|
+
* @since 1.0.0
|
|
230
|
+
*/
|
|
231
|
+
export interface HealthChecker<C = unknown> {
|
|
232
|
+
readonly id: string
|
|
233
|
+
readonly configSchema?: Schema.Codec<C, unknown> | undefined
|
|
234
|
+
readonly defaults?: Partial<CheckPolicy> | undefined
|
|
235
|
+
readonly probe: (context: ProbeContext, config: C) => Effect.Effect<ProbeReport, unknown>
|
|
236
|
+
}
|
|
237
|
+
/** Data may select a registered checker but never executable code.
|
|
238
|
+
* @category models
|
|
239
|
+
* @since 1.0.0
|
|
240
|
+
*/
|
|
241
|
+
export interface HealthBinding {
|
|
242
|
+
readonly checkerId: string
|
|
243
|
+
readonly config?: unknown
|
|
244
|
+
readonly policy?: Partial<CheckPolicy> | undefined
|
|
245
|
+
readonly exposeOutput?: boolean | undefined
|
|
246
|
+
}
|
|
247
|
+
/** Trusted host health configuration.
|
|
248
|
+
* @category models
|
|
249
|
+
* @since 1.0.0
|
|
250
|
+
*/
|
|
251
|
+
export interface HealthConfig {
|
|
252
|
+
// A heterogeneous registry erases C only after each binding is decoded by its own schema.
|
|
253
|
+
readonly checkers?: ReadonlyArray<HealthChecker<any>> | undefined
|
|
254
|
+
readonly bindings?: Readonly<Record<string, HealthBinding>> | undefined
|
|
255
|
+
readonly limits?: { readonly maxSubjects?: number; readonly maxConcurrentProbes?: number } | undefined
|
|
256
|
+
}
|
|
257
|
+
/** Fully admitted checker binding.
|
|
258
|
+
* @category models
|
|
259
|
+
* @since 1.0.0
|
|
260
|
+
*/
|
|
261
|
+
export interface ResolvedCheck {
|
|
262
|
+
readonly checker: HealthChecker<any>
|
|
263
|
+
readonly config: unknown
|
|
264
|
+
readonly policy: CheckPolicy
|
|
265
|
+
readonly exposeOutput: boolean
|
|
266
|
+
}
|
|
267
|
+
/** Host registry, built once before admitting subjects.
|
|
268
|
+
* @category models
|
|
269
|
+
* @since 1.0.0
|
|
270
|
+
*/
|
|
271
|
+
export interface Registry {
|
|
272
|
+
readonly resolve: (key: string) => ResolvedCheck
|
|
273
|
+
readonly limits: { readonly maxSubjects: number; readonly maxConcurrentProbes: number }
|
|
274
|
+
}
|
|
275
|
+
/** Invalid trusted configuration fails startup instead of silently running another policy.
|
|
276
|
+
* @category errors
|
|
277
|
+
* @since 1.0.0
|
|
278
|
+
*/
|
|
279
|
+
export class HealthConfigurationError
|
|
280
|
+
extends Schema.TaggedError<HealthConfigurationError>()("HealthConfigurationError", {
|
|
281
|
+
reason: Schema.Literals(["invalid-policy", "invalid-checker", "invalid-config"])
|
|
282
|
+
})
|
|
283
|
+
{}
|
|
284
|
+
Fault.register("HealthConfigurationError", "bug")
|
|
285
|
+
/** Safe cadence used by unconfigured hosts.
|
|
286
|
+
* @category constants
|
|
287
|
+
* @since 1.0.0
|
|
288
|
+
*/
|
|
289
|
+
export const defaultPolicy: CheckPolicy = {
|
|
290
|
+
intervalMs: 5_000,
|
|
291
|
+
timeoutMs: 2_000,
|
|
292
|
+
ttlMs: 20_000,
|
|
293
|
+
stallAfterMs: 120_000,
|
|
294
|
+
backoff: { initialMs: 5_000, maxMs: 60_000, factor: 2 }
|
|
295
|
+
}
|
|
296
|
+
/** A known engine wait is a lifecycle fact, not missing progress.
|
|
297
|
+
* @category getters
|
|
298
|
+
* @since 1.0.0
|
|
299
|
+
*/
|
|
300
|
+
export const waitReason = (state: SubjectState, waitingReason?: string): ReasonCode | undefined =>
|
|
301
|
+
state !== "parked" ? undefined : waitingReason === "quota" ?
|
|
302
|
+
"quota-wait"
|
|
303
|
+
: waitingReason === "timer"
|
|
304
|
+
? "timer-wait"
|
|
305
|
+
: waitingReason === "event"
|
|
306
|
+
? "event-wait"
|
|
307
|
+
: undefined
|
|
308
|
+
/** Flow default observes lifecycle without inventing semantic activity.
|
|
309
|
+
* @category constants
|
|
310
|
+
* @since 1.0.0
|
|
311
|
+
*/
|
|
312
|
+
export const lifecycleRunChecker: HealthChecker = {
|
|
313
|
+
id: "lifecycle.run",
|
|
314
|
+
probe: (context) =>
|
|
315
|
+
Effect.succeed({ activity: "unknown", reason: waitReason(context.state, context.summary?.waitingReason) ?? "ok" })
|
|
316
|
+
}
|
|
317
|
+
/** Session default never interprets silence or output movement as task activity.
|
|
318
|
+
* @category constants
|
|
319
|
+
* @since 1.0.0
|
|
320
|
+
*/
|
|
321
|
+
export const lifecycleSessionChecker: HealthChecker = {
|
|
322
|
+
id: "lifecycle.session",
|
|
323
|
+
probe: () => Effect.succeed({ activity: "unknown", reason: "ok" })
|
|
324
|
+
}
|
|
325
|
+
const hostCheckers = (evaluator: Evaluator.Evaluator | undefined): ReadonlyArray<HealthChecker<any>> => [
|
|
326
|
+
makeJevSessionChecker({ evaluator })
|
|
327
|
+
]
|
|
328
|
+
/** Checkers every host may bind by id, beyond the lifecycle default it always has.
|
|
329
|
+
*
|
|
330
|
+
* Registration is not a binding. A host opts in by naming one of these as a
|
|
331
|
+
* `HealthBinding.checkerId`; a host that names none keeps the lifecycle
|
|
332
|
+
* default, and no entry here is ever consulted for an unbound subject.
|
|
333
|
+
*
|
|
334
|
+
* @category constants
|
|
335
|
+
* @since 1.0.0
|
|
336
|
+
*/
|
|
337
|
+
export const registeredCheckers: ReadonlyArray<HealthChecker<any>> = hostCheckers(undefined)
|
|
338
|
+
const bounded = (value: number, min: number, max: number) => Number.isSafeInteger(value) && value >= min && value <= max
|
|
339
|
+
const invalid = (reason: HealthConfigurationError["reason"]): never => {
|
|
340
|
+
throw new HealthConfigurationError({ reason })
|
|
341
|
+
}
|
|
342
|
+
/** Admit a host registry and all configured bindings. Unknown checker ids use the lifecycle default.
|
|
343
|
+
* @category constructors
|
|
344
|
+
* @since 1.0.0
|
|
345
|
+
*/
|
|
346
|
+
export const makeRegistry = (
|
|
347
|
+
config: HealthConfig = {},
|
|
348
|
+
kind: "run" | "session" = "run",
|
|
349
|
+
evaluator?: Evaluator.Evaluator
|
|
350
|
+
): Registry => {
|
|
351
|
+
const fallback = kind === "run" ? lifecycleRunChecker : lifecycleSessionChecker
|
|
352
|
+
const checkers = new Map<string, ResolvedCheck["checker"]>([
|
|
353
|
+
[fallback.id, fallback],
|
|
354
|
+
...hostCheckers(evaluator).map((checker) => [checker.id, checker] as const)
|
|
355
|
+
])
|
|
356
|
+
for (const checker of config.checkers ?? []) {
|
|
357
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9._/-]{0,127}$/.test(checker.id) || checkers.has(checker.id)) invalid("invalid-checker")
|
|
358
|
+
checkers.set(checker.id, checker)
|
|
359
|
+
}
|
|
360
|
+
const resolve = (binding?: HealthBinding): ResolvedCheck => {
|
|
361
|
+
const checker = binding === undefined ? fallback : checkers.get(binding.checkerId) ?? fallback
|
|
362
|
+
const merged = { ...defaultPolicy, ...checker.defaults, ...binding?.policy }
|
|
363
|
+
if (merged.backoff == null || typeof merged.backoff !== "object") invalid("invalid-policy")
|
|
364
|
+
const policy = { ...merged, backoff: { ...merged.backoff } }
|
|
365
|
+
if (
|
|
366
|
+
!bounded(policy.intervalMs, 10, 3_600_000) || !bounded(policy.timeoutMs, 1, 60_000) ||
|
|
367
|
+
!bounded(policy.ttlMs, policy.timeoutMs, 86_400_000) ||
|
|
368
|
+
!bounded(policy.stallAfterMs, policy.intervalMs, 86_400_000) ||
|
|
369
|
+
!bounded(policy.backoff.initialMs, 10, 3_600_000) ||
|
|
370
|
+
!bounded(policy.backoff.maxMs, policy.backoff.initialMs, 3_600_000) ||
|
|
371
|
+
!Number.isFinite(policy.backoff.factor) || policy.backoff.factor < 1 || policy.backoff.factor > 10
|
|
372
|
+
) invalid("invalid-policy")
|
|
373
|
+
let decoded: unknown = undefined
|
|
374
|
+
if (checker !== fallback || binding?.checkerId === fallback.id) {
|
|
375
|
+
if (checker.configSchema !== undefined) {
|
|
376
|
+
try {
|
|
377
|
+
decoded = Schema.decodeUnknownSync(checker.configSchema)(binding?.config)
|
|
378
|
+
} catch {
|
|
379
|
+
invalid("invalid-config")
|
|
380
|
+
}
|
|
381
|
+
} else if (binding?.config !== undefined) invalid("invalid-config")
|
|
382
|
+
}
|
|
383
|
+
return { checker, config: decoded, policy, exposeOutput: binding?.exposeOutput === true }
|
|
384
|
+
}
|
|
385
|
+
const defaults = resolve()
|
|
386
|
+
const bindings = new Map(Object.entries(config.bindings ?? {}).map(([key, value]) => [key, resolve(value)]))
|
|
387
|
+
const maxSubjects = config.limits?.maxSubjects ?? 128
|
|
388
|
+
const maxConcurrentProbes = config.limits?.maxConcurrentProbes ?? 8
|
|
389
|
+
if (!bounded(maxSubjects, 1, 500) || !bounded(maxConcurrentProbes, 1, 64)) invalid("invalid-policy")
|
|
390
|
+
return { resolve: (key) => bindings.get(key) ?? defaults, limits: { maxSubjects, maxConcurrentProbes } }
|
|
391
|
+
}
|
|
392
|
+
/** Failure delay, bounded even when a subject has failed for a long time.
|
|
393
|
+
* @category getters
|
|
394
|
+
* @since 1.0.0
|
|
395
|
+
*/
|
|
396
|
+
export const nextDelay = (policy: CheckPolicy, consecutiveFailures: number): number =>
|
|
397
|
+
consecutiveFailures <= 0 ? policy.intervalMs : Math.max(
|
|
398
|
+
policy.intervalMs,
|
|
399
|
+
Math.min(
|
|
400
|
+
policy.backoff.maxMs,
|
|
401
|
+
policy.backoff.initialMs * policy.backoff.factor ** Math.min(consecutiveFailures - 1, 32)
|
|
402
|
+
)
|
|
403
|
+
)
|
|
404
|
+
/** Host-only ordering stamp captured before a probe.
|
|
405
|
+
* @category models
|
|
406
|
+
* @since 1.0.0
|
|
407
|
+
*/
|
|
408
|
+
export interface ObservationStamp {
|
|
409
|
+
readonly monitorId: string
|
|
410
|
+
readonly incarnation: string
|
|
411
|
+
readonly evidenceSeq: number
|
|
412
|
+
}
|
|
413
|
+
const outcomes = Metric.counter("smithers.health.probes")
|
|
414
|
+
const durations = Metric.histogram("smithers.health.probe_duration_ms", {
|
|
415
|
+
boundaries: [1, 10, 100, 1_000, 5_000, 60_000]
|
|
416
|
+
})
|
|
417
|
+
const decodeReport = Schema.decodeUnknownSync(ProbeReport)
|
|
418
|
+
/** Evaluate one trusted checker with bounded timeout and safe failure values. Scope interruption propagates.
|
|
419
|
+
* The host must still compare current ownership/lifecycle before durably publishing the returned observation.
|
|
420
|
+
* @category constructors
|
|
421
|
+
* @since 1.0.0
|
|
422
|
+
*/
|
|
423
|
+
export const evaluate = (
|
|
424
|
+
check: ResolvedCheck,
|
|
425
|
+
context: ProbeContext,
|
|
426
|
+
stamp: ObservationStamp
|
|
427
|
+
): Effect.Effect<HealthObservation> =>
|
|
428
|
+
Effect.gen(function*() {
|
|
429
|
+
const subjectId = context.subjectId
|
|
430
|
+
const state = context.state
|
|
431
|
+
const captured = { ...stamp }
|
|
432
|
+
const started = yield* Effect.clockWith((clock) => clock.currentTimeMillis)
|
|
433
|
+
const result = yield* Effect.suspend(() => check.checker.probe(context, check.config)).pipe(
|
|
434
|
+
Effect.flatMap((candidate) => Effect.try({ try: () => decodeReport(candidate), catch: () => "invalid-report" })),
|
|
435
|
+
Effect.map((report) => ({ outcome: "ok" as const, report })),
|
|
436
|
+
Effect.catchCause((cause) =>
|
|
437
|
+
Cause.hasInterruptsOnly(cause)
|
|
438
|
+
? Effect.interrupt
|
|
439
|
+
: Effect.succeed({ outcome: "error" as const, reason: "probe-error" as const })
|
|
440
|
+
),
|
|
441
|
+
Effect.timeoutOrElse({
|
|
442
|
+
duration: check.policy.timeoutMs,
|
|
443
|
+
orElse: () => Effect.succeed({ outcome: "timeout" as const, reason: "probe-timeout" as const })
|
|
444
|
+
}),
|
|
445
|
+
Effect.tap((outcome) =>
|
|
446
|
+
Effect.annotateCurrentSpan({
|
|
447
|
+
outcome: outcome.outcome,
|
|
448
|
+
activity: "report" in outcome ? outcome.report.activity : "unknown"
|
|
449
|
+
})
|
|
450
|
+
),
|
|
451
|
+
Effect.withSpan("smithers.health.probe", {
|
|
452
|
+
attributes: {
|
|
453
|
+
subjectKind: context.session === undefined ? "run" : "session",
|
|
454
|
+
subjectId,
|
|
455
|
+
checkerId: check.checker.id,
|
|
456
|
+
monitorId: captured.monitorId
|
|
457
|
+
}
|
|
458
|
+
})
|
|
459
|
+
)
|
|
460
|
+
const observedAt = yield* Effect.clockWith((clock) => clock.currentTimeMillis)
|
|
461
|
+
const durationMs = Math.max(0, observedAt - started)
|
|
462
|
+
yield* Metric.update(outcomes.pipe(Metric.withAttributes({ outcome: result.outcome })), 1)
|
|
463
|
+
yield* Metric.update(durations, durationMs)
|
|
464
|
+
return {
|
|
465
|
+
subjectId,
|
|
466
|
+
state,
|
|
467
|
+
checkerId: check.checker.id,
|
|
468
|
+
...captured,
|
|
469
|
+
observedAt,
|
|
470
|
+
expiresAt: observedAt + check.policy.ttlMs,
|
|
471
|
+
durationMs,
|
|
472
|
+
...result
|
|
473
|
+
}
|
|
474
|
+
})
|
|
475
|
+
|
|
476
|
+
/** Select evidence for the current opaque owner incarnation, breaking equal evidence ties by durable sequence.
|
|
477
|
+
* @category projections
|
|
478
|
+
* @since 1.0.0
|
|
479
|
+
*/
|
|
480
|
+
export const latestObservation = (
|
|
481
|
+
candidates: ReadonlyArray<RecordedObservation>,
|
|
482
|
+
subjectId: string,
|
|
483
|
+
incarnation: string
|
|
484
|
+
): RecordedObservation | undefined => {
|
|
485
|
+
let selected: RecordedObservation | undefined
|
|
486
|
+
for (const candidate of candidates) {
|
|
487
|
+
const observation = candidate.observation
|
|
488
|
+
if (
|
|
489
|
+
observation.subjectId !== subjectId || observation.incarnation !== incarnation ||
|
|
490
|
+
observation.outcome === "discarded"
|
|
491
|
+
) continue
|
|
492
|
+
if (
|
|
493
|
+
selected === undefined || observation.evidenceSeq > selected.observation.evidenceSeq ||
|
|
494
|
+
observation.evidenceSeq === selected.observation.evidenceSeq && candidate.sequence > selected.sequence
|
|
495
|
+
) selected = candidate
|
|
496
|
+
}
|
|
497
|
+
return selected
|
|
498
|
+
}
|
|
499
|
+
/** Authoritative facts supplied independently of checker output.
|
|
500
|
+
* @category models
|
|
501
|
+
* @since 1.0.0
|
|
502
|
+
*/
|
|
503
|
+
export interface RollupInput {
|
|
504
|
+
readonly subjectId: string
|
|
505
|
+
readonly state: SubjectState
|
|
506
|
+
readonly incarnation: string
|
|
507
|
+
readonly waitingReason?: string | undefined
|
|
508
|
+
readonly exitCode?: number | null | undefined
|
|
509
|
+
readonly baseHealth?: HealthState | undefined
|
|
510
|
+
readonly latest?: RecordedObservation | undefined
|
|
511
|
+
readonly evidenceSeq?: number | undefined
|
|
512
|
+
readonly now: number
|
|
513
|
+
readonly updatedAt: number
|
|
514
|
+
}
|
|
515
|
+
/** Pure precedence: lifecycle and approvals win, and stale probes never establish activity.
|
|
516
|
+
* @category projections
|
|
517
|
+
* @since 1.0.0
|
|
518
|
+
*/
|
|
519
|
+
export const rollup = (input: RollupInput): StatusRollup => {
|
|
520
|
+
const latest = input.latest
|
|
521
|
+
const reading = latest?.observation
|
|
522
|
+
const freshness = reading === undefined ?
|
|
523
|
+
"unobserved" :
|
|
524
|
+
reading.subjectId !== input.subjectId || reading.incarnation !== input.incarnation ||
|
|
525
|
+
reading.state !== input.state ||
|
|
526
|
+
reading.outcome !== "ok" || reading.report === undefined || input.now < reading.observedAt ||
|
|
527
|
+
input.now >= reading.expiresAt ||
|
|
528
|
+
(input.evidenceSeq !== undefined && reading.evidenceSeq < input.evidenceSeq) ?
|
|
529
|
+
"stale" :
|
|
530
|
+
"fresh"
|
|
531
|
+
const terminal = input.state === "completed" || input.state === "failed" || input.state === "cancelled" ||
|
|
532
|
+
input.state === "exited"
|
|
533
|
+
const approval = input.state === "waiting-approval" || input.state === "parked" && input.waitingReason === "approval"
|
|
534
|
+
// Released executions stay parked until an operator resumes the run (#3328).
|
|
535
|
+
const released = input.state === "parked" && input.waitingReason === "released"
|
|
536
|
+
const waiting = waitReason(input.state, input.waitingReason)
|
|
537
|
+
let activity: typeof Activity.Type = freshness === "fresh" && !terminal && !approval && !released
|
|
538
|
+
? reading!.report!.activity
|
|
539
|
+
: "unknown"
|
|
540
|
+
let health: HealthState = input.state === "failed" ? "failing" : input.state === "exited" ?
|
|
541
|
+
input.exitCode === 0 ? "healthy" : input.exitCode == null ? "unknown" : "failing" :
|
|
542
|
+
terminal ?
|
|
543
|
+
"healthy" :
|
|
544
|
+
approval || released ?
|
|
545
|
+
"awaiting-human" :
|
|
546
|
+
waiting !== undefined ?
|
|
547
|
+
"healthy" :
|
|
548
|
+
input.state === "parked" && input.waitingReason === undefined ?
|
|
549
|
+
"awaiting-human" :
|
|
550
|
+
freshness !== "fresh"
|
|
551
|
+
? "unknown"
|
|
552
|
+
: input.baseHealth ?? "unknown"
|
|
553
|
+
const reason = approval
|
|
554
|
+
? "awaiting-reply"
|
|
555
|
+
: released
|
|
556
|
+
? "released"
|
|
557
|
+
: waiting ?? (freshness === "fresh" ? reading?.report?.reason : reading?.reason)
|
|
558
|
+
if (!terminal && !approval && !released && waiting === undefined && freshness === "fresh") {
|
|
559
|
+
if (reason === "unreachable" && health !== "awaiting-human") health = "failing"
|
|
560
|
+
else if (reason === "no-progress" && health === "healthy") health = "stalled"
|
|
561
|
+
}
|
|
562
|
+
if (waiting !== undefined) activity = "unknown"
|
|
563
|
+
const attention: typeof Attention.Type = approval ? "awaiting-approval" : released ?
|
|
564
|
+
"needs-resume" :
|
|
565
|
+
activity === "needs-input" ?
|
|
566
|
+
"needs-input" :
|
|
567
|
+
["stalled", "wedged-node", "runaway-loop", "failing"].includes(health)
|
|
568
|
+
? "unhealthy"
|
|
569
|
+
: "none"
|
|
570
|
+
return {
|
|
571
|
+
subjectId: input.subjectId,
|
|
572
|
+
state: input.state,
|
|
573
|
+
activity,
|
|
574
|
+
health,
|
|
575
|
+
attention,
|
|
576
|
+
freshness,
|
|
577
|
+
...(reason === undefined ? {} : { reason }),
|
|
578
|
+
...(reading === undefined ? {} : {
|
|
579
|
+
provenance: {
|
|
580
|
+
checkerId: reading.checkerId,
|
|
581
|
+
monitorId: reading.monitorId,
|
|
582
|
+
observedAt: reading.observedAt,
|
|
583
|
+
expiresAt: reading.expiresAt,
|
|
584
|
+
evidenceSeq: reading.evidenceSeq,
|
|
585
|
+
incarnation: reading.incarnation,
|
|
586
|
+
version: latest!.sequence
|
|
587
|
+
}
|
|
588
|
+
}),
|
|
589
|
+
updatedAt: Math.max(input.updatedAt, reading?.observedAt ?? 0)
|
|
590
|
+
}
|
|
591
|
+
}
|