@alneas/eaos-server 0.0.0-stage → 0.2.0
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 +41 -0
- package/LICENSE +21 -0
- package/README.ja.md +49 -0
- package/README.md +46 -2
- package/dist/agentic/agent-jobs.d.ts +333 -0
- package/dist/agentic/agent-jobs.js +15 -0
- package/dist/agentic/agent-jobs.js.map +1 -0
- package/dist/agentic/agent-node.d.ts +40 -0
- package/dist/agentic/agent-node.js +7 -0
- package/dist/agentic/agent-node.js.map +1 -0
- package/dist/agentic/agent-runner.d.ts +33 -0
- package/dist/agentic/agent-runner.js +7 -0
- package/dist/agentic/agent-runner.js.map +1 -0
- package/dist/agentic/ai-broker.d.ts +169 -0
- package/dist/agentic/ai-broker.js +8 -0
- package/dist/agentic/ai-broker.js.map +1 -0
- package/dist/agentic/atomic-checkpointer.d.ts +137 -0
- package/dist/agentic/atomic-checkpointer.js +9 -0
- package/dist/agentic/atomic-checkpointer.js.map +1 -0
- package/dist/agentic/autonomous-worker.d.ts +122 -0
- package/dist/agentic/autonomous-worker.js +10 -0
- package/dist/agentic/autonomous-worker.js.map +1 -0
- package/dist/agentic/event-bridge.d.ts +350 -0
- package/dist/agentic/event-bridge.js +9 -0
- package/dist/agentic/event-bridge.js.map +1 -0
- package/dist/agentic/executor.d.ts +91 -0
- package/dist/agentic/executor.js +12 -0
- package/dist/agentic/executor.js.map +1 -0
- package/dist/agentic/graph-projection.d.ts +119 -0
- package/dist/agentic/graph-projection.js +7 -0
- package/dist/agentic/graph-projection.js.map +1 -0
- package/dist/agentic/graph.d.ts +524 -0
- package/dist/agentic/graph.js +7 -0
- package/dist/agentic/graph.js.map +1 -0
- package/dist/agentic/index.d.ts +28 -0
- package/dist/agentic/index.js +87 -0
- package/dist/agentic/index.js.map +1 -0
- package/dist/agentic/local-runner.d.ts +106 -0
- package/dist/agentic/local-runner.js +7 -0
- package/dist/agentic/local-runner.js.map +1 -0
- package/dist/agentic/remote-executor.d.ts +67 -0
- package/dist/agentic/remote-executor.js +8 -0
- package/dist/agentic/remote-executor.js.map +1 -0
- package/dist/agentic/types.d.ts +157 -0
- package/dist/agentic/types.js +7 -0
- package/dist/agentic/types.js.map +1 -0
- package/dist/agentic/workflow-catalog.d.ts +152 -0
- package/dist/agentic/workflow-catalog.js +7 -0
- package/dist/agentic/workflow-catalog.js.map +1 -0
- package/dist/agentic/workflow-compiler.d.ts +44 -0
- package/dist/agentic/workflow-compiler.js +8 -0
- package/dist/agentic/workflow-compiler.js.map +1 -0
- package/dist/agentic/workflow-coordinator.d.ts +170 -0
- package/dist/agentic/workflow-coordinator.js +8 -0
- package/dist/agentic/workflow-coordinator.js.map +1 -0
- package/dist/agentic/workflow-manager.d.ts +91 -0
- package/dist/agentic/workflow-manager.js +7 -0
- package/dist/agentic/workflow-manager.js.map +1 -0
- package/dist/agentic/workflow-registry.d.ts +334 -0
- package/dist/agentic/workflow-registry.js +7 -0
- package/dist/agentic/workflow-registry.js.map +1 -0
- package/dist/artifacts-CteHmCMI.d.ts +614 -0
- package/dist/auth-hooks/api-key.d.ts +75 -0
- package/dist/auth-hooks/api-key.js +7 -0
- package/dist/auth-hooks/api-key.js.map +1 -0
- package/dist/auth-hooks/index.d.ts +80 -0
- package/dist/auth-hooks/index.js +28 -0
- package/dist/auth-hooks/index.js.map +1 -0
- package/dist/browser-auth/account-memberships.d.ts +213 -0
- package/dist/browser-auth/account-memberships.js +13 -0
- package/dist/browser-auth/account-memberships.js.map +1 -0
- package/dist/browser-auth/index.d.ts +8 -0
- package/dist/browser-auth/index.js +59 -0
- package/dist/browser-auth/index.js.map +1 -0
- package/dist/browser-auth/kratos-session.d.ts +37 -0
- package/dist/browser-auth/kratos-session.js +7 -0
- package/dist/browser-auth/kratos-session.js.map +1 -0
- package/dist/browser-auth/platform-session.d.ts +120 -0
- package/dist/browser-auth/platform-session.js +7 -0
- package/dist/browser-auth/platform-session.js.map +1 -0
- package/dist/browser-auth/public-memberships.d.ts +22 -0
- package/dist/browser-auth/public-memberships.js +7 -0
- package/dist/browser-auth/public-memberships.js.map +1 -0
- package/dist/cache/index.d.ts +181 -0
- package/dist/cache/index.js +197 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/chunk-32746UQZ.js +131 -0
- package/dist/chunk-32746UQZ.js.map +1 -0
- package/dist/chunk-3NLSWP2Y.js +223 -0
- package/dist/chunk-3NLSWP2Y.js.map +1 -0
- package/dist/chunk-3P4XTTQH.js +346 -0
- package/dist/chunk-3P4XTTQH.js.map +1 -0
- package/dist/chunk-3VITSHN3.js +15 -0
- package/dist/chunk-3VITSHN3.js.map +1 -0
- package/dist/chunk-46UWPEW2.js +226 -0
- package/dist/chunk-46UWPEW2.js.map +1 -0
- package/dist/chunk-4BARRLJR.js +180 -0
- package/dist/chunk-4BARRLJR.js.map +1 -0
- package/dist/chunk-4VORUFKR.js +45 -0
- package/dist/chunk-4VORUFKR.js.map +1 -0
- package/dist/chunk-55D4ERWO.js +1 -0
- package/dist/chunk-55D4ERWO.js.map +1 -0
- package/dist/chunk-5TBBZIND.js +18 -0
- package/dist/chunk-5TBBZIND.js.map +1 -0
- package/dist/chunk-5WOZWPZA.js +211 -0
- package/dist/chunk-5WOZWPZA.js.map +1 -0
- package/dist/chunk-6CBVZ6CT.js +168 -0
- package/dist/chunk-6CBVZ6CT.js.map +1 -0
- package/dist/chunk-6XZXYNJR.js +74 -0
- package/dist/chunk-6XZXYNJR.js.map +1 -0
- package/dist/chunk-753HWDSC.js +222 -0
- package/dist/chunk-753HWDSC.js.map +1 -0
- package/dist/chunk-7AHSS7SO.js +81 -0
- package/dist/chunk-7AHSS7SO.js.map +1 -0
- package/dist/chunk-7FTJN4SA.js +1 -0
- package/dist/chunk-7FTJN4SA.js.map +1 -0
- package/dist/chunk-AVDMLTPF.js +24 -0
- package/dist/chunk-AVDMLTPF.js.map +1 -0
- package/dist/chunk-BKAHUEIT.js +95 -0
- package/dist/chunk-BKAHUEIT.js.map +1 -0
- package/dist/chunk-CJ7OFEF7.js +134 -0
- package/dist/chunk-CJ7OFEF7.js.map +1 -0
- package/dist/chunk-EJKWDZIU.js +111 -0
- package/dist/chunk-EJKWDZIU.js.map +1 -0
- package/dist/chunk-F2J6IQ6I.js +247 -0
- package/dist/chunk-F2J6IQ6I.js.map +1 -0
- package/dist/chunk-F3O7CELI.js +23 -0
- package/dist/chunk-F3O7CELI.js.map +1 -0
- package/dist/chunk-F4IITPGX.js +221 -0
- package/dist/chunk-F4IITPGX.js.map +1 -0
- package/dist/chunk-FD6VC2NA.js +66 -0
- package/dist/chunk-FD6VC2NA.js.map +1 -0
- package/dist/chunk-FRRRWAHO.js +14 -0
- package/dist/chunk-FRRRWAHO.js.map +1 -0
- package/dist/chunk-HCKAGLLB.js +17 -0
- package/dist/chunk-HCKAGLLB.js.map +1 -0
- package/dist/chunk-HVC7FG6B.js +191 -0
- package/dist/chunk-HVC7FG6B.js.map +1 -0
- package/dist/chunk-HXFVOR7Z.js +496 -0
- package/dist/chunk-HXFVOR7Z.js.map +1 -0
- package/dist/chunk-IJWYS2PF.js +122 -0
- package/dist/chunk-IJWYS2PF.js.map +1 -0
- package/dist/chunk-INCHXAFK.js +1395 -0
- package/dist/chunk-INCHXAFK.js.map +1 -0
- package/dist/chunk-JT6BRGQM.js +1 -0
- package/dist/chunk-JT6BRGQM.js.map +1 -0
- package/dist/chunk-L6PMAEMB.js +115 -0
- package/dist/chunk-L6PMAEMB.js.map +1 -0
- package/dist/chunk-LOUGW6MU.js +126 -0
- package/dist/chunk-LOUGW6MU.js.map +1 -0
- package/dist/chunk-LYPZPE53.js +129 -0
- package/dist/chunk-LYPZPE53.js.map +1 -0
- package/dist/chunk-NNCCN7JP.js +186 -0
- package/dist/chunk-NNCCN7JP.js.map +1 -0
- package/dist/chunk-PBC3MZUL.js +212 -0
- package/dist/chunk-PBC3MZUL.js.map +1 -0
- package/dist/chunk-R4ADP2DC.js +20 -0
- package/dist/chunk-R4ADP2DC.js.map +1 -0
- package/dist/chunk-RHN4Y6VS.js +44 -0
- package/dist/chunk-RHN4Y6VS.js.map +1 -0
- package/dist/chunk-SDJNYPYK.js +770 -0
- package/dist/chunk-SDJNYPYK.js.map +1 -0
- package/dist/chunk-UGNTCOVR.js +28 -0
- package/dist/chunk-UGNTCOVR.js.map +1 -0
- package/dist/chunk-VC2PCK2S.js +31 -0
- package/dist/chunk-VC2PCK2S.js.map +1 -0
- package/dist/chunk-VF5YDZJC.js +305 -0
- package/dist/chunk-VF5YDZJC.js.map +1 -0
- package/dist/chunk-VISRDVTK.js +675 -0
- package/dist/chunk-VISRDVTK.js.map +1 -0
- package/dist/chunk-W6O3LVBY.js +102 -0
- package/dist/chunk-W6O3LVBY.js.map +1 -0
- package/dist/chunk-WFEAKJLR.js +47 -0
- package/dist/chunk-WFEAKJLR.js.map +1 -0
- package/dist/chunk-XIEY2OF4.js +67 -0
- package/dist/chunk-XIEY2OF4.js.map +1 -0
- package/dist/chunk-XITLHDG3.js +37 -0
- package/dist/chunk-XITLHDG3.js.map +1 -0
- package/dist/chunk-XMWPYZWZ.js +61 -0
- package/dist/chunk-XMWPYZWZ.js.map +1 -0
- package/dist/chunk-XUP6CNOM.js +34 -0
- package/dist/chunk-XUP6CNOM.js.map +1 -0
- package/dist/chunk-YABTQZDJ.js +25 -0
- package/dist/chunk-YABTQZDJ.js.map +1 -0
- package/dist/chunk-YMHVRBUU.js +199 -0
- package/dist/chunk-YMHVRBUU.js.map +1 -0
- package/dist/chunk-YMJ7BRKU.js +510 -0
- package/dist/chunk-YMJ7BRKU.js.map +1 -0
- package/dist/chunk-ZC3XWU7B.js +28 -0
- package/dist/chunk-ZC3XWU7B.js.map +1 -0
- package/dist/chunk-ZHMTD5KV.js +90 -0
- package/dist/chunk-ZHMTD5KV.js.map +1 -0
- package/dist/chunk-ZWZDLOE7.js +160 -0
- package/dist/chunk-ZWZDLOE7.js.map +1 -0
- package/dist/db-package/db/local-first-sqlite-adapter.d.ts +432 -0
- package/dist/db-package/db/local-first-sqlite-adapter.js +7 -0
- package/dist/db-package/db/local-first-sqlite-adapter.js.map +1 -0
- package/dist/db-package/index.d.ts +7 -0
- package/dist/db-package/index.js +16 -0
- package/dist/db-package/index.js.map +1 -0
- package/dist/db-package/migration/aggregator.d.ts +86 -0
- package/dist/db-package/migration/aggregator.js +7 -0
- package/dist/db-package/migration/aggregator.js.map +1 -0
- package/dist/db-package/migration/index.d.ts +3 -0
- package/dist/db-package/migration/index.js +8 -0
- package/dist/db-package/migration/index.js.map +1 -0
- package/dist/engine.d.ts +205 -0
- package/dist/engine.js +20 -0
- package/dist/engine.js.map +1 -0
- package/dist/entitlements/index.d.ts +313 -0
- package/dist/entitlements/index.js +23 -0
- package/dist/entitlements/index.js.map +1 -0
- package/dist/execution/chat.d.ts +7 -0
- package/dist/execution/chat.js +7 -0
- package/dist/execution/chat.js.map +1 -0
- package/dist/execution/index.d.ts +342 -0
- package/dist/execution/index.js +66 -0
- package/dist/execution/index.js.map +1 -0
- package/dist/execution/tool-agent.d.ts +7 -0
- package/dist/execution/tool-agent.js +8 -0
- package/dist/execution/tool-agent.js.map +1 -0
- package/dist/extensions/index.d.ts +172 -0
- package/dist/extensions/index.js +95 -0
- package/dist/extensions/index.js.map +1 -0
- package/dist/health/index.d.ts +35 -0
- package/dist/health/index.js +7 -0
- package/dist/health/index.js.map +1 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.js +161 -0
- package/dist/index.js.map +1 -0
- package/dist/lifecycle/index.d.ts +133 -0
- package/dist/lifecycle/index.js +13 -0
- package/dist/lifecycle/index.js.map +1 -0
- package/dist/lifecycle-engine/backend-migration.d.ts +2 -0
- package/dist/lifecycle-engine/backend-migration.js +11 -0
- package/dist/lifecycle-engine/backend-migration.js.map +1 -0
- package/dist/lifecycle-engine/baseline.d.ts +2 -0
- package/dist/lifecycle-engine/baseline.js +9 -0
- package/dist/lifecycle-engine/baseline.js.map +1 -0
- package/dist/lifecycle-engine/index.d.ts +1497 -0
- package/dist/lifecycle-engine/index.js +41 -0
- package/dist/lifecycle-engine/index.js.map +1 -0
- package/dist/lifecycle-engine/platform/artifacts.d.ts +4 -0
- package/dist/lifecycle-engine/platform/artifacts.js +7 -0
- package/dist/lifecycle-engine/platform/artifacts.js.map +1 -0
- package/dist/lifecycle-engine/platform/control-plane.d.ts +12 -0
- package/dist/lifecycle-engine/platform/control-plane.js +2 -0
- package/dist/lifecycle-engine/platform/control-plane.js.map +1 -0
- package/dist/lifecycle-engine/platform/database-router.d.ts +118 -0
- package/dist/lifecycle-engine/platform/database-router.js +7 -0
- package/dist/lifecycle-engine/platform/database-router.js.map +1 -0
- package/dist/lifecycle-engine/platform/deployment-placements.d.ts +464 -0
- package/dist/lifecycle-engine/platform/deployment-placements.js +16 -0
- package/dist/lifecycle-engine/platform/deployment-placements.js.map +1 -0
- package/dist/lifecycle-engine/platform/deployment.d.ts +4 -0
- package/dist/lifecycle-engine/platform/deployment.js +17 -0
- package/dist/lifecycle-engine/platform/deployment.js.map +1 -0
- package/dist/lifecycle-engine/platform/index.d.ts +737 -0
- package/dist/lifecycle-engine/platform/index.js +60 -0
- package/dist/lifecycle-engine/platform/index.js.map +1 -0
- package/dist/lifecycle-engine/platform/runtime.d.ts +80 -0
- package/dist/lifecycle-engine/platform/runtime.js +11 -0
- package/dist/lifecycle-engine/platform/runtime.js.map +1 -0
- package/dist/lifecycle-engine/platform/site-publication.d.ts +102 -0
- package/dist/lifecycle-engine/platform/site-publication.js +7 -0
- package/dist/lifecycle-engine/platform/site-publication.js.map +1 -0
- package/dist/lifecycle-engine/platform/user-directory.d.ts +139 -0
- package/dist/lifecycle-engine/platform/user-directory.js +13 -0
- package/dist/lifecycle-engine/platform/user-directory.js.map +1 -0
- package/dist/mcp.d.ts +92 -0
- package/dist/mcp.js +10 -0
- package/dist/mcp.js.map +1 -0
- package/dist/middleware/auth.d.ts +118 -0
- package/dist/middleware/auth.js +7 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/error-handler.d.ts +35 -0
- package/dist/middleware/error-handler.js +7 -0
- package/dist/middleware/error-handler.js.map +1 -0
- package/dist/middleware/module-guard.d.ts +33 -0
- package/dist/middleware/module-guard.js +7 -0
- package/dist/middleware/module-guard.js.map +1 -0
- package/dist/platform-admin/index.d.ts +37 -0
- package/dist/platform-admin/index.js +11 -0
- package/dist/platform-admin/index.js.map +1 -0
- package/dist/platform-admin/store.d.ts +346 -0
- package/dist/platform-admin/store.js +7 -0
- package/dist/platform-admin/store.js.map +1 -0
- package/dist/plugin-data/annotations.d.ts +6 -0
- package/dist/plugin-data/annotations.js +12 -0
- package/dist/plugin-data/annotations.js.map +1 -0
- package/dist/plugin-data/atomic-context.d.ts +41 -0
- package/dist/plugin-data/atomic-context.js +9 -0
- package/dist/plugin-data/atomic-context.js.map +1 -0
- package/dist/plugin-data/atomic.d.ts +6 -0
- package/dist/plugin-data/atomic.js +26 -0
- package/dist/plugin-data/atomic.js.map +1 -0
- package/dist/plugin-data/audit.d.ts +6 -0
- package/dist/plugin-data/audit.js +12 -0
- package/dist/plugin-data/audit.js.map +1 -0
- package/dist/plugin-data/commands.d.ts +6 -0
- package/dist/plugin-data/commands.js +8 -0
- package/dist/plugin-data/commands.js.map +1 -0
- package/dist/plugin-data/composite.d.ts +6 -0
- package/dist/plugin-data/composite.js +8 -0
- package/dist/plugin-data/composite.js.map +1 -0
- package/dist/plugin-data/delivery.d.ts +6 -0
- package/dist/plugin-data/delivery.js +16 -0
- package/dist/plugin-data/delivery.js.map +1 -0
- package/dist/plugin-data/index.d.ts +2798 -0
- package/dist/plugin-data/index.js +102 -0
- package/dist/plugin-data/index.js.map +1 -0
- package/dist/plugin-data/json-documents.d.ts +6 -0
- package/dist/plugin-data/json-documents.js +16 -0
- package/dist/plugin-data/json-documents.js.map +1 -0
- package/dist/plugin-data/outbox.d.ts +6 -0
- package/dist/plugin-data/outbox.js +16 -0
- package/dist/plugin-data/outbox.js.map +1 -0
- package/dist/plugin-data/scoped-records.d.ts +6 -0
- package/dist/plugin-data/scoped-records.js +10 -0
- package/dist/plugin-data/scoped-records.js.map +1 -0
- package/dist/plugin-sdk.d.ts +41 -0
- package/dist/plugin-sdk.js +10 -0
- package/dist/plugin-sdk.js.map +1 -0
- package/dist/sql-lifecycle/index.d.ts +138 -0
- package/dist/sql-lifecycle/index.js +167 -0
- package/dist/sql-lifecycle/index.js.map +1 -0
- package/dist/storage/index.d.ts +2 -0
- package/dist/storage/index.js +13 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/s3-compatible.d.ts +139 -0
- package/dist/storage/s3-compatible.js +11 -0
- package/dist/storage/s3-compatible.js.map +1 -0
- package/dist/tenant.d.ts +108 -0
- package/dist/tenant.js +9 -0
- package/dist/tenant.js.map +1 -0
- package/dist/workspace-context.d.ts +119 -0
- package/dist/workspace-context.js +9 -0
- package/dist/workspace-context.js.map +1 -0
- package/docs/api.md +30560 -0
- package/docs/db/README.md +49 -0
- package/docs/db/docs/api.md +1613 -0
- package/docs/db/docs/guide.md +47 -0
- package/docs/entitlements.md +60 -0
- package/docs/guide.md +92 -0
- package/docs/index.md +57 -0
- package/docs/ja/db/README.md +46 -0
- package/docs/ja/db/docs/guide.md +53 -0
- package/docs/ja/guide.md +46 -0
- package/docs/ja/lifecycle/README.md +46 -0
- package/docs/ja/lifecycle/docs/guide.md +45 -0
- package/docs/ja/storage/README.md +46 -0
- package/docs/ja/storage/docs/guide.md +43 -0
- package/docs/lifecycle/README.md +49 -0
- package/docs/lifecycle/docs/api.md +3770 -0
- package/docs/lifecycle/docs/baseline.md +50 -0
- package/docs/lifecycle/docs/guide.md +47 -0
- package/docs/storage/README.md +49 -0
- package/docs/storage/docs/api.md +470 -0
- package/docs/storage/docs/guide.md +47 -0
- package/package.json +354 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/storage/index.ts","../src/storage/s3-compatible.ts"],"sourcesContent":["export { createS3CompatibleStorage, createTenantS3CompatibleStorage, readS3CompatibleStorageConfig, type S3CompatibleStorageConfig, type StorageCredentials } from './s3-compatible'\nimport type { StorageAdapter, StorageObject, StorageListOptions, StorageListResult, StorageUploadOptions, StorageURLOptions } from '@alneas/appkit-core/storage'\nexport type { StorageAdapter } from '@alneas/appkit-core/storage'\n/**\n * Operation names passed to the host authorization callback before scoped storage access.\n * @see createTenantStorage for a complete usage example.\n */\nexport type StorageOperation = 'upload' | 'download' | 'delete' | 'exists' | 'head' | 'list' | 'getUrl' | 'copy' | 'move'\n/**\n * Trusted tenant/plugin scope and per-operation authorization for {@link createTenantStorage}.\n */\nexport interface TenantStorageOptions {\n /** Trusted tenant ID: 1–128 ASCII letters, digits, underscores, or hyphens, starting alphanumeric. It must come from verified server identity/configuration, never request body keys. */\n tenantId: string\n /** Trusted plugin ID: 1–128 ASCII letters, digits, underscores, or hyphens, starting with a letter or digit. */\n pluginId: string\n /** Invoked before each operation with trusted scope, operation, and frozen relative keys (list receives its prefix); only exactly true allows access. Errors propagate. */\n authorize: (request: {\n /**\n * Trusted tenant scope captured at adapter construction, independent of caller object keys.\n */\n tenantId: string;\n /**\n * Trusted plugin scope captured at adapter construction.\n */\n pluginId: string;\n /**\n * Storage operation about to be delegated to the provider.\n */\n operation: StorageOperation;\n /**\n * Frozen relative-key array; copy/move supplies source then destination, and list supplies its requested prefix.\n */\n keys: readonly string[] }) => boolean | Promise<boolean>\n /** Signed URLs remain usable until expiry; default ceiling 300 seconds. */\n maxUrlLifetimeSeconds?: number\n}\nfunction segment(value: string): string {\n if (typeof value !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/.test(value)) throw new Error('Invalid storage scope ID')\n return value\n}\nfunction path(value: string, prefix = false): string {\n // eslint-disable-next-line no-control-regex -- Reject ASCII control characters in untrusted identifiers/paths.\n if (typeof value !== 'string' || (!prefix && !value) || /[\\\\%\\x00-\\x1f\\x7f?#]/.test(value) || value.startsWith('/')) throw new Error('Invalid storage key')\n const parts = value.split('/')\n if (prefix && parts.at(-1) === '') parts.pop()\n if (parts.some(part => !part || part === '.' || part === '..')) throw new Error('Invalid storage key')\n return value\n}\n/**\n * Wraps a storage adapter with tenant/plugin key isolation and per-operation authorization.\n * @param adapter - Host-owned transport/signing adapter; optional copy and move are exposed only when supported.\n * @param options - Trusted scope, authorization callback, and signed URL ceiling; see {@link TenantStorageOptions}.\n * @returns A frozen adapter using relative keys while storing under tenants/{tenantId}/plugins/{pluginId}/; returned object keys and list cursors are scope-relative.\n * @throws Throws synchronously for invalid scope IDs or URL lifetime. Operations reject for invalid relative keys, denied access, public uploads, unsigned or overlong URLs, invalid cursors/delimiters, out-of-scope provider results, or propagated adapter/authorization errors.\n * @remarks Keys cannot be absolute or contain traversal, empty segments, backslashes, percent escapes, control characters, query strings, or fragments. List permits an empty prefix and slash delimiter only. URLs default to a 300-second ceiling and remain usable until expiry after authorization changes. This wrapper supplies no persistence, locking, revocation, or transaction mechanism beyond the underlying adapter. The host must retain ownership of the unscoped adapter.\n * @example\n * ```typescript\n * import { createTenantStorage, type StorageAdapter, type TenantStorageOptions } from \"@alneas/eaos-server/storage\";\n * export function pluginStorage(adapter: StorageAdapter, trustedOptions: TenantStorageOptions) {\n * // Resolve scope and authorization from verified host identity, never request body IDs.\n * return createTenantStorage(adapter, trustedOptions);\n * }\n * ```\n */\nexport function createTenantStorage(adapter: StorageAdapter, options: TenantStorageOptions): StorageAdapter {\n const tenantId = segment(options.tenantId), pluginId = segment(options.pluginId)\n const scope = `tenants/${tenantId}/plugins/${pluginId}/`\n const authorize = options.authorize\n const maxAge = options.maxUrlLifetimeSeconds ?? 300\n if (!Number.isInteger(maxAge) || maxAge < 1) throw new Error('Invalid URL lifetime')\n async function check(operation: StorageOperation, keys: string[]) {\n if (await authorize({ tenantId, pluginId, operation, keys: Object.freeze([...keys]) }) !== true) throw new Error('Storage access denied')\n }\n function relative(key: string, prefix = false): string {\n if (!key.startsWith(scope)) throw new Error('Storage returned an object outside the authorized scope')\n return path(key.slice(scope.length), prefix)\n }\n function object(value: StorageObject): StorageObject { return { ...value, key: relative(value.key) } }\n function exact(value: StorageObject, key: string): StorageObject {\n const result = object(value)\n if (result.key !== key) throw new Error('Storage returned an unexpected object')\n return result\n }\n return Object.freeze({\n async upload(key: string, data: Parameters<StorageAdapter['upload']>[1], settings?: StorageUploadOptions) {\n key = path(key)\n if (settings?.public) throw new Error('Public tenant objects are disabled')\n const safe = { ...settings, public: false }\n await check('upload', [key])\n return exact(await adapter.upload(scope + key, data, safe), key)\n },\n async download(key: string) { key = path(key); await check('download', [key]); return adapter.download(scope + key) },\n async delete(key: string) { key = path(key); await check('delete', [key]); return adapter.delete(scope + key) },\n async exists(key: string) { key = path(key); await check('exists', [key]); return adapter.exists(scope + key) },\n async head(key: string) {\n key = path(key); await check('head', [key])\n const result = await adapter.head(scope + key)\n return result ? exact(result, key) : null\n },\n async getUrl(key: string, settings?: StorageURLOptions) {\n key = path(key)\n const expiresIn = settings?.expiresIn ?? maxAge\n if (settings?.signed === false || !Number.isInteger(expiresIn) || expiresIn < 1 || expiresIn > maxAge) throw new Error('Signed URL policy violation')\n const safe = { ...settings, signed: true, expiresIn }\n await check('getUrl', [key])\n return adapter.getUrl(scope + key, safe)\n },\n async list(settings: StorageListOptions = {}): Promise<StorageListResult> {\n const prefix = path(settings.prefix ?? '', true)\n if (settings.delimiter !== undefined && settings.delimiter !== '/') throw new Error('Invalid delimiter')\n let cursor: string | undefined\n if (settings.cursor) {\n try {\n const data = JSON.parse(Buffer.from(settings.cursor, 'base64url').toString())\n if (data.scope !== scope || data.prefix !== prefix || typeof data.token !== 'string') throw new Error()\n cursor = data.token\n } catch { throw new Error('Invalid scoped storage cursor') }\n }\n const safe = { ...settings, prefix: scope + prefix, cursor }\n await check('list', [prefix])\n const result = await adapter.list(safe)\n const objects = result.objects.map(object), prefixes = result.prefixes?.map(key => relative(key, true))\n if (objects.some(o => !o.key.startsWith(prefix)) || prefixes?.some(p => !p.startsWith(prefix))) throw new Error('Storage returned objects outside the requested prefix')\n return { objects, prefixes, hasMore: result.hasMore, nextCursor: result.nextCursor ? Buffer.from(JSON.stringify({ scope, prefix, token: result.nextCursor })).toString('base64url') : undefined }\n },\n ...(adapter.copy ? { async copy(source: string, destination: string) {\n source = path(source); destination = path(destination); await check('copy', [source, destination])\n return exact(await adapter.copy!(scope + source, scope + destination), destination)\n } } : {}),\n ...(adapter.move ? { async move(source: string, destination: string) {\n source = path(source); destination = path(destination); await check('move', [source, destination])\n return exact(await adapter.move!(scope + source, scope + destination), destination)\n } } : {}),\n })\n}\n","import type { StorageAdapter, R2StorageConfig, S3StorageConfig } from '@alneas/appkit-core/storage'\nimport { createTenantStorage, type TenantStorageOptions } from './index'\n\n/**\n * Server-held signing credentials; never expose these through browser configuration.\n * @see createS3CompatibleStorage for host setup.\n */\nexport interface StorageCredentials {\n /** Nonempty access key identifier for the selected provider. */\n accessKeyId: string\n /** Nonempty secret used for request signing. */\n secretAccessKey: string\n}\ntype Credentials = StorageCredentials | (() => StorageCredentials | Promise<StorageCredentials>)\ntype Common = {\n /** Server-held credentials or a callback invoked once when constructing the adapter; callback errors reject creation. */\n credentials: Credentials\n /** Signed URL lifetime ceiling in whole seconds, from 1 to 604800. @defaultValue 300 */\n maxUrlLifetimeSeconds?: number\n}\n/**\n * Selects R2 or S3 transport settings with host-held credentials and a signed URL ceiling.\n * @remarks R2 requires a 32-hex-character accountId; S3 requires region. Both require a valid bucket name. Optional endpoints must be HTTPS URLs without credentials, query, or fragment. Provider-specific fields follow app-core storage configuration; public URLs are not configured by this factory.\n * @see createS3CompatibleStorage for a complete setup example.\n */\nexport type S3CompatibleStorageConfig = Common & (\n | ({ \n /**\n * Select Cloudflare R2 transport; accountId must be 32 hexadecimal characters and bucket/credentials must be configured.\n */\n provider: 'r2' } & Omit<R2StorageConfig, 'accessKeyId' | 'secretAccessKey' | 'publicUrl' | 'defaultExpiresIn'>)\n | ({ \n /**\n * Select S3-compatible transport; a nonempty region and valid bucket/credentials are required, with optional HTTPS endpoint.\n */\n provider: 's3' } & Omit<S3StorageConfig, 'accessKeyId' | 'secretAccessKey' | 'defaultExpiresIn'>)\n)\n\n/**\n * Creates an unscoped private-upload, signed-URL storage adapter for R2 or S3.\n * @param config - Provider configuration, credentials, and URL ceiling; see {@link S3CompatibleStorageConfig}.\n * @returns A frozen app-core adapter whose uploads reject public=true and whose URLs always use signatures within the configured ceiling.\n * @throws Rejects invalid provider, lifetime, bucket, account/region, endpoint, or empty credentials, and propagates credential callback/import/adapter construction errors. Subsequent operations propagate transport errors.\n * @remarks Server-only: transport/signing stay in app-core and the host supplies secrets. Credentials are resolved once at construction. Signed access also works during a public-bucket migration but cannot close existing public URLs. Keep this unscoped adapter host-owned; give plugins createTenantS3CompatibleStorage. Constructing an adapter does not verify remote credentials or create a bucket.\n * @example\n * ```typescript\n * import { createS3CompatibleStorage, readS3CompatibleStorageConfig } from \"@alneas/eaos-server/storage/s3-compatible\";\n * // The host provisions a private bucket and supplies the documented storage environment.\n * const config = readS3CompatibleStorageConfig();\n * if (!config) throw new Error(\"Storage is not configured\");\n * const storage = await createS3CompatibleStorage(config);\n * // Retain storage in trusted server code; authorize requests before performing operations.\n * ```\n */\nexport async function createS3CompatibleStorage(config: S3CompatibleStorageConfig): Promise<StorageAdapter> {\n if (!['s3', 'r2'].includes(config.provider)) throw new Error('Unsupported storage provider')\n const maxAge = config.maxUrlLifetimeSeconds ?? 300\n if (!Number.isInteger(maxAge) || maxAge < 1 || maxAge > 604800) throw new Error('Invalid URL lifetime')\n if (!config.bucket || !/^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/.test(config.bucket)) throw new Error('Invalid storage bucket')\n if (config.provider === 'r2' && !/^[a-f0-9]{32}$/i.test(config.accountId)) throw new Error('Invalid R2 account ID')\n if (config.provider === 's3' && !config.region) throw new Error('Storage region required')\n if (config.endpoint) {\n const endpoint = new URL(config.endpoint)\n if (endpoint.protocol !== 'https:' || endpoint.username || endpoint.password || endpoint.search || endpoint.hash) throw new Error('Invalid storage endpoint')\n }\n const credentials = typeof config.credentials === 'function' ? await config.credentials() : config.credentials\n if (!credentials.accessKeyId || !credentials.secretAccessKey) throw new Error('Storage credentials required')\n const { provider, credentials: _credentials, maxUrlLifetimeSeconds: _maxAge, ...settings } = config\n // Lazy import keeps SDK peers out of callers using only the policy wrapper.\n const { R2StorageAdapter, S3StorageAdapter } = await import('@alneas/appkit-core/storage')\n const adapter: StorageAdapter = provider === 'r2'\n ? new R2StorageAdapter({ ...settings, ...credentials, defaultExpiresIn: maxAge } as R2StorageConfig)\n : new S3StorageAdapter({ ...settings, ...credentials, defaultExpiresIn: maxAge } as S3StorageConfig)\n return Object.freeze({\n async upload(key, data, options) {\n if (options?.public) throw new Error('Public storage uploads are disabled')\n return adapter.upload(key, data, { ...options, public: false })\n },\n download: adapter.download.bind(adapter), delete: adapter.delete.bind(adapter),\n exists: adapter.exists.bind(adapter), head: adapter.head.bind(adapter), list: adapter.list.bind(adapter),\n async getUrl(key, options) {\n const expiresIn = options?.expiresIn ?? maxAge\n if (options?.signed === false || !Number.isInteger(expiresIn) || expiresIn < 1 || expiresIn > maxAge) throw new Error('Signed URL policy violation')\n return adapter.getUrl(key, { ...options, signed: true, expiresIn })\n },\n ...(adapter.copy ? { copy: adapter.copy.bind(adapter) } : {}),\n ...(adapter.move ? { move: adapter.move.bind(adapter) } : {}),\n } satisfies StorageAdapter)\n}\n\n/**\n * Creates provider-backed storage isolated to a verified tenant/plugin scope.\n * @param config - Host provider configuration and credentials.\n * @param options - Trusted scope and live authorization; an omitted scope URL ceiling inherits config.maxUrlLifetimeSeconds, then 300.\n * @returns A frozen scoped adapter; see {@link createTenantStorage} for relative key and operation policy.\n * @throws Rejects provider construction failures and invalid scope settings; operations propagate authorization, policy, and transport errors.\n * @remarks The provider ceiling also applies, so a larger scope ceiling cannot increase it. The host owns bucket provisioning and verified identity resolution.\n * @example\n * ```typescript\n * import { createTenantS3CompatibleStorage, type S3CompatibleStorageConfig } from \"@alneas/eaos-server/storage/s3-compatible\";\n * import type { TenantStorageOptions } from \"@alneas/eaos-server/storage\";\n * export async function scopedStorage(config: S3CompatibleStorageConfig, trustedOptions: TenantStorageOptions) {\n * return createTenantS3CompatibleStorage(config, trustedOptions);\n * }\n * ```\n */\nexport async function createTenantS3CompatibleStorage(config: S3CompatibleStorageConfig, options: TenantStorageOptions): Promise<StorageAdapter> {\n const adapter = await createS3CompatibleStorage(config)\n return createTenantStorage(adapter, { ...options, maxUrlLifetimeSeconds: options.maxUrlLifetimeSeconds ?? config.maxUrlLifetimeSeconds })\n}\n\n/**\n * Reads trimmed S3 or R2 configuration from a host environment map.\n * @param env - Environment values; defaults to process.env. EAOS_STORAGE_PROVIDER selects s3/r2 case-insensitively. R2 uses R2_ACCOUNT_ID, R2_BUCKET, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, and optional R2_ENDPOINT. S3 uses S3_BUCKET, S3_REGION, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, optional S3_ENDPOINT, and S3_FORCE_PATH_STYLE (true or 1). EAOS_STORAGE_MAX_URL_SECONDS is converted with Number.\n * @returns Configuration, or null when the provider is absent/blank. This read does not fully validate values; creation validates provider settings.\n * @throws If a selected provider lacks required nonblank values, or the provider is unsupported.\n * @remarks Returned configuration contains secrets and belongs exclusively in trusted server code.\n * @example\n * ```typescript\n * import { readS3CompatibleStorageConfig } from \"@alneas/eaos-server/storage/s3-compatible\";\n * const config = readS3CompatibleStorageConfig();\n * console.log(config ? \"Storage configured\" : \"Storage disabled\");\n * ```\n */\nexport function readS3CompatibleStorageConfig(env: Record<string, string | undefined> = process.env): S3CompatibleStorageConfig | null {\n const provider = env.EAOS_STORAGE_PROVIDER?.trim().toLowerCase()\n if (!provider) return null\n const maxAge = env.EAOS_STORAGE_MAX_URL_SECONDS ? Number(env.EAOS_STORAGE_MAX_URL_SECONDS) : undefined\n if (provider === 'r2') {\n const accountId = env.R2_ACCOUNT_ID?.trim() ?? '', bucket = env.R2_BUCKET?.trim() ?? ''\n const accessKeyId = env.R2_ACCESS_KEY_ID?.trim() ?? '', secretAccessKey = env.R2_SECRET_ACCESS_KEY?.trim() ?? ''\n if (!accountId || !bucket || !accessKeyId || !secretAccessKey) throw new Error('Incomplete R2 storage environment')\n return {\n provider: 'r2', accountId, bucket,\n ...(env.R2_ENDPOINT ? { endpoint: env.R2_ENDPOINT.trim() } : {}),\n ...(maxAge !== undefined ? { maxUrlLifetimeSeconds: maxAge } : {}),\n credentials: { accessKeyId, secretAccessKey },\n }\n }\n if (provider === 's3') {\n const bucket = env.S3_BUCKET?.trim() ?? '', region = env.S3_REGION?.trim() ?? ''\n const accessKeyId = env.S3_ACCESS_KEY_ID?.trim() ?? '', secretAccessKey = env.S3_SECRET_ACCESS_KEY?.trim() ?? ''\n if (!bucket || !region || !accessKeyId || !secretAccessKey) throw new Error('Incomplete S3 storage environment')\n return {\n provider: 's3', bucket, region,\n ...(env.S3_ENDPOINT ? { endpoint: env.S3_ENDPOINT.trim() } : {}),\n ...(env.S3_FORCE_PATH_STYLE === 'true' || env.S3_FORCE_PATH_STYLE === '1' ? { forcePathStyle: true } : {}),\n ...(maxAge !== undefined ? { maxUrlLifetimeSeconds: maxAge } : {}),\n credentials: { accessKeyId, secretAccessKey },\n }\n }\n throw new Error('Unsupported storage provider')\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
// src/agentic/agent-jobs.ts
|
|
2
|
+
import { hashAgentJobRequest } from "@alneas/eaos-core";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
var DurableAgentJobDispatcher = class {
|
|
5
|
+
options;
|
|
6
|
+
/**
|
|
7
|
+
* Validates one-pass claim and exponential retry settings.
|
|
8
|
+
* @param options - Store/worker plus bounded batch, attempt, delay, clock, and completion settings.
|
|
9
|
+
* @throws Missing store/worker, batchSize/maxAttempts outside safe integer 1–100, or nonfinite/negative/inverted retry delays.
|
|
10
|
+
*/
|
|
11
|
+
constructor(options) {
|
|
12
|
+
if (!options?.store || !options.worker) throw new Error("Durable agent job store and worker are required");
|
|
13
|
+
const batchSize = options.batchSize ?? 1;
|
|
14
|
+
const maxAttempts = options.maxAttempts ?? 5;
|
|
15
|
+
const baseDelayMs = options.baseDelayMs ?? 1e3;
|
|
16
|
+
const maxDelayMs = options.maxDelayMs ?? 6e4;
|
|
17
|
+
if (!Number.isSafeInteger(batchSize) || batchSize < 1 || batchSize > 100) throw new Error("Invalid agent job batch size");
|
|
18
|
+
if (!Number.isSafeInteger(maxAttempts) || maxAttempts < 1 || maxAttempts > 100) throw new Error("Invalid agent job max attempts");
|
|
19
|
+
if (!Number.isFinite(baseDelayMs) || baseDelayMs < 0 || !Number.isFinite(maxDelayMs) || maxDelayMs < baseDelayMs) throw new Error("Invalid agent job retry policy");
|
|
20
|
+
this.options = { ...options, batchSize, maxAttempts, baseDelayMs, maxDelayMs, now: options.now ?? (() => /* @__PURE__ */ new Date()) };
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Claims one batch and executes jobs sequentially, persisting completion, approval wait, retry, or failure.
|
|
24
|
+
* @returns Per-outcome counts after all claimed jobs have been processed.
|
|
25
|
+
* @throws Rejects claim/transition/clock failures; worker errors become retryable failure results. Completion callback/markCompleted errors trigger retry-or-fail handling.
|
|
26
|
+
* @remarks Retry delay is min(maxDelayMs, baseDelayMs * 2^(attempt-1)), using job.attempt + 1 for the attempt calculation. No timer or automatic second pass is installed. Results marked PENDING_APPROVAL do not count as success.
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* import { DurableAgentJobDispatcher, type AgentJobDispatcherOptions } from "@alneas/eaos-server/agentic/agent-jobs";
|
|
30
|
+
* export async function runOnce(options: AgentJobDispatcherOptions) { return new DurableAgentJobDispatcher(options).runOnce(); }
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
async runOnce() {
|
|
34
|
+
const now = this.options.now();
|
|
35
|
+
const jobs = await this.options.store.claim(now, this.options.batchSize);
|
|
36
|
+
const result = { claimed: jobs.length, succeeded: 0, waitingApproval: 0, retried: 0, failed: 0 };
|
|
37
|
+
for (const job of jobs) {
|
|
38
|
+
const leaseToken = job.leaseToken;
|
|
39
|
+
if (!leaseToken) {
|
|
40
|
+
result.failed++;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
let taskResult;
|
|
44
|
+
try {
|
|
45
|
+
taskResult = await this.options.worker.runTask(structuredClone(job.request));
|
|
46
|
+
} catch (error) {
|
|
47
|
+
taskResult = {
|
|
48
|
+
taskId: job.request.id,
|
|
49
|
+
status: "FAILED",
|
|
50
|
+
error: error instanceof Error ? error.message : "Agent job failed",
|
|
51
|
+
retryable: true,
|
|
52
|
+
durationMs: 0
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
if (taskResult.status === "PENDING_APPROVAL") {
|
|
56
|
+
await this.options.store.markWaitingApproval(job.jobId, leaseToken, taskResult, this.options.now());
|
|
57
|
+
result.waitingApproval++;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (taskResult.status === "COMPLETED") {
|
|
61
|
+
try {
|
|
62
|
+
await this.options.onCompleted?.(structuredClone(job), taskResult);
|
|
63
|
+
await this.options.store.markCompleted(job.jobId, leaseToken, taskResult, this.options.now());
|
|
64
|
+
result.succeeded++;
|
|
65
|
+
} catch (error) {
|
|
66
|
+
await this.retryOrFail(job, leaseToken, error instanceof Error ? error.message : "Agent completion callback failed", "RUNTIME_FAILED", result);
|
|
67
|
+
}
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
await this.retryOrFail(job, leaseToken, taskResult.error ?? "Agent job failed", taskResult.failureCode, result, taskResult.retryable);
|
|
71
|
+
}
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
async retryOrFail(job, leaseToken, error, failureCode, result, retryable = true) {
|
|
75
|
+
const attempt = job.attempt + 1;
|
|
76
|
+
if (!retryable || attempt >= Math.min(job.maxAttempts, this.options.maxAttempts)) {
|
|
77
|
+
await this.options.store.markFailed(job.jobId, leaseToken, error.slice(0, 1e3), failureCode, this.options.now());
|
|
78
|
+
result.failed++;
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const delay = Math.min(this.options.maxDelayMs, this.options.baseDelayMs * 2 ** Math.max(0, attempt - 1));
|
|
82
|
+
await this.options.store.markRetryable(job.jobId, leaseToken, error.slice(0, 1e3), failureCode, new Date(this.options.now().getTime() + delay), this.options.now());
|
|
83
|
+
result.retried++;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
function createAgentJob(input) {
|
|
87
|
+
const now = input.now ?? /* @__PURE__ */ new Date();
|
|
88
|
+
const request = structuredClone(input.request);
|
|
89
|
+
const requestHash = hashAgentJobRequest(request);
|
|
90
|
+
const idempotencyKey2 = input.idempotencyKey ?? request.executionContext?.idempotencyKey ?? request.id;
|
|
91
|
+
return {
|
|
92
|
+
jobId: `agent-job-${randomUUID()}`,
|
|
93
|
+
tenantId: request.tenantId,
|
|
94
|
+
kind: input.kind,
|
|
95
|
+
status: "queued",
|
|
96
|
+
request,
|
|
97
|
+
requestHash,
|
|
98
|
+
...input.workflowInstanceId === void 0 ? {} : { workflowInstanceId: input.workflowInstanceId },
|
|
99
|
+
correlationId: input.correlationId ?? request.executionContext?.correlationId ?? request.id,
|
|
100
|
+
idempotencyKey: idempotencyKey2,
|
|
101
|
+
attempt: 0,
|
|
102
|
+
maxAttempts: input.maxAttempts ?? 5,
|
|
103
|
+
availableAt: now.toISOString(),
|
|
104
|
+
createdAt: now.toISOString(),
|
|
105
|
+
updatedAt: now.toISOString()
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
var jobKey = (id) => `agent-job/${id}`;
|
|
109
|
+
var idempotencyKey = (tenantId, kind, key) => `agent-job-idempotency/${hashAgentJobRequest([tenantId, kind, key])}`;
|
|
110
|
+
async function enqueueAgentJob(tx, job) {
|
|
111
|
+
const index = idempotencyKey(job.tenantId, job.kind, job.idempotencyKey);
|
|
112
|
+
const existing = await tx.get(index);
|
|
113
|
+
if (existing) {
|
|
114
|
+
if (existing.value.requestHash !== job.requestHash) throw new Error("Agent job idempotency key reused with different input");
|
|
115
|
+
const saved = await tx.get(jobKey(existing.value.jobId));
|
|
116
|
+
if (!saved) throw new Error("Agent job idempotency index is corrupt");
|
|
117
|
+
return structuredClone(saved.value);
|
|
118
|
+
}
|
|
119
|
+
await tx.put(jobKey(job.jobId), job, 0);
|
|
120
|
+
await tx.put(index, { jobId: job.jobId, requestHash: job.requestHash }, 0);
|
|
121
|
+
return structuredClone(job);
|
|
122
|
+
}
|
|
123
|
+
function createAtomicAgentJobStore(options) {
|
|
124
|
+
const staleAfterMs = options.staleAfterMs ?? 5 * 6e4;
|
|
125
|
+
const leaseMs = options.leaseMs ?? 15 * 6e4;
|
|
126
|
+
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
127
|
+
if (!options?.repository || !options.principal || !Number.isFinite(staleAfterMs) || staleAfterMs < 1e3 || !Number.isFinite(leaseMs) || leaseMs < 1e3) throw new Error("Invalid atomic agent job store options");
|
|
128
|
+
const update = async (jobId, token, mutate) => options.repository.run(options.principal, async (tx) => {
|
|
129
|
+
const row = await tx.get(jobKey(jobId));
|
|
130
|
+
if (!row || row.value.tenantId !== options.principal.tenantId || row.value.leaseToken !== token) throw new Error("Agent job lease changed");
|
|
131
|
+
if (row.value.requestHash !== hashAgentJobRequest(row.value.request)) throw new Error("Agent job request integrity differs");
|
|
132
|
+
await options.verifyCurrent?.();
|
|
133
|
+
const next = await mutate(structuredClone(row.value), now(), tx);
|
|
134
|
+
await tx.put(row.id, next, row.revision);
|
|
135
|
+
await options.verifyCurrent?.();
|
|
136
|
+
});
|
|
137
|
+
return {
|
|
138
|
+
async claim(current, limit, filter) {
|
|
139
|
+
return options.repository.run(options.principal, async (tx) => {
|
|
140
|
+
await options.verifyCurrent?.();
|
|
141
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100) throw new Error("Invalid claim limit");
|
|
142
|
+
const claimed = [];
|
|
143
|
+
let after = "";
|
|
144
|
+
for (let page = 0; page < 100 && claimed.length < limit; page++) {
|
|
145
|
+
const rows = await tx.list("agent-job/", after, 100);
|
|
146
|
+
if (!rows.length) break;
|
|
147
|
+
for (const row of rows) {
|
|
148
|
+
after = row.id;
|
|
149
|
+
const value = row.value;
|
|
150
|
+
if (value.tenantId !== options.principal.tenantId || value.request.tenantId !== options.principal.tenantId) throw new Error("Foreign agent job");
|
|
151
|
+
if (value.requestHash !== hashAgentJobRequest(value.request)) throw new Error("Agent job request integrity differs");
|
|
152
|
+
if (!matchesClaimFilter(value, filter)) continue;
|
|
153
|
+
const due = (value.status === "queued" || value.status === "retryable") && Date.parse(value.availableAt) <= current.getTime();
|
|
154
|
+
const stale = value.status === "running" && Date.parse(value.leaseUntil ?? "") <= current.getTime() - staleAfterMs;
|
|
155
|
+
if (!due && !stale) continue;
|
|
156
|
+
const next = { ...value, status: "running", attempt: value.attempt + 1, leaseToken: randomUUID(), leaseUntil: new Date(current.getTime() + leaseMs).toISOString(), updatedAt: current.toISOString(), error: void 0, failureCode: void 0 };
|
|
157
|
+
await tx.put(row.id, next, row.revision);
|
|
158
|
+
claimed.push(structuredClone(next));
|
|
159
|
+
if (claimed.length === limit) break;
|
|
160
|
+
}
|
|
161
|
+
if (rows.length < 100) break;
|
|
162
|
+
if (page === 99 && claimed.length < limit) throw new Error("Agent job scan exceeds bounded capacity; archive completed jobs before retrying");
|
|
163
|
+
}
|
|
164
|
+
await options.verifyCurrent?.();
|
|
165
|
+
return claimed;
|
|
166
|
+
});
|
|
167
|
+
},
|
|
168
|
+
markCompleted(jobId, token, result, _current) {
|
|
169
|
+
return update(jobId, token, async (job, nowValue, tx) => {
|
|
170
|
+
await options.onCompleted?.(tx, structuredClone(job), structuredClone(result));
|
|
171
|
+
return { ...job, status: "succeeded", result, updatedAt: nowValue.toISOString(), leaseToken: void 0, leaseUntil: void 0 };
|
|
172
|
+
});
|
|
173
|
+
},
|
|
174
|
+
markWaitingApproval(jobId, token, result, _current) {
|
|
175
|
+
return update(jobId, token, (job, nowValue) => ({ ...job, status: "waiting_approval", result, updatedAt: nowValue.toISOString(), leaseToken: void 0, leaseUntil: void 0 }));
|
|
176
|
+
},
|
|
177
|
+
markRetryable(jobId, token, error, failureCode, retryAt, _current) {
|
|
178
|
+
return update(jobId, token, (job, nowValue) => ({ ...job, status: "retryable", error, failureCode, availableAt: retryAt.toISOString(), updatedAt: nowValue.toISOString(), leaseToken: void 0, leaseUntil: void 0 }));
|
|
179
|
+
},
|
|
180
|
+
markFailed(jobId, token, error, failureCode, _current) {
|
|
181
|
+
return update(jobId, token, (job, nowValue) => ({ ...job, status: "failed", error, failureCode, updatedAt: nowValue.toISOString(), leaseToken: void 0, leaseUntil: void 0 }));
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
function matchesClaimFilter(job, filter) {
|
|
186
|
+
if (!filter) return true;
|
|
187
|
+
const context = job.request.executionContext;
|
|
188
|
+
if (filter.dispatchMode !== void 0 && (context?.dispatchMode ?? "server-queue") !== filter.dispatchMode) return false;
|
|
189
|
+
if (filter.userId !== void 0 && job.request.userId !== filter.userId) return false;
|
|
190
|
+
if (filter.runnerId !== void 0 && context?.runnerId !== void 0 && context.runnerId !== filter.runnerId) return false;
|
|
191
|
+
if (filter.routeIds !== void 0 && (!context?.routeId || !filter.routeIds.includes(context.routeId))) return false;
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
async function cancelAgentJob(tx, principal, jobId, now = /* @__PURE__ */ new Date()) {
|
|
195
|
+
const row = await tx.get(jobKey(jobId));
|
|
196
|
+
if (!row || row.value.tenantId !== principal.tenantId || row.value.request.userId !== principal.userId) throw new Error("Agent job unavailable");
|
|
197
|
+
if (row.value.request.tenantId !== principal.tenantId || row.value.requestHash !== hashAgentJobRequest(row.value.request)) throw new Error("Agent job request integrity differs");
|
|
198
|
+
if (row.value.status === "cancelled") return structuredClone(row.value);
|
|
199
|
+
if (!["queued", "retryable", "waiting_approval"].includes(row.value.status)) throw new Error("Only an unstarted agent job can be cancelled");
|
|
200
|
+
const next = { ...row.value, status: "cancelled", updatedAt: now.toISOString(), leaseToken: void 0, leaseUntil: void 0 };
|
|
201
|
+
await tx.put(row.id, next, row.revision);
|
|
202
|
+
return structuredClone(next);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export {
|
|
206
|
+
DurableAgentJobDispatcher,
|
|
207
|
+
createAgentJob,
|
|
208
|
+
enqueueAgentJob,
|
|
209
|
+
createAtomicAgentJobStore,
|
|
210
|
+
cancelAgentJob
|
|
211
|
+
};
|
|
212
|
+
//# sourceMappingURL=chunk-PBC3MZUL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/agentic/agent-jobs.ts"],"sourcesContent":["import { hashAgentJobRequest, type AgentJobRecord, type AgentJobStatus } from '@alneas/eaos-core'\nimport { randomUUID } from 'node:crypto'\nimport type { ServerAgentDispatchMode, ServerAgentTaskRequest, ServerAgentTaskResult } from './types'\nimport type { ServerAutonomousWorker } from './autonomous-worker'\nimport type { AtomicRepository, AtomicSession } from '../plugin-data/atomic'\nimport type { PluginPrincipal } from '../plugin-data/index'\n\n/**\n * Host durable queue boundary with atomic claims and lease-checked transitions.\n * @see createAtomicAgentJobStore for the canonical implementation.\n */\nexport interface AgentJobStore {\n /**\n * Claims due/recoverable jobs using durable transaction/lease exclusion.\n * @param now - Claim clock used for due and stale checks.\n * @param limit - Maximum job count, validated by the implementation.\n * @param filter - Optional dispatch/user/runner/route eligibility.\n * @returns Leased jobs, or an empty array.\n * @throws Queue persistence or admission failures propagate.\n \n * @example\n * ```typescript\n * import type { AgentJobStore } from \"@alneas/eaos-server\";\n * export async function claimHostJobs(\n * store: AgentJobStore, eligibility: Parameters<AgentJobStore[\"claim\"]>[2],\n * ) {\n * // Supply a store already scoped to verified tenant admission.\n * return await store.claim(new Date(), 10, eligibility);\n * }\n * ```\n */\n claim(now: Date, limit: number, filter?: AgentJobClaimFilter): Promise<readonly AgentJobRecord<ServerAgentTaskRequest>[]>\n /**\n * Transitions the leased job to succeeded and persists its result.\n * @param jobId - Stored job ID.\n * @param leaseToken - Current claim token; stale tokens must be rejected.\n * @param result - Normalized task outcome to persist.\n * @param now - Transition clock; the atomic implementation uses its configured clock instead.\n * @returns No value; resolves after the durable transition.\n * @throws Lease/integrity/admission or persistence failures propagate.\n \n * @example\n * ```typescript\n * import type { AgentJobStore } from \"@alneas/eaos-server\";\n * export async function completeClaimedJob(\n * store: AgentJobStore, jobId: string, currentLeaseToken: string,\n * verifiedResult: Parameters<AgentJobStore[\"markCompleted\"]>[2],\n * ) {\n * await store.markCompleted(jobId, currentLeaseToken, verifiedResult, new Date());\n * // Completion is durable under the host store contract; stale leases reject.\n * }\n * ```\n */\n markCompleted(jobId: string, leaseToken: string, result: ServerAgentTaskResult, now: Date): Promise<void>\n /**\n * Transitions the leased job to waiting_approval and persists approval result data.\n * @param jobId - Stored job ID.\n * @param leaseToken - Current claim token; stale tokens must be rejected.\n * @param result - Normalized task outcome to persist.\n * @param now - Transition clock; the atomic implementation uses its configured clock instead.\n * @returns No value; resolves after the durable transition.\n * @throws Lease/integrity/admission or persistence failures propagate.\n \n * @example\n * ```typescript\n * import type { AgentJobStore } from \"@alneas/eaos-server\";\n * export async function deferClaimedJobForReview(\n * store: AgentJobStore, jobId: string, currentLeaseToken: string,\n * approvalResult: Parameters<AgentJobStore[\"markWaitingApproval\"]>[2],\n * ) {\n * await store.markWaitingApproval(jobId, currentLeaseToken, approvalResult, new Date());\n * // This records pending review; it does not approve or execute the proposed action.\n * }\n * ```\n */\n markWaitingApproval(jobId: string, leaseToken: string, result: ServerAgentTaskResult, now: Date): Promise<void>\n /**\n * Releases the lease and schedules the job for a later attempt.\n * @param jobId - Stored job ID.\n * @param leaseToken - Current claim token; stale tokens must be rejected.\n * @param error - Persisted failure description.\n * @param failureCode - Optional stable runtime classification.\n * @param retryAt - Earliest eligible next-claim timestamp.\n * @param now - Transition clock; the atomic implementation uses its configured clock instead.\n * @returns No value; resolves after the durable transition.\n * @throws Lease/integrity/admission or persistence failures propagate.\n \n * @example\n * ```typescript\n * import type { AgentJobStore } from \"@alneas/eaos-server\";\n * export async function rescheduleClaimedJob(\n * store: AgentJobStore, jobId: string, currentLeaseToken: string,\n * sanitizedError: string, failureCode: string | undefined, reviewedRetryAt: Date,\n * ) {\n * // Reconcile any uncertain external effect before deciding a retry is safe.\n * await store.markRetryable(\n * jobId, currentLeaseToken, sanitizedError, failureCode, reviewedRetryAt, new Date(),\n * );\n * }\n * ```\n */\n markRetryable(jobId: string, leaseToken: string, error: string, failureCode: string | undefined, retryAt: Date, now: Date): Promise<void>\n /**\n * Releases the lease and marks a terminal failed result.\n * @param jobId - Stored job ID.\n * @param leaseToken - Current claim token; stale tokens must be rejected.\n * @param error - Persisted failure description.\n * @param failureCode - Optional stable runtime classification.\n * @param now - Transition clock; the atomic implementation uses its configured clock instead.\n * @returns No value; resolves after the durable transition.\n * @throws Lease/integrity/admission or persistence failures propagate.\n \n * @example\n * ```typescript\n * import type { AgentJobStore } from \"@alneas/eaos-server\";\n * export async function failClaimedJob(\n * store: AgentJobStore, jobId: string, currentLeaseToken: string,\n * sanitizedError: string, failureCode: string | undefined,\n * ) {\n * await store.markFailed(jobId, currentLeaseToken, sanitizedError, failureCode, new Date());\n * // The host persisted terminal failure; no automatic retry is requested.\n * }\n * ```\n */\n markFailed(jobId: string, leaseToken: string, error: string, failureCode: string | undefined, now: Date): Promise<void>\n}\n\n/**\n * Optional eligibility constraints for durable queue claims; omitted fields impose no restriction.\n * @see AgentJobStore.claim.\n */\nexport interface AgentJobClaimFilter {\n /** Delivery mode; legacy jobs without a mode are treated as server-queue by the atomic implementation. */\n dispatchMode?: ServerAgentDispatchMode\n /** Eligible registered route IDs; jobs without a matching route are excluded. */\n routeIds?: readonly string[]\n /** Verified runner ID; jobs with a different explicit runner binding are excluded, unbound jobs remain eligible. */\n runnerId?: string\n /** Verified owning user ID to require. */\n userId?: string\n}\n\n/**\n * Host queue/worker and one-pass retry policy for {@link DurableAgentJobDispatcher}.\n */\nexport interface AgentJobDispatcherOptions {\n /** Durable queue enforcing claim leases and tenant admission. */\n store: AgentJobStore\n /** Worker executing each cloned claimed task. */\n worker: ServerAutonomousWorker\n /** Jobs requested per pass, safe integer 1–100; defaults to 1. */\n batchSize?: number\n /** Dispatcher attempt ceiling, safe integer 1–100; defaults to 5 and is combined with each job ceiling. */\n maxAttempts?: number\n /** Finite nonnegative initial exponential retry delay in milliseconds; defaults to 1000. */\n baseDelayMs?: number\n /** Finite retry delay ceiling at least baseDelayMs; defaults to 60000 milliseconds. */\n maxDelayMs?: number\n /** Clock for claims, transitions, and retry availability; defaults to fresh Date. */\n now?: () => Date\n /** Resume a LangGraph thread or publish a domain event after a successful agent result. */\n onCompleted?: (job: Readonly<AgentJobRecord<ServerAgentTaskRequest>>, result: ServerAgentTaskResult) => Promise<void>\n}\n\n/**\n * Counts of one dispatcher pass; jobs missing lease tokens increment failed without a store transition.\n * @see DurableAgentJobDispatcher.runOnce.\n */\nexport interface AgentJobDispatchResult {\n /** Number of rows returned by the claim. */\n claimed: number\n /** Jobs whose completion callback and completed transition succeeded. */\n succeeded: number\n /** Jobs transitioned to waiting_approval. */\n waitingApproval: number\n /** Jobs scheduled for a later retry. */\n retried: number\n /** Jobs marked terminal failure plus claimed jobs missing a lease token. */\n failed: number\n}\n\n/**\n * Durable boundary between Micro 2 and an ARM agent runtime.\n *\n * The store is host-owned and must persist leases, attempts and results. This\n * class owns only one bounded pass; a host scheduler calls runOnce again.\n * @remarks Jobs execute sequentially within each pass, but concurrent runOnce calls are not serialized here. Leases and admission must be enforced by the host store. Completion callbacks run before markCompleted and can be repeated after retry; make external effects idempotent.\n * @example\n * ```typescript\n * import { DurableAgentJobDispatcher, type AgentJobDispatcherOptions } from \"@alneas/eaos-server/agentic/agent-jobs\";\n * export async function dispatch(options: AgentJobDispatcherOptions) {\n * return new DurableAgentJobDispatcher(options).runOnce();\n * }\n * ```\n */\nexport class DurableAgentJobDispatcher {\n private readonly options: Required<Pick<AgentJobDispatcherOptions, 'batchSize' | 'maxAttempts' | 'baseDelayMs' | 'maxDelayMs' | 'now'>> & AgentJobDispatcherOptions\n\n /**\n * Validates one-pass claim and exponential retry settings.\n * @param options - Store/worker plus bounded batch, attempt, delay, clock, and completion settings.\n * @throws Missing store/worker, batchSize/maxAttempts outside safe integer 1–100, or nonfinite/negative/inverted retry delays.\n */\n constructor(options: AgentJobDispatcherOptions) {\n if (!options?.store || !options.worker) throw new Error('Durable agent job store and worker are required')\n const batchSize = options.batchSize ?? 1\n const maxAttempts = options.maxAttempts ?? 5\n const baseDelayMs = options.baseDelayMs ?? 1_000\n const maxDelayMs = options.maxDelayMs ?? 60_000\n if (!Number.isSafeInteger(batchSize) || batchSize < 1 || batchSize > 100) throw new Error('Invalid agent job batch size')\n if (!Number.isSafeInteger(maxAttempts) || maxAttempts < 1 || maxAttempts > 100) throw new Error('Invalid agent job max attempts')\n if (!Number.isFinite(baseDelayMs) || baseDelayMs < 0 || !Number.isFinite(maxDelayMs) || maxDelayMs < baseDelayMs) throw new Error('Invalid agent job retry policy')\n this.options = { ...options, batchSize, maxAttempts, baseDelayMs, maxDelayMs, now: options.now ?? (() => new Date()) }\n }\n\n /**\n * Claims one batch and executes jobs sequentially, persisting completion, approval wait, retry, or failure.\n * @returns Per-outcome counts after all claimed jobs have been processed.\n * @throws Rejects claim/transition/clock failures; worker errors become retryable failure results. Completion callback/markCompleted errors trigger retry-or-fail handling.\n * @remarks Retry delay is min(maxDelayMs, baseDelayMs * 2^(attempt-1)), using job.attempt + 1 for the attempt calculation. No timer or automatic second pass is installed. Results marked PENDING_APPROVAL do not count as success.\n * @example\n * ```typescript\n * import { DurableAgentJobDispatcher, type AgentJobDispatcherOptions } from \"@alneas/eaos-server/agentic/agent-jobs\";\n * export async function runOnce(options: AgentJobDispatcherOptions) { return new DurableAgentJobDispatcher(options).runOnce(); }\n * ```\n */\n async runOnce(): Promise<AgentJobDispatchResult> {\n const now = this.options.now()\n const jobs = await this.options.store.claim(now, this.options.batchSize)\n const result: AgentJobDispatchResult = { claimed: jobs.length, succeeded: 0, waitingApproval: 0, retried: 0, failed: 0 }\n for (const job of jobs) {\n const leaseToken = job.leaseToken\n if (!leaseToken) {\n result.failed++\n continue\n }\n let taskResult: ServerAgentTaskResult\n try {\n taskResult = await this.options.worker.runTask(structuredClone(job.request))\n } catch (error) {\n taskResult = {\n taskId: job.request.id,\n status: 'FAILED',\n error: error instanceof Error ? error.message : 'Agent job failed',\n retryable: true,\n durationMs: 0,\n }\n }\n\n if (taskResult.status === 'PENDING_APPROVAL') {\n await this.options.store.markWaitingApproval(job.jobId, leaseToken, taskResult, this.options.now())\n result.waitingApproval++\n continue\n }\n if (taskResult.status === 'COMPLETED') {\n try {\n await this.options.onCompleted?.(structuredClone(job), taskResult)\n await this.options.store.markCompleted(job.jobId, leaseToken, taskResult, this.options.now())\n result.succeeded++\n } catch (error) {\n await this.retryOrFail(job, leaseToken, error instanceof Error ? error.message : 'Agent completion callback failed', 'RUNTIME_FAILED', result)\n }\n continue\n }\n\n await this.retryOrFail(job, leaseToken, taskResult.error ?? 'Agent job failed', taskResult.failureCode, result, taskResult.retryable)\n }\n return result\n }\n\n private async retryOrFail(\n job: AgentJobRecord<ServerAgentTaskRequest>,\n leaseToken: string,\n error: string,\n failureCode: string | undefined,\n result: AgentJobDispatchResult,\n retryable = true,\n ): Promise<void> {\n const attempt = job.attempt + 1\n if (!retryable || attempt >= Math.min(job.maxAttempts, this.options.maxAttempts)) {\n await this.options.store.markFailed(job.jobId, leaseToken, error.slice(0, 1_000), failureCode, this.options.now())\n result.failed++\n return\n }\n const delay = Math.min(this.options.maxDelayMs, this.options.baseDelayMs * (2 ** Math.max(0, attempt - 1)))\n await this.options.store.markRetryable(job.jobId, leaseToken, error.slice(0, 1_000), failureCode, new Date(this.options.now().getTime() + delay), this.options.now())\n result.retried++\n }\n}\n\n/**\n * Creates a cloned, hashed queued job record without persisting or executing it.\n * @param input - request supplies verified task identity; kind groups idempotency; workflowInstanceId is optional; correlationId defaults to request executionContext correlation then request.id; idempotencyKey defaults to executionContext key then request.id; maxAttempts defaults to 5; now defaults to the current Date.\n * @returns A new queued record with generated jobId, attempt zero, request hash, and matching available/created/updated timestamps.\n * @throws Clone/hash/invalid Date failures; this factory does not independently validate every task or attempt field.\n * @example\n * ```typescript\n * import { createAgentJob } from \"@alneas/eaos-server/agentic/agent-jobs\";\n * import type { ServerAgentTaskRequest } from \"@alneas/eaos-server/agentic/types\";\n * export function queued(request: ServerAgentTaskRequest) { return createAgentJob({ request, kind: \"workflow\" }); }\n * ```\n */\nexport function createAgentJob(input: {\n /**\n * Host-verified task cloned into the durable job and hashed for idempotency integrity.\n */\n request: ServerAgentTaskRequest\n /**\n * Queue kind used together with tenant and idempotencyKey to partition durable deduplication.\n */\n kind: string\n /**\n * Optional companion workflow instance ID retained in the queued record.\n */\n workflowInstanceId?: string\n /**\n * Queue trace label; defaults to request.executionContext.correlationId, then request.id.\n */\n correlationId?: string\n /**\n * Durable deduplication identity; defaults to request.executionContext.idempotencyKey, then request.id.\n */\n idempotencyKey?: string\n /**\n * Job attempt ceiling retained without validation here; defaults to 5 and is later combined with dispatcher policy.\n */\n maxAttempts?: number\n /**\n * Timestamp for availableAt, createdAt, and updatedAt; defaults to a fresh Date and must support toISOString.\n */\n now?: Date\n}): AgentJobRecord<ServerAgentTaskRequest> {\n const now = input.now ?? new Date()\n const request = structuredClone(input.request)\n const requestHash = hashAgentJobRequest(request)\n const idempotencyKey = input.idempotencyKey ?? request.executionContext?.idempotencyKey ?? request.id\n return {\n jobId: `agent-job-${randomUUID()}`,\n tenantId: request.tenantId,\n kind: input.kind,\n status: 'queued' as AgentJobStatus,\n request,\n requestHash,\n ...(input.workflowInstanceId === undefined ? {} : { workflowInstanceId: input.workflowInstanceId }),\n correlationId: input.correlationId ?? request.executionContext?.correlationId ?? request.id,\n idempotencyKey,\n attempt: 0,\n maxAttempts: input.maxAttempts ?? 5,\n availableAt: now.toISOString(),\n createdAt: now.toISOString(),\n updatedAt: now.toISOString(),\n }\n}\n\nconst jobKey = (id: string) => `agent-job/${id}`\nconst idempotencyKey = (tenantId: string, kind: string, key: string) => `agent-job-idempotency/${hashAgentJobRequest([tenantId, kind, key])}`\n\n/**\n * Persists a job and tenant/kind/idempotency index in the caller's atomic transaction.\n * @param tx - Authorized active atomic session; the caller owns commit/rollback.\n * @param job - Created job whose tenant/kind/key define deduplication and whose requestHash binds input.\n * @returns A clone of the saved job, or existing indexed job when its request hash matches; existing lifecycle state is preserved.\n * @throws Reused idempotency key with a different request hash, missing indexed job, or propagated transaction/clone errors.\n * @remarks This helper neither executes the job nor opens a transaction; host transaction isolation/CAS must prevent concurrent index creation races.\n * @example\n * ```typescript\n * import { enqueueAgentJob, createAgentJob } from \"@alneas/eaos-server/agentic/agent-jobs\";\n * import type { AtomicSession } from \"@alneas/eaos-server/plugin-data/atomic\";\n * import type { ServerAgentTaskRequest } from \"@alneas/eaos-server/agentic/types\";\n * export async function enqueue(tx: AtomicSession, request: ServerAgentTaskRequest) {\n * return enqueueAgentJob(tx, createAgentJob({ request, kind: \"workflow\" }));\n * }\n * ```\n */\nexport async function enqueueAgentJob(tx: AtomicSession, job: AgentJobRecord<ServerAgentTaskRequest>): Promise<AgentJobRecord<ServerAgentTaskRequest>> {\n const index = idempotencyKey(job.tenantId, job.kind, job.idempotencyKey)\n const existing = await tx.get<{ jobId: string; requestHash: string }>(index)\n if (existing) {\n if (existing.value.requestHash !== job.requestHash) throw new Error('Agent job idempotency key reused with different input')\n const saved = await tx.get<AgentJobRecord<ServerAgentTaskRequest>>(jobKey(existing.value.jobId))\n if (!saved) throw new Error('Agent job idempotency index is corrupt')\n return structuredClone(saved.value)\n }\n await tx.put(jobKey(job.jobId), job, 0)\n await tx.put(index, { jobId: job.jobId, requestHash: job.requestHash }, 0)\n return structuredClone(job)\n}\n\n/**\n * Tenant-scoped CAS persistence, lease timing, admission, and transactional completion hook.\n * @see createAtomicAgentJobStore.\n */\nexport interface AtomicAgentJobStoreOptions {\n /** Host atomic repository enforcing durable CAS and tenant isolation. */\n repository: AtomicRepository\n /** Verified tenant/plugin/user repository scope, retained by reference. */\n principal: PluginPrincipal\n /** Additional stale grace after lease expiry in milliseconds; finite and at least 1000, defaults to 300000. */\n staleAfterMs?: number\n /** Lease duration in milliseconds; finite and at least 1000, defaults to 900000. */\n leaseMs?: number\n /** Clock for state transitions; defaults to fresh Date. */\n now?: () => Date\n /** Current host admission is checked before and after durable mutations. */\n verifyCurrent?: () => Promise<void>\n /** Persist the domain result in the same transaction as the succeeded job state. */\n onCompleted?: (tx: AtomicSession, job: Readonly<AgentJobRecord<ServerAgentTaskRequest>>, result: Readonly<ServerAgentTaskResult>) => Promise<void>\n}\n\n/**\n * Creates a lease-checked agent queue over an existing tenant-scoped atomic repository.\n * @param options - Repository/principal plus clocks, leases, optional current-admission checks, and transactional result persistence.\n * @returns Store operations validating tenant/request integrity and lease token before transitions. claim increments attempt and leases due queued/retryable jobs or sufficiently stale running jobs.\n * @throws Synchronously for missing dependencies or lease/stale durations below 1000 ms/nonfinite. Operations reject integrity/tenant/lease/admission errors, invalid claim limits, bounded-scan exhaustion, or propagated repository/hook errors.\n * @remarks claim scans at most 100 pages of 100 rows and permits safe-integer limits 1–100. Running jobs recover only when leaseUntil is at least staleAfterMs before the claim clock. Transition timestamps use options.now, ignoring the interface-supplied now argument. No lease renewal or worker cancellation exists; stale execution can overlap a reclaimed attempt, so side effects need host idempotency. verifyCurrent runs around durable mutations when supplied. Transactional onCompleted runs before succeeded state persistence in the same session.\n * @example\n * ```typescript\n * import { createAtomicAgentJobStore, type AtomicAgentJobStoreOptions } from \"@alneas/eaos-server/agentic/agent-jobs\";\n * export function jobStore(options: AtomicAgentJobStoreOptions) { return createAtomicAgentJobStore(options); }\n * ```\n */\nexport function createAtomicAgentJobStore(options: AtomicAgentJobStoreOptions): AgentJobStore {\n const staleAfterMs = options.staleAfterMs ?? 5 * 60_000\n const leaseMs = options.leaseMs ?? 15 * 60_000\n const now = options.now ?? (() => new Date())\n if (!options?.repository || !options.principal || !Number.isFinite(staleAfterMs) || staleAfterMs < 1_000 || !Number.isFinite(leaseMs) || leaseMs < 1_000) throw new Error('Invalid atomic agent job store options')\n const update = async (jobId: string, token: string, mutate: (job: AgentJobRecord<ServerAgentTaskRequest>, current: Date, tx: AtomicSession) => AgentJobRecord<ServerAgentTaskRequest> | Promise<AgentJobRecord<ServerAgentTaskRequest>>) => options.repository.run(options.principal, async tx => {\n const row = await tx.get<AgentJobRecord<ServerAgentTaskRequest>>(jobKey(jobId))\n if (!row || row.value.tenantId !== options.principal.tenantId || row.value.leaseToken !== token) throw new Error('Agent job lease changed')\n if(row.value.requestHash!==hashAgentJobRequest(row.value.request))throw new Error('Agent job request integrity differs')\n await options.verifyCurrent?.()\n const next = await mutate(structuredClone(row.value), now(), tx)\n await tx.put(row.id, next, row.revision)\n await options.verifyCurrent?.()\n })\n return {\n async claim(current, limit, filter) {\n return options.repository.run(options.principal, async tx => {\n await options.verifyCurrent?.()\n if(!Number.isSafeInteger(limit)||limit<1||limit>100)throw new Error('Invalid claim limit')\n const claimed: AgentJobRecord<ServerAgentTaskRequest>[] = []\n let after=''\n for(let page=0;page<100&&claimed.length<limit;page++){\n const rows=await tx.list<AgentJobRecord<ServerAgentTaskRequest>>('agent-job/',after,100)\n if(!rows.length)break\n for(const row of rows){\n after=row.id\n const value = row.value\n if(value.tenantId!==options.principal.tenantId||value.request.tenantId!==options.principal.tenantId)throw new Error('Foreign agent job')\n if(value.requestHash!==hashAgentJobRequest(value.request))throw new Error('Agent job request integrity differs')\n if (!matchesClaimFilter(value, filter)) continue\n const due = (value.status === 'queued' || value.status === 'retryable') && Date.parse(value.availableAt) <= current.getTime()\n const stale = value.status === 'running' && Date.parse(value.leaseUntil ?? '') <= current.getTime() - staleAfterMs\n if (!due && !stale) continue\n const next = { ...value, status: 'running' as const, attempt: value.attempt + 1, leaseToken: randomUUID(), leaseUntil: new Date(current.getTime() + leaseMs).toISOString(), updatedAt: current.toISOString(), error: undefined, failureCode: undefined }\n await tx.put(row.id, next, row.revision)\n claimed.push(structuredClone(next))\n if(claimed.length===limit)break\n }\n if(rows.length<100)break\n if(page===99&&claimed.length<limit)throw new Error('Agent job scan exceeds bounded capacity; archive completed jobs before retrying')\n }\n await options.verifyCurrent?.()\n return claimed\n })\n },\n markCompleted(jobId, token, result, _current) { return update(jobId, token, async(job, nowValue, tx) => {await options.onCompleted?.(tx,structuredClone(job),structuredClone(result));return{ ...job, status: 'succeeded', result, updatedAt: nowValue.toISOString(), leaseToken: undefined, leaseUntil: undefined }}) },\n markWaitingApproval(jobId, token, result, _current) { return update(jobId, token, (job, nowValue) => ({ ...job, status: 'waiting_approval', result, updatedAt: nowValue.toISOString(), leaseToken: undefined, leaseUntil: undefined })) },\n markRetryable(jobId, token, error, failureCode, retryAt, _current) { return update(jobId, token, (job, nowValue) => ({ ...job, status: 'retryable', error, failureCode, availableAt: retryAt.toISOString(), updatedAt: nowValue.toISOString(), leaseToken: undefined, leaseUntil: undefined })) },\n markFailed(jobId, token, error, failureCode, _current) { return update(jobId, token, (job, nowValue) => ({ ...job, status: 'failed', error, failureCode, updatedAt: nowValue.toISOString(), leaseToken: undefined, leaseUntil: undefined })) },\n }\n}\n\nfunction matchesClaimFilter(job: AgentJobRecord<ServerAgentTaskRequest>, filter?: AgentJobClaimFilter): boolean {\n if (!filter) return true\n const context = job.request.executionContext\n if (filter.dispatchMode !== undefined && (context?.dispatchMode ?? 'server-queue') !== filter.dispatchMode) return false\n if (filter.userId !== undefined && job.request.userId !== filter.userId) return false\n if (filter.runnerId !== undefined && context?.runnerId !== undefined && context.runnerId !== filter.runnerId) return false\n if (filter.routeIds !== undefined && (!context?.routeId || !filter.routeIds.includes(context.routeId))) return false\n return true\n}\n\n/**\n * Marks an owned unstarted job cancelled in the caller's atomic session.\n * @param tx - Authorized atomic session; caller commits/rolls back.\n * @param principal - Verified tenant/user who owns the job.\n * @param jobId - Stored queue job identifier.\n * @param now - Cancellation timestamp; defaults to a fresh Date.\n * @returns A cloned cancelled job, unchanged when already cancelled.\n * @throws Missing/foreign job, request integrity mismatch, status other than queued/retryable/waiting_approval/cancelled, or transaction/clock errors.\n * @remarks Running leases cannot be interrupted by this edit. This function performs no external runtime cancellation, approval-ticket revocation, or live admission callback.\n * @example\n * ```typescript\n * import { cancelAgentJob } from \"@alneas/eaos-server/agentic/agent-jobs\";\n * import type { AtomicSession } from \"@alneas/eaos-server/plugin-data/atomic\";\n * import type { PluginPrincipal } from \"@alneas/eaos-server/plugin-data\";\n * export async function cancel(tx: AtomicSession, principal: PluginPrincipal, jobId: string) { return cancelAgentJob(tx, principal, jobId); }\n * ```\n */\nexport async function cancelAgentJob(tx:AtomicSession,principal:PluginPrincipal,jobId:string,now=new Date()):Promise<AgentJobRecord<ServerAgentTaskRequest>>{\n const row=await tx.get<AgentJobRecord<ServerAgentTaskRequest>>(jobKey(jobId))\n if(!row||row.value.tenantId!==principal.tenantId||row.value.request.userId!==principal.userId)throw new Error('Agent job unavailable')\n if(row.value.request.tenantId!==principal.tenantId||row.value.requestHash!==hashAgentJobRequest(row.value.request))throw new Error('Agent job request integrity differs')\n if(row.value.status==='cancelled')return structuredClone(row.value)\n if(!['queued','retryable','waiting_approval'].includes(row.value.status))throw new Error('Only an unstarted agent job can be cancelled')\n const next={...row.value,status:'cancelled' as const,updatedAt:now.toISOString(),leaseToken:undefined,leaseUntil:undefined}\n await tx.put(row.id,next,row.revision);return structuredClone(next)\n}\n"],"mappings":"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","names":["idempotencyKey"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// src/health/index.ts
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
import { createLivenessReport, runReadinessChecks } from "@alneas/eaos-core";
|
|
4
|
+
function createHealthRouter(options) {
|
|
5
|
+
if (!options || typeof options.appName !== "string" || !options.appName.trim()) throw new Error("Health app name is required");
|
|
6
|
+
const app = new Hono();
|
|
7
|
+
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
8
|
+
app.get("/health", (c) => c.json({ status: "healthy", appName: options.appName, ...createLivenessReport(now()) }));
|
|
9
|
+
app.get("/health/live", (c) => c.json({ status: "healthy", appName: options.appName, ...createLivenessReport(now()) }));
|
|
10
|
+
app.get("/health/ready", async (c) => {
|
|
11
|
+
const report = await runReadinessChecks(options.checks ?? [], now());
|
|
12
|
+
return c.json({ appName: options.appName, ...report }, report.state === "not_ready" ? 503 : 200);
|
|
13
|
+
});
|
|
14
|
+
return app;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
createHealthRouter
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=chunk-R4ADP2DC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/health/index.ts"],"sourcesContent":["import { Hono } from 'hono'\nimport { createLivenessReport, runReadinessChecks, type HealthCheck } from '@alneas/eaos-core'\n\n/**\n * Host inputs for the unauthenticated liveness and readiness routes.\n * @see createHealthRouter for a complete usage example.\n */\nexport interface HealthRouterOptions {\n /** Nonblank application label included in every response. */\n appName: string\n /** Readiness checks run on each readiness request; defaults to no checks. */\n checks?: readonly HealthCheck[]\n /** Clock read separately for each request; defaults to the current Date. */\n now?: () => Date\n}\n\n/**\n * Creates public /health, /health/live, and /health/ready routes.\n * @param options - Application label, readiness checks, and optional clock.\n * @returns A new Hono router; liveness returns 200 and readiness returns 503 only for a not_ready report.\n * @throws If appName is missing, not a string, or blank. Request handling may reject if the supplied clock throws.\n * @remarks Check implementations and credentials remain host-owned. These routes install no authentication; mount them where public health status is appropriate. Checks run for every readiness request with no caching by this router.\n * @example\n * ```typescript\n * import { Hono } from \"hono\";\n * import { createHealthRouter } from \"@alneas/eaos-server/health\";\n * const app = new Hono();\n * app.route(\"/\", createHealthRouter({ appName: \"example-service\" }));\n * const response = await app.request(\"/health/live\");\n * console.log(response.status); // 200\n * ```\n */\nexport function createHealthRouter(options: HealthRouterOptions): Hono {\n if (!options || typeof options.appName !== 'string' || !options.appName.trim()) throw new Error('Health app name is required')\n const app = new Hono()\n const now = options.now ?? (() => new Date())\n app.get('/health', c => c.json({ status: 'healthy', appName: options.appName, ...createLivenessReport(now()) }))\n app.get('/health/live', c => c.json({ status: 'healthy', appName: options.appName, ...createLivenessReport(now()) }))\n app.get('/health/ready', async c => {\n const report = await runReadinessChecks(options.checks ?? [], now())\n return c.json({ appName: options.appName, ...report }, report.state === 'not_ready' ? 503 : 200)\n })\n return app\n}\n"],"mappings":";AAAA,SAAS,YAAY;AACrB,SAAS,sBAAsB,0BAA4C;AA+BpE,SAAS,mBAAmB,SAAoC;AACrE,MAAI,CAAC,WAAW,OAAO,QAAQ,YAAY,YAAY,CAAC,QAAQ,QAAQ,KAAK,EAAG,OAAM,IAAI,MAAM,6BAA6B;AAC7H,QAAM,MAAM,IAAI,KAAK;AACrB,QAAM,MAAM,QAAQ,QAAQ,MAAM,oBAAI,KAAK;AAC3C,MAAI,IAAI,WAAW,OAAK,EAAE,KAAK,EAAE,QAAQ,WAAW,SAAS,QAAQ,SAAS,GAAG,qBAAqB,IAAI,CAAC,EAAE,CAAC,CAAC;AAC/G,MAAI,IAAI,gBAAgB,OAAK,EAAE,KAAK,EAAE,QAAQ,WAAW,SAAS,QAAQ,SAAS,GAAG,qBAAqB,IAAI,CAAC,EAAE,CAAC,CAAC;AACpH,MAAI,IAAI,iBAAiB,OAAM,MAAK;AAClC,UAAM,SAAS,MAAM,mBAAmB,QAAQ,UAAU,CAAC,GAAG,IAAI,CAAC;AACnE,WAAO,EAAE,KAAK,EAAE,SAAS,QAAQ,SAAS,GAAG,OAAO,GAAG,OAAO,UAAU,cAAc,MAAM,GAAG;AAAA,EACjG,CAAC;AACD,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// src/lifecycle-engine/platform/runtime.ts
|
|
2
|
+
function validTarget(b) {
|
|
3
|
+
return !!b && typeof b === "object" && !Array.isArray(b) && !Object.keys(b).some((k) => !["id", "databaseId", "dialect", "mode", "tenantId"].includes(k)) && typeof b.id === "string" && !!b.id.trim() && typeof b.databaseId === "string" && !!b.databaseId.trim() && ["sqlite", "oracle", "postgres"].includes(b.dialect) && ["shared", "dedicated"].includes(b.mode) && (b.mode === "shared" ? b.tenantId === void 0 : typeof b.tenantId === "string" && !!b.tenantId.trim() && b.tenantId === b.tenantId.trim() && b.tenantId !== "*");
|
|
4
|
+
}
|
|
5
|
+
function platformDatabaseTargets(runtime) {
|
|
6
|
+
return [runtime.backend, ...runtime.databaseTargets ?? []];
|
|
7
|
+
}
|
|
8
|
+
function validatePlatformRuntime(value, packages = []) {
|
|
9
|
+
if (value === void 0) return void 0;
|
|
10
|
+
const v = value;
|
|
11
|
+
if (!v || typeof v !== "object" || Array.isArray(v) || Object.keys(v).some((k) => !["backend", "databaseTargets", "tenantPlacements"].includes(k)) || !validTarget(v.backend) || v.databaseTargets !== void 0 && !Array.isArray(v.databaseTargets)) throw new Error("Invalid platform runtime backend");
|
|
12
|
+
const targets = platformDatabaseTargets(v), ids = /* @__PURE__ */ new Set(), dedicatedTenants = /* @__PURE__ */ new Set();
|
|
13
|
+
for (const target of targets) {
|
|
14
|
+
if (!validTarget(target) || ids.has(target.id)) throw new Error("Invalid or duplicate database target");
|
|
15
|
+
ids.add(target.id);
|
|
16
|
+
if (target.mode === "dedicated") {
|
|
17
|
+
if (dedicatedTenants.has(target.tenantId)) throw new Error("Duplicate dedicated tenant target");
|
|
18
|
+
dedicatedTenants.add(target.tenantId);
|
|
19
|
+
if (targets.some((other) => other.id !== target.id && other.databaseId === target.databaseId)) throw new Error("Dedicated database must have its own databaseId");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (v.tenantPlacements !== void 0 && (!v.tenantPlacements || typeof v.tenantPlacements !== "object" || Array.isArray(v.tenantPlacements))) throw new Error("Invalid tenant placements");
|
|
23
|
+
for (const [tenant, id] of Object.entries(v.tenantPlacements ?? {})) {
|
|
24
|
+
const target = targets.find((t) => t.id === id);
|
|
25
|
+
if (!tenant.trim() || tenant !== tenant.trim() || tenant === "*" || !target || target.mode === "dedicated" && target.tenantId !== tenant) throw new Error("Tenant placement does not match database target");
|
|
26
|
+
}
|
|
27
|
+
for (const target of targets) if (target.mode === "dedicated" && target.id !== v.backend.id && v.tenantPlacements?.[target.tenantId] !== target.id) throw new Error("Dedicated database requires an explicit tenant placement");
|
|
28
|
+
for (const entry of packages) for (const tenant of entry.tenantIds) resolvePlatformDatabaseTarget(v, tenant);
|
|
29
|
+
return v;
|
|
30
|
+
}
|
|
31
|
+
function resolvePlatformDatabaseTarget(runtime, tenantId) {
|
|
32
|
+
if (typeof tenantId !== "string" || !tenantId.trim() || tenantId === "*") throw new Error("Verified tenant is required");
|
|
33
|
+
const id = Object.hasOwn(runtime.tenantPlacements ?? {}, tenantId) ? runtime.tenantPlacements[tenantId] : runtime.backend.id;
|
|
34
|
+
const target = platformDatabaseTargets(runtime).find((t) => t.id === id);
|
|
35
|
+
if (!target || target.mode === "dedicated" && target.tenantId !== tenantId) throw new Error("Tenant database target unavailable");
|
|
36
|
+
return target;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export {
|
|
40
|
+
platformDatabaseTargets,
|
|
41
|
+
validatePlatformRuntime,
|
|
42
|
+
resolvePlatformDatabaseTarget
|
|
43
|
+
};
|
|
44
|
+
//# sourceMappingURL=chunk-RHN4Y6VS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lifecycle-engine/platform/runtime.ts"],"sourcesContent":["import type { BackendTarget } from '../index'\nimport type { PlatformPackage } from './artifacts'\n\n/**\n * Nonsecret target catalog for routing databases within one shared execution host.\n * \n * \n * Database placement inside one shared execution host. Targets reuse lifecycle identities.\n * @see validatePlatformRuntime for canonical integration usage.\n */\nexport interface PlatformRuntimeConfig {\n /**\n * Default target used for tenants without an explicit placement.\n */\n backend: BackendTarget\n /**\n * Optional additional target definitions with distinct IDs.\n */\n databaseTargets?: BackendTarget[]\n /**\n * Optional verified tenant ID to configured target-ID map; dedicated additional targets require explicit entries.\n */\n tenantPlacements?: Record<string, string>\n}\nfunction validTarget(b: BackendTarget): boolean {\n return !!b && typeof b === 'object' && !Array.isArray(b) && !Object.keys(b).some(k => !['id', 'databaseId', 'dialect', 'mode', 'tenantId'].includes(k))\n && typeof b.id === 'string' && !!b.id.trim() && typeof b.databaseId === 'string' && !!b.databaseId.trim()\n && ['sqlite', 'oracle', 'postgres'].includes(b.dialect) && ['shared', 'dedicated'].includes(b.mode)\n && (b.mode === 'shared' ? b.tenantId === undefined : typeof b.tenantId === 'string' && !!b.tenantId.trim() && b.tenantId === b.tenantId.trim() && b.tenantId !== '*')\n}\n/**\n * Lists the default and additional configured database targets in declaration order.\n * @param runtime - Platform runtime configuration; validate it before use.\n * @returns A new array containing runtime.backend followed by databaseTargets; target objects are shared.\n * \n * @remarks\n * Does not validate or deduplicate targets and performs no discovery or connection.\n * \n * @example\n * ```typescript\n * import { platformDatabaseTargets } from '@alneas/eaos-server/platform/runtime';\n * const targets = platformDatabaseTargets({ backend: { id: 'main', databaseId: 'db-main', dialect: 'sqlite', mode: 'shared' } });\n * ```\n */\nexport function platformDatabaseTargets(runtime: PlatformRuntimeConfig): BackendTarget[] { return [runtime.backend, ...(runtime.databaseTargets ?? [])] }\n/**\n * Validates explicit database placements and verifies that registered package tenants resolve to permitted targets.\n * @param value - Untrusted runtime declaration, or undefined when no runtime is configured.\n * @param packages - Validated package registrations whose tenant scopes must resolve; defaults to none.\n * @returns The original runtime object when accepted, or undefined for absent configuration.\n * @throws Error for malformed/duplicate targets, shared/dedicated scope mismatch, missing explicit placements or unresolved package tenants.\n * \n * @remarks\n * Dedicated targets require unique physical database IDs and explicit placements unless they are the default backend. This checks nonsecret declarations only; host pools, provisioning and membership remain external.\n * \n * @example\n * ```typescript\n * import { validatePlatformRuntime } from '@alneas/eaos-server/platform/runtime';\n * const runtime = validatePlatformRuntime({ backend: { id: 'main', databaseId: 'db-main', dialect: 'sqlite', mode: 'shared' } });\n * ```\n */\nexport function validatePlatformRuntime(value: unknown, packages: PlatformPackage[] = []): PlatformRuntimeConfig | undefined {\n if (value === undefined) return undefined\n const v = value as PlatformRuntimeConfig\n if (!v || typeof v !== 'object' || Array.isArray(v) || Object.keys(v).some(k => !['backend', 'databaseTargets', 'tenantPlacements'].includes(k))\n || !validTarget(v.backend) || (v.databaseTargets !== undefined && !Array.isArray(v.databaseTargets))) throw new Error('Invalid platform runtime backend')\n const targets = platformDatabaseTargets(v), ids = new Set<string>(), dedicatedTenants = new Set<string>()\n for (const target of targets) {\n if (!validTarget(target) || ids.has(target.id)) throw new Error('Invalid or duplicate database target')\n ids.add(target.id)\n if (target.mode === 'dedicated') {\n if (dedicatedTenants.has(target.tenantId!)) throw new Error('Duplicate dedicated tenant target')\n dedicatedTenants.add(target.tenantId!)\n if (targets.some(other => other.id !== target.id && other.databaseId === target.databaseId)) throw new Error('Dedicated database must have its own databaseId')\n }\n }\n if (v.tenantPlacements !== undefined && (!v.tenantPlacements || typeof v.tenantPlacements !== 'object' || Array.isArray(v.tenantPlacements))) throw new Error('Invalid tenant placements')\n for (const [tenant, id] of Object.entries(v.tenantPlacements ?? {})) {\n const target = targets.find(t => t.id === id)\n if (!tenant.trim() || tenant !== tenant.trim() || tenant === '*' || !target || (target.mode === 'dedicated' && target.tenantId !== tenant)) throw new Error('Tenant placement does not match database target')\n }\n for (const target of targets) if (target.mode === 'dedicated' && target.id !== v.backend.id && v.tenantPlacements?.[target.tenantId!] !== target.id) throw new Error('Dedicated database requires an explicit tenant placement')\n for (const entry of packages) for (const tenant of entry.tenantIds) resolvePlatformDatabaseTarget(v, tenant)\n return v\n}\n/**\n * Resolves a verified tenant to its explicit placement or the configured default backend.\n * \n * Tenant comes from verified host membership, never raw request input. No implicit migration/fallback.\n * @param runtime - Previously validated runtime configuration.\n * @param tenantId - Verified concrete tenant from host membership, never raw request input.\n * @returns The configured target object without cloning.\n * @throws Error for missing/wildcard tenant or unavailable/mismatched target.\n * \n * @remarks\n * Unmapped tenants use the configured default; a missing or dedicated-mismatched selected target fails rather than trying another database. No migration or database operation occurs.\n * \n * @example\n * ```typescript\n * import { resolvePlatformDatabaseTarget } from '@alneas/eaos-server/platform/runtime';\n * const target = resolvePlatformDatabaseTarget({ backend: { id: 'main', databaseId: 'db-main', dialect: 'sqlite', mode: 'shared' } }, 'sample');\n * ```\n */\nexport function resolvePlatformDatabaseTarget(runtime: PlatformRuntimeConfig, tenantId: string): BackendTarget {\n if (typeof tenantId !== 'string' || !tenantId.trim() || tenantId === '*') throw new Error('Verified tenant is required')\n const id = Object.hasOwn(runtime.tenantPlacements ?? {}, tenantId) ? runtime.tenantPlacements![tenantId] : runtime.backend.id\n const target = platformDatabaseTargets(runtime).find(t => t.id === id)\n if (!target || (target.mode === 'dedicated' && target.tenantId !== tenantId)) throw new Error('Tenant database target unavailable')\n return target\n}\n"],"mappings":"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","names":[]}
|