@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
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AES-256-GCM credential encryption over the Web Crypto API.
|
|
3
|
+
*
|
|
4
|
+
* One adapter serves both hosts the project supports: `globalThis.crypto.subtle`
|
|
5
|
+
* is the browser's own API and has been Node's since v19, so this module
|
|
6
|
+
* imports nothing from `node:*` and still runs unmodified on the server. A host
|
|
7
|
+
* without Web Crypto, such as an old runtime or a locked-down worker, fails
|
|
8
|
+
* with the typed `Unavailable`, never a defect. A key that is not 32
|
|
9
|
+
* base64-encoded bytes fails with `InvalidInput`. A record that does not open
|
|
10
|
+
* fails with `PersistenceError` on operation `credential.open`, which names
|
|
11
|
+
* whether the stored nonce was malformed or the ciphertext failed
|
|
12
|
+
* authentication (a different key, changed metadata, or tampered bytes).
|
|
13
|
+
*
|
|
14
|
+
* The key is host-managed and supplied at layer construction. It is held as a
|
|
15
|
+
* non-extractable `CryptoKey`, so it cannot be read back out of the cipher, and
|
|
16
|
+
* it never reaches `CredentialStore`.
|
|
17
|
+
*
|
|
18
|
+
* @since 0.1.0
|
|
19
|
+
*/
|
|
20
|
+
import { canonicalize } from "@smthrs/canonical";
|
|
21
|
+
import { Effect, Layer, Redacted } from "effect";
|
|
22
|
+
import { InvalidInput, PersistenceError } from "./ControlError.js";
|
|
23
|
+
import * as CredentialCipher from "./CredentialCipher.js";
|
|
24
|
+
const algorithm = "AES-GCM";
|
|
25
|
+
const nonceBytes = 12;
|
|
26
|
+
const contextFormatVersion = 1;
|
|
27
|
+
const toBase64 = (bytes) => {
|
|
28
|
+
let binary = "";
|
|
29
|
+
for (const byte of bytes)
|
|
30
|
+
binary += String.fromCharCode(byte);
|
|
31
|
+
return btoa(binary);
|
|
32
|
+
};
|
|
33
|
+
const fromBase64 = (value) => {
|
|
34
|
+
const binary = atob(value);
|
|
35
|
+
const bytes = new Uint8Array(binary.length);
|
|
36
|
+
for (let index = 0; index < binary.length; index++)
|
|
37
|
+
bytes[index] = binary.charCodeAt(index);
|
|
38
|
+
return bytes;
|
|
39
|
+
};
|
|
40
|
+
const subtle = () => Effect.suspend(() => {
|
|
41
|
+
const available = globalThis.crypto;
|
|
42
|
+
return available?.subtle === undefined
|
|
43
|
+
? Effect.fail(CredentialCipher.unavailable())
|
|
44
|
+
: Effect.succeed(available.subtle);
|
|
45
|
+
});
|
|
46
|
+
const randomNonce = () => Effect.suspend(() => {
|
|
47
|
+
const available = globalThis.crypto;
|
|
48
|
+
return available?.getRandomValues === undefined
|
|
49
|
+
? Effect.fail(CredentialCipher.unavailable())
|
|
50
|
+
: Effect.succeed(available.getRandomValues(new Uint8Array(nonceBytes)));
|
|
51
|
+
});
|
|
52
|
+
const encodeContext = (context) => Effect.try({
|
|
53
|
+
try: () => {
|
|
54
|
+
// Canonical JSON names and escapes each field, so embedded delimiters
|
|
55
|
+
// cannot make two metadata tuples authenticate as the same bytes. The
|
|
56
|
+
// explicit format version makes a future byte contract distinguishable.
|
|
57
|
+
const document = canonicalize({
|
|
58
|
+
formatVersion: contextFormatVersion,
|
|
59
|
+
id: context.id,
|
|
60
|
+
name: context.name,
|
|
61
|
+
version: context.version
|
|
62
|
+
});
|
|
63
|
+
return new TextEncoder().encode(document);
|
|
64
|
+
},
|
|
65
|
+
catch: CredentialCipher.unavailable
|
|
66
|
+
});
|
|
67
|
+
const invalidKey = () => new InvalidInput({ issue: "Credential key must be 32 base64-encoded bytes" });
|
|
68
|
+
const unopenable = (reason, cause) => new PersistenceError({
|
|
69
|
+
operation: "credential.open",
|
|
70
|
+
message: reason === "malformed_nonce"
|
|
71
|
+
? "Stored credential nonce is malformed"
|
|
72
|
+
: "Credential failed authentication: a different key, changed metadata, or tampered ciphertext",
|
|
73
|
+
cause
|
|
74
|
+
});
|
|
75
|
+
/**
|
|
76
|
+
* Imports the host key and constructs the cipher.
|
|
77
|
+
*
|
|
78
|
+
* @category constructors
|
|
79
|
+
* @since 0.1.0
|
|
80
|
+
*/
|
|
81
|
+
export const make = (options) => Effect.gen(function* () {
|
|
82
|
+
const crypto = yield* subtle();
|
|
83
|
+
const raw = yield* Effect.try({
|
|
84
|
+
try: () => fromBase64(Redacted.value(options.key)),
|
|
85
|
+
catch: invalidKey
|
|
86
|
+
});
|
|
87
|
+
if (raw.length !== 32)
|
|
88
|
+
return yield* Effect.fail(invalidKey());
|
|
89
|
+
const key = yield* Effect.tryPromise({
|
|
90
|
+
try: () => crypto.importKey("raw", raw, algorithm, false, ["encrypt", "decrypt"]),
|
|
91
|
+
catch: CredentialCipher.unavailable
|
|
92
|
+
});
|
|
93
|
+
return CredentialCipher.make({
|
|
94
|
+
seal: Effect.fn("WebCryptoCipher.seal")(function* (plaintext, context) {
|
|
95
|
+
const additionalData = yield* encodeContext(context);
|
|
96
|
+
const nonce = yield* randomNonce();
|
|
97
|
+
const ciphertext = yield* Effect.tryPromise({
|
|
98
|
+
try: () => crypto.encrypt({ name: algorithm, iv: nonce, additionalData: additionalData }, key, new TextEncoder().encode(Redacted.value(plaintext))),
|
|
99
|
+
catch: CredentialCipher.unavailable
|
|
100
|
+
});
|
|
101
|
+
return { ciphertext: toBase64(new Uint8Array(ciphertext)), nonce: toBase64(nonce) };
|
|
102
|
+
}),
|
|
103
|
+
open: Effect.fn("WebCryptoCipher.open")(function* (sealed, context) {
|
|
104
|
+
const additionalData = yield* encodeContext(context);
|
|
105
|
+
const plaintext = yield* Effect.try({
|
|
106
|
+
try: () => fromBase64(sealed.nonce),
|
|
107
|
+
catch: (cause) => unopenable("malformed_nonce", cause)
|
|
108
|
+
}).pipe(Effect.flatMap((nonce) => Effect.tryPromise({
|
|
109
|
+
try: () => crypto.decrypt({ name: algorithm, iv: nonce, additionalData: additionalData }, key, fromBase64(sealed.ciphertext)),
|
|
110
|
+
catch: (cause) => unopenable("authentication_failed", cause)
|
|
111
|
+
})), Effect.tapError((error) => Effect.logWarning({ message: error.message, operation: error.operation, credentialId: context.id })));
|
|
112
|
+
return Redacted.make(new TextDecoder().decode(plaintext));
|
|
113
|
+
})
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
/**
|
|
117
|
+
* Provides AES-256-GCM credential encryption under a host-managed key.
|
|
118
|
+
*
|
|
119
|
+
* @category layers
|
|
120
|
+
* @since 0.1.0
|
|
121
|
+
*/
|
|
122
|
+
export const layer = (options) => Layer.effect(CredentialCipher.CredentialCipher)(make(options));
|
|
123
|
+
//# sourceMappingURL=WebCryptoCipher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WebCryptoCipher.js","sourceRoot":"","sources":["../../src/WebCryptoCipher.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * AES-256-GCM credential encryption over the Web Crypto API.\n *\n * One adapter serves both hosts the project supports: `globalThis.crypto.subtle`\n * is the browser's own API and has been Node's since v19, so this module\n * imports nothing from `node:*` and still runs unmodified on the server. A host\n * without Web Crypto, such as an old runtime or a locked-down worker, fails\n * with the typed `Unavailable`, never a defect. A key that is not 32\n * base64-encoded bytes fails with `InvalidInput`. A record that does not open\n * fails with `PersistenceError` on operation `credential.open`, which names\n * whether the stored nonce was malformed or the ciphertext failed\n * authentication (a different key, changed metadata, or tampered bytes).\n *\n * The key is host-managed and supplied at layer construction. It is held as a\n * non-extractable `CryptoKey`, so it cannot be read back out of the cipher, and\n * it never reaches `CredentialStore`.\n *\n * @since 0.1.0\n */\n\nimport { canonicalize } from \"@smthrs/canonical\"\nimport { Effect, Layer, Redacted } from \"effect\"\nimport { InvalidInput, PersistenceError, type Unavailable } from \"./ControlError.ts\"\nimport * as CredentialCipher from \"./CredentialCipher.ts\"\n\nconst algorithm = \"AES-GCM\"\nconst nonceBytes = 12\nconst contextFormatVersion = 1\n\nconst toBase64 = (bytes: Uint8Array): string => {\n let binary = \"\"\n for (const byte of bytes) binary += String.fromCharCode(byte)\n return btoa(binary)\n}\n\nconst fromBase64 = (value: string): Uint8Array<ArrayBuffer> => {\n const binary = atob(value)\n const bytes = new Uint8Array(binary.length)\n for (let index = 0; index < binary.length; index++) bytes[index] = binary.charCodeAt(index)\n return bytes\n}\n\nconst subtle = (): Effect.Effect<SubtleCrypto, Unavailable> =>\n Effect.suspend(() => {\n const available = globalThis.crypto as Crypto | undefined\n return available?.subtle === undefined\n ? Effect.fail(CredentialCipher.unavailable())\n : Effect.succeed(available.subtle)\n })\n\nconst randomNonce = (): Effect.Effect<Uint8Array<ArrayBuffer>, Unavailable> =>\n Effect.suspend(() => {\n const available = globalThis.crypto as Crypto | undefined\n return available?.getRandomValues === undefined\n ? Effect.fail(CredentialCipher.unavailable())\n : Effect.succeed(available.getRandomValues(new Uint8Array(nonceBytes)))\n })\n\nconst encodeContext = (context: CredentialCipher.Context): Effect.Effect<Uint8Array<ArrayBuffer>, Unavailable> =>\n Effect.try({\n try: () => {\n // Canonical JSON names and escapes each field, so embedded delimiters\n // cannot make two metadata tuples authenticate as the same bytes. The\n // explicit format version makes a future byte contract distinguishable.\n const document = canonicalize({\n formatVersion: contextFormatVersion,\n id: context.id,\n name: context.name,\n version: context.version\n })\n return new TextEncoder().encode(document)\n },\n catch: CredentialCipher.unavailable\n })\n\nconst invalidKey = (): InvalidInput => new InvalidInput({ issue: \"Credential key must be 32 base64-encoded bytes\" })\n\nconst unopenable = (reason: \"malformed_nonce\" | \"authentication_failed\", cause: unknown): PersistenceError =>\n new PersistenceError({\n operation: \"credential.open\",\n message: reason === \"malformed_nonce\"\n ? \"Stored credential nonce is malformed\"\n : \"Credential failed authentication: a different key, changed metadata, or tampered ciphertext\",\n cause\n })\n\n/**\n * Host-managed key material for the cipher.\n *\n * `key` is 32 raw bytes, base64-encoded, held redacted so it cannot be printed\n * or serialized by accident.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Options {\n readonly key: Redacted.Redacted<string>\n}\n\n/**\n * Imports the host key and constructs the cipher.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const make = (options: Options): Effect.Effect<CredentialCipher.Service, Unavailable | InvalidInput> =>\n Effect.gen(function*() {\n const crypto = yield* subtle()\n const raw = yield* Effect.try({\n try: () => fromBase64(Redacted.value(options.key)),\n catch: invalidKey\n })\n if (raw.length !== 32) return yield* Effect.fail(invalidKey())\n const key = yield* Effect.tryPromise({\n try: () => crypto.importKey(\"raw\", raw, algorithm, false, [\"encrypt\", \"decrypt\"]),\n catch: CredentialCipher.unavailable\n })\n\n return CredentialCipher.make({\n seal: Effect.fn(\"WebCryptoCipher.seal\")(function*(plaintext, context) {\n const additionalData = yield* encodeContext(context)\n const nonce = yield* randomNonce()\n const ciphertext = yield* Effect.tryPromise({\n try: () =>\n crypto.encrypt(\n { name: algorithm, iv: nonce, additionalData: additionalData },\n key,\n new TextEncoder().encode(Redacted.value(plaintext))\n ),\n catch: CredentialCipher.unavailable\n })\n return { ciphertext: toBase64(new Uint8Array(ciphertext)), nonce: toBase64(nonce) }\n }),\n open: Effect.fn(\"WebCryptoCipher.open\")(function*(sealed, context) {\n const additionalData = yield* encodeContext(context)\n const plaintext = yield* Effect.try({\n try: () => fromBase64(sealed.nonce),\n catch: (cause) => unopenable(\"malformed_nonce\", cause)\n }).pipe(\n Effect.flatMap((nonce) =>\n Effect.tryPromise({\n try: () =>\n crypto.decrypt(\n { name: algorithm, iv: nonce, additionalData: additionalData },\n key,\n fromBase64(sealed.ciphertext)\n ),\n catch: (cause) => unopenable(\"authentication_failed\", cause)\n })\n ),\n Effect.tapError((error) =>\n Effect.logWarning({ message: error.message, operation: error.operation, credentialId: context.id })\n )\n )\n return Redacted.make(new TextDecoder().decode(plaintext))\n })\n })\n })\n\n/**\n * Provides AES-256-GCM credential encryption under a host-managed key.\n *\n * @category layers\n * @since 0.1.0\n */\nexport const layer = (\n options: Options\n): Layer.Layer<CredentialCipher.CredentialCipher, Unavailable | InvalidInput> =>\n Layer.effect(CredentialCipher.CredentialCipher)(make(options))\n"]}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Webhook channel construction over Effect's transport-neutral HTTP request.
|
|
3
|
+
*
|
|
4
|
+
* @since 0.1.0
|
|
5
|
+
*/
|
|
6
|
+
import { Effect, type Redacted, Schema } from "effect";
|
|
7
|
+
import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest";
|
|
8
|
+
import { type Channel, Channels, type InboundResult, type RawInbound } from "./Channels.ts";
|
|
9
|
+
import { InvalidInput, type Unauthorized } from "./ControlError.ts";
|
|
10
|
+
import type { IdempotencyKey } from "./ControlSchema.ts";
|
|
11
|
+
import type { CredentialRef } from "./Credential.ts";
|
|
12
|
+
/**
|
|
13
|
+
* Signature verifier injected by a webhook transport.
|
|
14
|
+
*
|
|
15
|
+
* The credential is intentionally a redacted reference. Resolution belongs at
|
|
16
|
+
* the host adapter boundary, never in webhook input or its persisted record.
|
|
17
|
+
*
|
|
18
|
+
* @category models
|
|
19
|
+
* @since 0.1.0
|
|
20
|
+
*/
|
|
21
|
+
export type SignatureVerifier = (raw: RawInbound, credential: Redacted.Redacted<CredentialRef>) => Effect.Effect<void, Unauthorized>;
|
|
22
|
+
/**
|
|
23
|
+
* Configuration for a schema-declared webhook channel.
|
|
24
|
+
*
|
|
25
|
+
* @category models
|
|
26
|
+
* @since 0.1.0
|
|
27
|
+
*/
|
|
28
|
+
export interface Config<A> {
|
|
29
|
+
readonly name: string;
|
|
30
|
+
readonly schema: Schema.Schema<A>;
|
|
31
|
+
readonly credential: Redacted.Redacted<CredentialRef>;
|
|
32
|
+
readonly fingerprintHeaders?: Channel<A>["fingerprintHeaders"];
|
|
33
|
+
readonly verify: SignatureVerifier;
|
|
34
|
+
readonly map: (payload: A) => Effect.Effect<InboundResult, InvalidInput>;
|
|
35
|
+
readonly project: Channel<A>["project"];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Largest webhook request body one mount reads by default, in bytes.
|
|
39
|
+
*
|
|
40
|
+
* A webhook is the one control-plane ingress a bearer holder reaches with an
|
|
41
|
+
* arbitrary payload, and `handler` used to materialize whatever arrived before
|
|
42
|
+
* anything looked at its size. The default is deliberately smaller than the
|
|
43
|
+
* 4 MiB mutation-identity budget in `internal/mutationBoundary.ts`: a body that
|
|
44
|
+
* cannot become a durable mutation is refused at the door rather than copied,
|
|
45
|
+
* decoded, and refused later.
|
|
46
|
+
*
|
|
47
|
+
* @category constants
|
|
48
|
+
* @since 1.0.0-rc.0
|
|
49
|
+
*/
|
|
50
|
+
export declare const maximumBodyBytes: number;
|
|
51
|
+
/**
|
|
52
|
+
* Per-mount overrides for the webhook request handler.
|
|
53
|
+
*
|
|
54
|
+
* @category models
|
|
55
|
+
* @since 1.0.0-rc.0
|
|
56
|
+
*/
|
|
57
|
+
export interface HandlerOptions {
|
|
58
|
+
/** Body ceiling for this mount. Defaults to `maximumBodyBytes`. */
|
|
59
|
+
readonly maximumBodyBytes?: number | undefined;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Builds a webhook channel. Verification receives raw bytes and headers before
|
|
63
|
+
* JSON parsing and schema decoding.
|
|
64
|
+
*
|
|
65
|
+
* @category constructors
|
|
66
|
+
* @since 0.1.0
|
|
67
|
+
*/
|
|
68
|
+
export declare const make: <A>(config: Config<A>) => Channel<A>;
|
|
69
|
+
/**
|
|
70
|
+
* Reads an abstract Effect HTTP request and dispatches it through Channels.
|
|
71
|
+
* This is mountable by any Effect HTTP host; it contains no Node transport
|
|
72
|
+
* dependencies.
|
|
73
|
+
*
|
|
74
|
+
* The body is bounded twice. A `content-length` over the limit is refused
|
|
75
|
+
* before the body is read at all, so a declared flood costs nothing; the
|
|
76
|
+
* measured length is checked before retaining each streamed chunk. Reading
|
|
77
|
+
* stops at the first chunk exceeding the limit, including when the caller
|
|
78
|
+
* understates or omits the length. Both refusals are `InvalidInput` naming the
|
|
79
|
+
* two byte counts and no body content.
|
|
80
|
+
*
|
|
81
|
+
* @category handlers
|
|
82
|
+
* @since 0.1.0
|
|
83
|
+
*/
|
|
84
|
+
export declare const handler: (channel: string, idempotencyKey: IdempotencyKey, options?: HandlerOptions | undefined) => Effect.Effect<{
|
|
85
|
+
readonly _tag: "Accepted";
|
|
86
|
+
readonly receiptId: string;
|
|
87
|
+
readonly runId?: string | undefined;
|
|
88
|
+
readonly inputConsumed?: boolean | undefined;
|
|
89
|
+
readonly inputBody?: string | undefined;
|
|
90
|
+
readonly handedTo?: {
|
|
91
|
+
readonly hostId: string;
|
|
92
|
+
readonly pid: number;
|
|
93
|
+
} | undefined;
|
|
94
|
+
} | {
|
|
95
|
+
readonly _tag: "AlreadyApplied";
|
|
96
|
+
readonly receiptId: string;
|
|
97
|
+
readonly runId?: string | undefined;
|
|
98
|
+
readonly inputConsumed?: boolean | undefined;
|
|
99
|
+
readonly inputBody?: string | undefined;
|
|
100
|
+
} | {
|
|
101
|
+
readonly _tag: "Parked";
|
|
102
|
+
readonly receiptId: string;
|
|
103
|
+
readonly planId: string;
|
|
104
|
+
readonly status: "waiting-approval";
|
|
105
|
+
} | {
|
|
106
|
+
readonly _tag: "Conflict";
|
|
107
|
+
readonly message: string;
|
|
108
|
+
} | {
|
|
109
|
+
readonly _tag: "Terminal";
|
|
110
|
+
readonly runId: string;
|
|
111
|
+
readonly status: "running" | "completed" | "failed" | "cancelled" | "accepted" | "parked" | "waiting-approval";
|
|
112
|
+
}, import("@smthrs/notifications/NotificationQueue").NotificationError | import("./ControlError.ts").RunNotFound | import("./ControlError.ts").PlanNotFound | import("./ControlError.ts").PlanDenied | import("./ControlError.ts").FlowNotFound | import("./ControlError.ts").PlanDigestMismatch | import("./ControlError.ts").EnvelopeMismatch | import("./ControlError.ts").ClaimLost | import("./ControlError.ts").CodeDrift | import("./ControlError.ts").AlreadyResolved | InvalidInput | Unauthorized | import("./ControlError.ts").Unavailable | import("./ControlError.ts").TransportError | import("./ControlError.ts").PersistenceError | import("./ControlError.ts").LaunchFailed | import("./ControlError.ts").NoMatchingWait | import("./ControlError.ts").CredentialConflict | import("effect/unstable/http/HttpServerError").HttpServerError, Channels | HttpServerRequest.HttpServerRequest>;
|
|
113
|
+
//# sourceMappingURL=WebhookChannel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WebhookChannel.d.ts","sourceRoot":"","sources":["../../src/WebhookChannel.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,EAAE,KAAK,QAAQ,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAA;AAC9D,OAAO,KAAK,iBAAiB,MAAM,wCAAwC,CAAA;AAC3E,OAAO,EAAE,KAAK,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,KAAK,UAAU,EAAE,MAAM,eAAe,CAAA;AAC3F,OAAO,EAAE,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAA;AACnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAEpD;;;;;;;;GAQG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAC9B,GAAG,EAAE,UAAU,EACf,UAAU,EAAE,QAAQ,CAAC,QAAQ,CAAC,aAAa,CAAC,KACzC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC,CAAA;AAEtC;;;;;GAKG;AACH,MAAM,WAAW,MAAM,CAAC,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;IACjC,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAA;IACrD,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAA;IAC9D,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAA;IAClC,QAAQ,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,aAAa,EAAE,YAAY,CAAC,CAAA;IACxE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;CACxC;AAID;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,gBAAgB,QAAc,CAAA;AAE3C;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,mEAAmE;IACnE,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAC/C;AAoBD;;;;;;GAMG;AACH,eAAO,MAAM,IAAI,GAAI,CAAC,EAAE,QAAQ,MAAM,CAAC,CAAC,CAAC,KAAG,OAAO,CAAC,CAAC,CAmBpD,CAAA;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,OAAO,GAClB,SAAS,MAAM,EACf,gBAAgB,cAAc,EAC9B,UAAU,cAAc,GAAG,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;42BA8BlC,CAAA"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Webhook channel construction over Effect's transport-neutral HTTP request.
|
|
3
|
+
*
|
|
4
|
+
* @since 0.1.0
|
|
5
|
+
*/
|
|
6
|
+
import { Effect, Schema, Stream } from "effect";
|
|
7
|
+
import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest";
|
|
8
|
+
import { Channels } from "./Channels.js";
|
|
9
|
+
import { InvalidInput } from "./ControlError.js";
|
|
10
|
+
const invalidInput = (issue) => new InvalidInput({ issue });
|
|
11
|
+
/**
|
|
12
|
+
* Largest webhook request body one mount reads by default, in bytes.
|
|
13
|
+
*
|
|
14
|
+
* A webhook is the one control-plane ingress a bearer holder reaches with an
|
|
15
|
+
* arbitrary payload, and `handler` used to materialize whatever arrived before
|
|
16
|
+
* anything looked at its size. The default is deliberately smaller than the
|
|
17
|
+
* 4 MiB mutation-identity budget in `internal/mutationBoundary.ts`: a body that
|
|
18
|
+
* cannot become a durable mutation is refused at the door rather than copied,
|
|
19
|
+
* decoded, and refused later.
|
|
20
|
+
*
|
|
21
|
+
* @category constants
|
|
22
|
+
* @since 1.0.0-rc.0
|
|
23
|
+
*/
|
|
24
|
+
export const maximumBodyBytes = 1024 * 1024;
|
|
25
|
+
/**
|
|
26
|
+
* The declared body length, when the caller stated one this host can trust.
|
|
27
|
+
*
|
|
28
|
+
* Header names arrive lower-cased from Effect's HTTP layer, but a hand-built
|
|
29
|
+
* request is not obliged to, so the lookup folds case itself. An absent,
|
|
30
|
+
* non-numeric, fractional, or negative declaration is not evidence of
|
|
31
|
+
* anything: it falls through to the streaming measurement rather than being
|
|
32
|
+
* treated as zero.
|
|
33
|
+
*/
|
|
34
|
+
const declaredLength = (headers) => {
|
|
35
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
36
|
+
if (name.toLowerCase() !== "content-length" || value === undefined)
|
|
37
|
+
continue;
|
|
38
|
+
const parsed = Number(value.trim());
|
|
39
|
+
return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : undefined;
|
|
40
|
+
}
|
|
41
|
+
return undefined;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Builds a webhook channel. Verification receives raw bytes and headers before
|
|
45
|
+
* JSON parsing and schema decoding.
|
|
46
|
+
*
|
|
47
|
+
* @category constructors
|
|
48
|
+
* @since 0.1.0
|
|
49
|
+
*/
|
|
50
|
+
export const make = (config) => {
|
|
51
|
+
const decode = Schema.decodeUnknownEffect(config.schema);
|
|
52
|
+
return {
|
|
53
|
+
name: config.name,
|
|
54
|
+
schema: config.schema,
|
|
55
|
+
...(config.fingerprintHeaders === undefined ? {} : { fingerprintHeaders: config.fingerprintHeaders }),
|
|
56
|
+
verify: Effect.fn("WebhookChannel.verify")((raw) => config.verify(raw, config.credential)),
|
|
57
|
+
decode: Effect.fn("WebhookChannel.decode")((raw) => Effect.try({
|
|
58
|
+
try: () => JSON.parse(new TextDecoder().decode(raw.body)),
|
|
59
|
+
catch: () => invalidInput("invalid webhook JSON")
|
|
60
|
+
}).pipe(Effect.flatMap((json) => decode(json)), Effect.mapError((cause) => cause instanceof InvalidInput ? cause : invalidInput(String(cause))))),
|
|
61
|
+
map: Effect.fn("WebhookChannel.map")((payload) => config.map(payload)),
|
|
62
|
+
project: config.project
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Reads an abstract Effect HTTP request and dispatches it through Channels.
|
|
67
|
+
* This is mountable by any Effect HTTP host; it contains no Node transport
|
|
68
|
+
* dependencies.
|
|
69
|
+
*
|
|
70
|
+
* The body is bounded twice. A `content-length` over the limit is refused
|
|
71
|
+
* before the body is read at all, so a declared flood costs nothing; the
|
|
72
|
+
* measured length is checked before retaining each streamed chunk. Reading
|
|
73
|
+
* stops at the first chunk exceeding the limit, including when the caller
|
|
74
|
+
* understates or omits the length. Both refusals are `InvalidInput` naming the
|
|
75
|
+
* two byte counts and no body content.
|
|
76
|
+
*
|
|
77
|
+
* @category handlers
|
|
78
|
+
* @since 0.1.0
|
|
79
|
+
*/
|
|
80
|
+
export const handler = (channel, idempotencyKey, options) => Effect.gen(function* () {
|
|
81
|
+
const limit = options?.maximumBodyBytes ?? maximumBodyBytes;
|
|
82
|
+
const request = yield* HttpServerRequest.HttpServerRequest;
|
|
83
|
+
const declared = declaredLength(request.headers);
|
|
84
|
+
if (declared !== undefined && declared > limit) {
|
|
85
|
+
return yield* invalidInput(`webhook body: declared ${declared} bytes exceeds the ${limit} byte limit`);
|
|
86
|
+
}
|
|
87
|
+
const chunks = [];
|
|
88
|
+
let size = 0;
|
|
89
|
+
yield* Stream.runForEach(request.stream, (chunk) => Effect.gen(function* () {
|
|
90
|
+
size += chunk.byteLength;
|
|
91
|
+
if (size > limit) {
|
|
92
|
+
return yield* invalidInput(`webhook body: ${size} bytes exceeds the ${limit} byte limit`);
|
|
93
|
+
}
|
|
94
|
+
if (chunk.byteLength > 0)
|
|
95
|
+
chunks.push(chunk.slice());
|
|
96
|
+
}));
|
|
97
|
+
const body = new Uint8Array(size);
|
|
98
|
+
let offset = 0;
|
|
99
|
+
for (const chunk of chunks) {
|
|
100
|
+
body.set(chunk, offset);
|
|
101
|
+
offset += chunk.byteLength;
|
|
102
|
+
}
|
|
103
|
+
const channels = yield* Channels;
|
|
104
|
+
return yield* channels.ingest({
|
|
105
|
+
channel,
|
|
106
|
+
raw: { body, headers: request.headers, idempotencyKey }
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
//# sourceMappingURL=WebhookChannel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WebhookChannel.js","sourceRoot":"","sources":["../../src/WebhookChannel.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Webhook channel construction over Effect's transport-neutral HTTP request.\n *\n * @since 0.1.0\n */\n\nimport { Effect, type Redacted, Schema, Stream } from \"effect\"\nimport * as HttpServerRequest from \"effect/unstable/http/HttpServerRequest\"\nimport { type Channel, Channels, type InboundResult, type RawInbound } from \"./Channels.ts\"\nimport { InvalidInput, type Unauthorized } from \"./ControlError.ts\"\nimport type { IdempotencyKey } from \"./ControlSchema.ts\"\nimport type { CredentialRef } from \"./Credential.ts\"\n\n/**\n * Signature verifier injected by a webhook transport.\n *\n * The credential is intentionally a redacted reference. Resolution belongs at\n * the host adapter boundary, never in webhook input or its persisted record.\n *\n * @category models\n * @since 0.1.0\n */\nexport type SignatureVerifier = (\n raw: RawInbound,\n credential: Redacted.Redacted<CredentialRef>\n) => Effect.Effect<void, Unauthorized>\n\n/**\n * Configuration for a schema-declared webhook channel.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Config<A> {\n readonly name: string\n readonly schema: Schema.Schema<A>\n readonly credential: Redacted.Redacted<CredentialRef>\n readonly fingerprintHeaders?: Channel<A>[\"fingerprintHeaders\"]\n readonly verify: SignatureVerifier\n readonly map: (payload: A) => Effect.Effect<InboundResult, InvalidInput>\n readonly project: Channel<A>[\"project\"]\n}\n\nconst invalidInput = (issue: string): InvalidInput => new InvalidInput({ issue })\n\n/**\n * Largest webhook request body one mount reads by default, in bytes.\n *\n * A webhook is the one control-plane ingress a bearer holder reaches with an\n * arbitrary payload, and `handler` used to materialize whatever arrived before\n * anything looked at its size. The default is deliberately smaller than the\n * 4 MiB mutation-identity budget in `internal/mutationBoundary.ts`: a body that\n * cannot become a durable mutation is refused at the door rather than copied,\n * decoded, and refused later.\n *\n * @category constants\n * @since 1.0.0-rc.0\n */\nexport const maximumBodyBytes = 1024 * 1024\n\n/**\n * Per-mount overrides for the webhook request handler.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport interface HandlerOptions {\n /** Body ceiling for this mount. Defaults to `maximumBodyBytes`. */\n readonly maximumBodyBytes?: number | undefined\n}\n\n/**\n * The declared body length, when the caller stated one this host can trust.\n *\n * Header names arrive lower-cased from Effect's HTTP layer, but a hand-built\n * request is not obliged to, so the lookup folds case itself. An absent,\n * non-numeric, fractional, or negative declaration is not evidence of\n * anything: it falls through to the streaming measurement rather than being\n * treated as zero.\n */\nconst declaredLength = (headers: Readonly<Record<string, string | undefined>>): number | undefined => {\n for (const [name, value] of Object.entries(headers)) {\n if (name.toLowerCase() !== \"content-length\" || value === undefined) continue\n const parsed = Number(value.trim())\n return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : undefined\n }\n return undefined\n}\n\n/**\n * Builds a webhook channel. Verification receives raw bytes and headers before\n * JSON parsing and schema decoding.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const make = <A>(config: Config<A>): Channel<A> => {\n const decode = Schema.decodeUnknownEffect(config.schema)\n return {\n name: config.name,\n schema: config.schema,\n ...(config.fingerprintHeaders === undefined ? {} : { fingerprintHeaders: config.fingerprintHeaders }),\n verify: Effect.fn(\"WebhookChannel.verify\")((raw) => config.verify(raw, config.credential)),\n decode: Effect.fn(\"WebhookChannel.decode\")((raw) =>\n Effect.try({\n try: () => JSON.parse(new TextDecoder().decode(raw.body)),\n catch: () => invalidInput(\"invalid webhook JSON\")\n }).pipe(\n Effect.flatMap((json) => decode(json)),\n Effect.mapError((cause) => cause instanceof InvalidInput ? cause : invalidInput(String(cause)))\n ) as Effect.Effect<A, InvalidInput>\n ),\n map: Effect.fn(\"WebhookChannel.map\")((payload) => config.map(payload)),\n project: config.project\n }\n}\n\n/**\n * Reads an abstract Effect HTTP request and dispatches it through Channels.\n * This is mountable by any Effect HTTP host; it contains no Node transport\n * dependencies.\n *\n * The body is bounded twice. A `content-length` over the limit is refused\n * before the body is read at all, so a declared flood costs nothing; the\n * measured length is checked before retaining each streamed chunk. Reading\n * stops at the first chunk exceeding the limit, including when the caller\n * understates or omits the length. Both refusals are `InvalidInput` naming the\n * two byte counts and no body content.\n *\n * @category handlers\n * @since 0.1.0\n */\nexport const handler = (\n channel: string,\n idempotencyKey: IdempotencyKey,\n options?: HandlerOptions | undefined\n) =>\n Effect.gen(function*() {\n const limit = options?.maximumBodyBytes ?? maximumBodyBytes\n const request = yield* HttpServerRequest.HttpServerRequest\n const declared = declaredLength(request.headers)\n if (declared !== undefined && declared > limit) {\n return yield* invalidInput(`webhook body: declared ${declared} bytes exceeds the ${limit} byte limit`)\n }\n const chunks: Array<Uint8Array> = []\n let size = 0\n yield* Stream.runForEach(request.stream, (chunk) =>\n Effect.gen(function*() {\n size += chunk.byteLength\n if (size > limit) {\n return yield* invalidInput(`webhook body: ${size} bytes exceeds the ${limit} byte limit`)\n }\n if (chunk.byteLength > 0) chunks.push(chunk.slice())\n }))\n const body = new Uint8Array(size)\n let offset = 0\n for (const chunk of chunks) {\n body.set(chunk, offset)\n offset += chunk.byteLength\n }\n const channels = yield* Channels\n return yield* channels.ingest({\n channel,\n raw: { body, headers: request.headers, idempotencyKey }\n })\n })\n"]}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Control-plane contracts and projections for the Smithers harness.
|
|
3
|
+
*
|
|
4
|
+
* This is the public surface of the control plane: the transport-independent
|
|
5
|
+
* `Control` service, the `ControlRuntime` port it writes through, the
|
|
6
|
+
* projections it reads back, and the durable adapter that binds both to a real
|
|
7
|
+
* database.
|
|
8
|
+
*
|
|
9
|
+
* `Control` is authority, not execution. Every mutation it accepts is
|
|
10
|
+
* idempotent, principal-stamped, and recorded in the journal beside the state
|
|
11
|
+
* change it caused, so "who asked for this, and when did it take effect?" is
|
|
12
|
+
* answered from persisted evidence rather than from a log line.
|
|
13
|
+
*
|
|
14
|
+
* @since 0.1.0
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* @category services
|
|
18
|
+
* @since 0.1.0
|
|
19
|
+
*/
|
|
20
|
+
export * as Control from "./Control.ts";
|
|
21
|
+
/**
|
|
22
|
+
* @category services
|
|
23
|
+
* @since 1.0.0
|
|
24
|
+
*/
|
|
25
|
+
export * as ApprovalAuthority from "./ApprovalAuthority.ts";
|
|
26
|
+
/**
|
|
27
|
+
* @category errors
|
|
28
|
+
* @since 0.1.0
|
|
29
|
+
*/
|
|
30
|
+
export * as ControlError from "./ControlError.ts";
|
|
31
|
+
/**
|
|
32
|
+
* @category models
|
|
33
|
+
* @since 0.1.0
|
|
34
|
+
*/
|
|
35
|
+
export * as ControlSchema from "./ControlSchema.ts";
|
|
36
|
+
/**
|
|
37
|
+
* @category projections
|
|
38
|
+
* @since 0.1.0
|
|
39
|
+
*/
|
|
40
|
+
export * as Cancellation from "./Cancellation.ts";
|
|
41
|
+
/**
|
|
42
|
+
* @category projections
|
|
43
|
+
* @since 0.1.0
|
|
44
|
+
*/
|
|
45
|
+
export * as Lineage from "./Lineage.ts";
|
|
46
|
+
/**
|
|
47
|
+
* @category projections
|
|
48
|
+
* @since 0.1.0
|
|
49
|
+
*/
|
|
50
|
+
export * as Monitor from "./Monitor.ts";
|
|
51
|
+
/**
|
|
52
|
+
* @category projections
|
|
53
|
+
* @since 0.1.0
|
|
54
|
+
*/
|
|
55
|
+
export * as Steering from "./Steering.ts";
|
|
56
|
+
/**
|
|
57
|
+
* @category services
|
|
58
|
+
* @since 0.1.0
|
|
59
|
+
*/
|
|
60
|
+
export * as ControlRuntime from "./ControlRuntime.ts";
|
|
61
|
+
/**
|
|
62
|
+
* @category services
|
|
63
|
+
* @since 0.1.0
|
|
64
|
+
*/
|
|
65
|
+
export * as ControlExecutor from "./ControlExecutor.ts";
|
|
66
|
+
/**
|
|
67
|
+
* @category services
|
|
68
|
+
* @since 1.0.0
|
|
69
|
+
*/
|
|
70
|
+
export * as DispatchReader from "./DispatchReader.ts";
|
|
71
|
+
/**
|
|
72
|
+
* @category layers
|
|
73
|
+
* @since 0.1.0
|
|
74
|
+
*/
|
|
75
|
+
export * as ControlLive from "./ControlLive.ts";
|
|
76
|
+
/**
|
|
77
|
+
* @category models
|
|
78
|
+
* @since 0.1.0
|
|
79
|
+
*/
|
|
80
|
+
export * as SystemFlows from "./SystemFlows.ts";
|
|
81
|
+
/**
|
|
82
|
+
* @category rpc
|
|
83
|
+
* @since 0.1.0
|
|
84
|
+
*/
|
|
85
|
+
export * as ControlRpcs from "./ControlRpcs.ts";
|
|
86
|
+
/**
|
|
87
|
+
* @category layers
|
|
88
|
+
* @since 0.1.0
|
|
89
|
+
*/
|
|
90
|
+
export * as ControlServer from "./ControlServer.ts";
|
|
91
|
+
/**
|
|
92
|
+
* @category layers
|
|
93
|
+
* @since 0.1.0
|
|
94
|
+
*/
|
|
95
|
+
export * as ControlClient from "./ControlClient.ts";
|
|
96
|
+
/**
|
|
97
|
+
* @category services
|
|
98
|
+
* @since 0.1.0
|
|
99
|
+
*/
|
|
100
|
+
export * as Channels from "./Channels.ts";
|
|
101
|
+
/**
|
|
102
|
+
* @category channels
|
|
103
|
+
* @since 0.1.0
|
|
104
|
+
*/
|
|
105
|
+
export * as WebhookChannel from "./WebhookChannel.ts";
|
|
106
|
+
/**
|
|
107
|
+
* @category services
|
|
108
|
+
* @since 0.1.0
|
|
109
|
+
*/
|
|
110
|
+
export * as Credential from "./Credential.ts";
|
|
111
|
+
/**
|
|
112
|
+
* @category services
|
|
113
|
+
* @since 0.1.0
|
|
114
|
+
*/
|
|
115
|
+
export * as CredentialCipher from "./CredentialCipher.ts";
|
|
116
|
+
/**
|
|
117
|
+
* @category services
|
|
118
|
+
* @since 0.1.0
|
|
119
|
+
*/
|
|
120
|
+
export * as CredentialStore from "./CredentialStore.ts";
|
|
121
|
+
/**
|
|
122
|
+
* @category layers
|
|
123
|
+
* @since 0.1.0
|
|
124
|
+
*/
|
|
125
|
+
export * as SqlCredentialStore from "./SqlCredentialStore.ts";
|
|
126
|
+
/**
|
|
127
|
+
* @category layers
|
|
128
|
+
* @since 0.1.0
|
|
129
|
+
*/
|
|
130
|
+
export * as WebCryptoCipher from "./WebCryptoCipher.ts";
|
|
131
|
+
/**
|
|
132
|
+
* @category layers
|
|
133
|
+
* @since 0.1.0
|
|
134
|
+
*/
|
|
135
|
+
export * as SqlControlRuntime from "./SqlControlRuntime.ts";
|
|
136
|
+
/**
|
|
137
|
+
* @category authentication
|
|
138
|
+
* @since 1.0.0
|
|
139
|
+
*/
|
|
140
|
+
export * as ScopedToken from "./ScopedToken.ts";
|
|
141
|
+
/**
|
|
142
|
+
* @category migrations
|
|
143
|
+
* @since 0.1.0
|
|
144
|
+
*/
|
|
145
|
+
export * as Migrations from "./Migrations.ts";
|
|
146
|
+
/**
|
|
147
|
+
* Observational health contracts.
|
|
148
|
+
* @category health
|
|
149
|
+
* @since 1.0.0
|
|
150
|
+
*/
|
|
151
|
+
export * as Health from "./Health.ts";
|
|
152
|
+
/**
|
|
153
|
+
* The Jev-decided session checker a host binds as `jev.session`.
|
|
154
|
+
* @category health
|
|
155
|
+
* @since 1.0.0
|
|
156
|
+
*/
|
|
157
|
+
export * as JevSessionChecker from "./JevSessionChecker.ts";
|
|
158
|
+
/** Versioned control lifecycle producer and projection contract. */
|
|
159
|
+
export * as ControlFacts from "./ControlFacts.ts";
|
|
160
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH;;;GAGG;AACH,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AAEvC;;;GAGG;AACH,OAAO,KAAK,iBAAiB,MAAM,wBAAwB,CAAA;AAE3D;;;GAGG;AACH,OAAO,KAAK,YAAY,MAAM,mBAAmB,CAAA;AAEjD;;;GAGG;AACH,OAAO,KAAK,aAAa,MAAM,oBAAoB,CAAA;AAEnD;;;GAGG;AACH,OAAO,KAAK,YAAY,MAAM,mBAAmB,CAAA;AAEjD;;;GAGG;AACH,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AAEvC;;;GAGG;AACH,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AAEvC;;;GAGG;AACH,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA;AAEzC;;;GAGG;AACH,OAAO,KAAK,cAAc,MAAM,qBAAqB,CAAA;AAErD;;;GAGG;AACH,OAAO,KAAK,eAAe,MAAM,sBAAsB,CAAA;AAEvD;;;GAGG;AACH,OAAO,KAAK,cAAc,MAAM,qBAAqB,CAAA;AAErD;;;GAGG;AACH,OAAO,KAAK,WAAW,MAAM,kBAAkB,CAAA;AAE/C;;;GAGG;AACH,OAAO,KAAK,WAAW,MAAM,kBAAkB,CAAA;AAE/C;;;GAGG;AACH,OAAO,KAAK,WAAW,MAAM,kBAAkB,CAAA;AAE/C;;;GAGG;AACH,OAAO,KAAK,aAAa,MAAM,oBAAoB,CAAA;AAEnD;;;GAGG;AACH,OAAO,KAAK,aAAa,MAAM,oBAAoB,CAAA;AAEnD;;;GAGG;AACH,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA;AAEzC;;;GAGG;AACH,OAAO,KAAK,cAAc,MAAM,qBAAqB,CAAA;AAErD;;;GAGG;AACH,OAAO,KAAK,UAAU,MAAM,iBAAiB,CAAA;AAE7C;;;GAGG;AACH,OAAO,KAAK,gBAAgB,MAAM,uBAAuB,CAAA;AAEzD;;;GAGG;AACH,OAAO,KAAK,eAAe,MAAM,sBAAsB,CAAA;AAEvD;;;GAGG;AACH,OAAO,KAAK,kBAAkB,MAAM,yBAAyB,CAAA;AAE7D;;;GAGG;AACH,OAAO,KAAK,eAAe,MAAM,sBAAsB,CAAA;AAEvD;;;GAGG;AACH,OAAO,KAAK,iBAAiB,MAAM,wBAAwB,CAAA;AAE3D;;;GAGG;AACH,OAAO,KAAK,WAAW,MAAM,kBAAkB,CAAA;AAE/C;;;GAGG;AACH,OAAO,KAAK,UAAU,MAAM,iBAAiB,CAAA;AAE7C;;;;GAIG;AACH,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AAErC;;;;GAIG;AACH,OAAO,KAAK,iBAAiB,MAAM,wBAAwB,CAAA;AAE3D,oEAAoE;AACpE,OAAO,KAAK,YAAY,MAAM,mBAAmB,CAAA"}
|