@plumbus/core 0.3.3 → 0.4.1
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/README.md +2 -2
- package/bin/plumbus.js +16 -2
- package/dist/.tsbuildinfo +1 -1
- package/dist/ai/ai-service.d.ts.map +1 -1
- package/dist/ai/ai-service.js +8 -3
- package/dist/ai/ai-service.js.map +1 -1
- package/dist/ai/provider.d.ts.map +1 -1
- package/dist/ai/provider.js +1 -2
- package/dist/ai/provider.js.map +1 -1
- package/dist/ai/rag/pipeline.d.ts +4 -0
- package/dist/ai/rag/pipeline.d.ts.map +1 -1
- package/dist/ai/rag/pipeline.js +17 -2
- package/dist/ai/rag/pipeline.js.map +1 -1
- package/dist/ai/security.d.ts.map +1 -1
- package/dist/ai/security.js +14 -1
- package/dist/ai/security.js.map +1 -1
- package/dist/api/route-generator.d.ts.map +1 -1
- package/dist/api/route-generator.js +57 -11
- package/dist/api/route-generator.js.map +1 -1
- package/dist/auth/adapter.d.ts +0 -4
- package/dist/auth/adapter.d.ts.map +1 -1
- package/dist/auth/adapter.js +4 -0
- package/dist/auth/adapter.js.map +1 -1
- package/dist/auth/saml-adapter.d.ts.map +1 -1
- package/dist/auth/saml-adapter.js +73 -120
- package/dist/auth/saml-adapter.js.map +1 -1
- package/dist/auth/scim.d.ts.map +1 -1
- package/dist/auth/scim.js +6 -1
- package/dist/auth/scim.js.map +1 -1
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +3 -1
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/commands/browser-extension.d.ts +10 -0
- package/dist/cli/commands/browser-extension.d.ts.map +1 -0
- package/dist/cli/commands/browser-extension.js +235 -0
- package/dist/cli/commands/browser-extension.js.map +1 -0
- package/dist/cli/commands/dev.d.ts +4 -1
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +21 -30
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/cli/commands/doctor.d.ts +6 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/commands/doctor.js +146 -2
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +18 -0
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/commands/index.d.ts +2 -0
- package/dist/cli/commands/index.d.ts.map +1 -1
- package/dist/cli/commands/index.js +2 -0
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/init.d.ts +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +122 -3
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/mcp.d.ts +9 -0
- package/dist/cli/commands/mcp.d.ts.map +1 -0
- package/dist/cli/commands/mcp.js +125 -0
- package/dist/cli/commands/mcp.js.map +1 -0
- package/dist/cli/commands/migrate.d.ts.map +1 -1
- package/dist/cli/commands/migrate.js +2 -1
- package/dist/cli/commands/migrate.js.map +1 -1
- package/dist/cli/commands/start.d.ts +4 -1
- package/dist/cli/commands/start.d.ts.map +1 -1
- package/dist/cli/commands/start.js +20 -29
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/commands/translation.d.ts.map +1 -1
- package/dist/cli/commands/translation.js +22 -10
- package/dist/cli/commands/translation.js.map +1 -1
- package/dist/cli/mcp-serve-context.d.ts +13 -0
- package/dist/cli/mcp-serve-context.d.ts.map +1 -0
- package/dist/cli/mcp-serve-context.js +119 -0
- package/dist/cli/mcp-serve-context.js.map +1 -0
- package/dist/cli/migrate-snapshot-schema.d.ts +15 -0
- package/dist/cli/migrate-snapshot-schema.d.ts.map +1 -0
- package/dist/cli/migrate-snapshot-schema.js +23 -0
- package/dist/cli/migrate-snapshot-schema.js.map +1 -0
- package/dist/cli/project-root.d.ts +9 -0
- package/dist/cli/project-root.d.ts.map +1 -0
- package/dist/cli/project-root.js +41 -0
- package/dist/cli/project-root.js.map +1 -0
- package/dist/cli/scaffold-write.d.ts +17 -0
- package/dist/cli/scaffold-write.d.ts.map +1 -0
- package/dist/cli/scaffold-write.js +40 -0
- package/dist/cli/scaffold-write.js.map +1 -0
- package/dist/cli/templates/resources.d.ts.map +1 -1
- package/dist/cli/templates/resources.js +7 -1
- package/dist/cli/templates/resources.js.map +1 -1
- package/dist/cli/utils.d.ts +2 -0
- package/dist/cli/utils.d.ts.map +1 -1
- package/dist/cli/utils.js +14 -0
- package/dist/cli/utils.js.map +1 -1
- package/dist/config/loader.d.ts +1 -1
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +46 -27
- package/dist/config/loader.js.map +1 -1
- package/dist/data/connection.d.ts +16 -0
- package/dist/data/connection.d.ts.map +1 -0
- package/dist/data/connection.js +27 -0
- package/dist/data/connection.js.map +1 -0
- package/dist/data/index.d.ts +1 -0
- package/dist/data/index.d.ts.map +1 -1
- package/dist/data/index.js +1 -0
- package/dist/data/index.js.map +1 -1
- package/dist/data/migration.d.ts.map +1 -1
- package/dist/data/migration.js +24 -0
- package/dist/data/migration.js.map +1 -1
- package/dist/data/repository.d.ts.map +1 -1
- package/dist/data/repository.js +32 -9
- package/dist/data/repository.js.map +1 -1
- package/dist/define/defineCapability.d.ts +4 -2
- package/dist/define/defineCapability.d.ts.map +1 -1
- package/dist/define/defineCapability.js +68 -8
- package/dist/define/defineCapability.js.map +1 -1
- package/dist/define/defineEntity.d.ts.map +1 -1
- package/dist/define/defineEntity.js +8 -4
- package/dist/define/defineEntity.js.map +1 -1
- package/dist/define/defineEvent.d.ts.map +1 -1
- package/dist/define/defineEvent.js +7 -3
- package/dist/define/defineEvent.js.map +1 -1
- package/dist/define/defineFlow.d.ts.map +1 -1
- package/dist/define/defineFlow.js +11 -5
- package/dist/define/defineFlow.js.map +1 -1
- package/dist/define/definePrompt.d.ts.map +1 -1
- package/dist/define/definePrompt.js +11 -5
- package/dist/define/definePrompt.js.map +1 -1
- package/dist/define/defineTranslation.d.ts.map +1 -1
- package/dist/define/defineTranslation.js +12 -10
- package/dist/define/defineTranslation.js.map +1 -1
- package/dist/define/validation-error.d.ts +2 -0
- package/dist/define/validation-error.d.ts.map +1 -0
- package/dist/define/validation-error.js +9 -0
- package/dist/define/validation-error.js.map +1 -0
- package/dist/errors/hints.d.ts +13 -0
- package/dist/errors/hints.d.ts.map +1 -0
- package/dist/errors/hints.js +15 -0
- package/dist/errors/hints.js.map +1 -0
- package/dist/errors/hook-log.d.ts +3 -0
- package/dist/errors/hook-log.d.ts.map +1 -0
- package/dist/errors/hook-log.js +6 -0
- package/dist/errors/hook-log.js.map +1 -0
- package/dist/errors/http.d.ts +19 -0
- package/dist/errors/http.d.ts.map +1 -1
- package/dist/errors/http.js +32 -2
- package/dist/errors/http.js.map +1 -1
- package/dist/errors/safe-metadata.d.ts +5 -0
- package/dist/errors/safe-metadata.d.ts.map +1 -0
- package/dist/errors/safe-metadata.js +24 -0
- package/dist/errors/safe-metadata.js.map +1 -0
- package/dist/execution/auth-roles.d.ts +6 -0
- package/dist/execution/auth-roles.d.ts.map +1 -0
- package/dist/execution/auth-roles.js +12 -0
- package/dist/execution/auth-roles.js.map +1 -0
- package/dist/execution/authorization.d.ts.map +1 -1
- package/dist/execution/authorization.js +3 -5
- package/dist/execution/authorization.js.map +1 -1
- package/dist/execution/context-factory.d.ts +2 -17
- package/dist/execution/context-factory.d.ts.map +1 -1
- package/dist/execution/context-factory.js +1 -0
- package/dist/execution/context-factory.js.map +1 -1
- package/dist/flows/engine.js +1 -1
- package/dist/flows/engine.js.map +1 -1
- package/dist/flows/evaluate-condition.d.ts +19 -0
- package/dist/flows/evaluate-condition.d.ts.map +1 -0
- package/dist/flows/evaluate-condition.js +276 -0
- package/dist/flows/evaluate-condition.js.map +1 -0
- package/dist/flows/index.d.ts +1 -0
- package/dist/flows/index.d.ts.map +1 -1
- package/dist/flows/index.js +2 -0
- package/dist/flows/index.js.map +1 -1
- package/dist/flows/step-executor.d.ts.map +1 -1
- package/dist/flows/step-executor.js +15 -7
- package/dist/flows/step-executor.js.map +1 -1
- package/dist/governance/rules/architecture.d.ts.map +1 -1
- package/dist/governance/rules/architecture.js +2 -0
- package/dist/governance/rules/architecture.js.map +1 -1
- package/dist/governance/rules/index.d.ts +1 -0
- package/dist/governance/rules/index.d.ts.map +1 -1
- package/dist/governance/rules/index.js +1 -0
- package/dist/governance/rules/index.js.map +1 -1
- package/dist/governance/rules/mcp.d.ts +5 -0
- package/dist/governance/rules/mcp.d.ts.map +1 -0
- package/dist/governance/rules/mcp.js +34 -0
- package/dist/governance/rules/mcp.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp/exposure.d.ts +4 -0
- package/dist/mcp/exposure.d.ts.map +1 -0
- package/dist/mcp/exposure.js +5 -0
- package/dist/mcp/exposure.js.map +1 -0
- package/dist/mcp/index.d.ts +4 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +6 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/manifest-generator.d.ts +19 -0
- package/dist/mcp/manifest-generator.d.ts.map +1 -0
- package/dist/mcp/manifest-generator.js +29 -0
- package/dist/mcp/manifest-generator.js.map +1 -0
- package/dist/mcp/skill-generator.d.ts +4 -0
- package/dist/mcp/skill-generator.d.ts.map +1 -0
- package/dist/mcp/skill-generator.js +59 -0
- package/dist/mcp/skill-generator.js.map +1 -0
- package/dist/schema/zod-input-to-json-schema.d.ts +4 -0
- package/dist/schema/zod-input-to-json-schema.d.ts.map +1 -0
- package/dist/schema/zod-input-to-json-schema.js +6 -0
- package/dist/schema/zod-input-to-json-schema.js.map +1 -0
- package/dist/server/bootstrap.d.ts.map +1 -1
- package/dist/server/bootstrap.js +8 -3
- package/dist/server/bootstrap.js.map +1 -1
- package/dist/testing/context.d.ts +2 -0
- package/dist/testing/context.d.ts.map +1 -1
- package/dist/testing/context.js +1 -0
- package/dist/testing/context.js.map +1 -1
- package/dist/testing/e2e.js +1 -1
- package/dist/testing/e2e.js.map +1 -1
- package/dist/types/capability.d.ts +13 -0
- package/dist/types/capability.d.ts.map +1 -1
- package/dist/types/config.d.ts +9 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/context.d.ts +31 -0
- package/dist/types/context.d.ts.map +1 -1
- package/dist/types/deep-freeze.d.ts +6 -0
- package/dist/types/deep-freeze.d.ts.map +1 -0
- package/dist/types/deep-freeze.js +38 -0
- package/dist/types/deep-freeze.js.map +1 -0
- package/dist/types/enums.d.ts +5 -0
- package/dist/types/enums.d.ts.map +1 -1
- package/dist/types/enums.js +5 -0
- package/dist/types/enums.js.map +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/security.d.ts +3 -1
- package/dist/types/security.d.ts.map +1 -1
- package/instructions/capabilities.md +36 -0
- package/instructions/cli.md +44 -0
- package/instructions/framework.md +20 -4
- package/instructions/mcp.md +20 -0
- package/package.json +21 -2
- package/dist/ai/__tests__/ai-service.test.d.ts +0 -2
- package/dist/ai/__tests__/ai-service.test.d.ts.map +0 -1
- package/dist/ai/__tests__/ai-service.test.js +0 -1078
- package/dist/ai/__tests__/ai-service.test.js.map +0 -1
- package/dist/ai/__tests__/chunking.test.d.ts +0 -2
- package/dist/ai/__tests__/chunking.test.d.ts.map +0 -1
- package/dist/ai/__tests__/chunking.test.js +0 -78
- package/dist/ai/__tests__/chunking.test.js.map +0 -1
- package/dist/ai/__tests__/cost-tracker.test.d.ts +0 -2
- package/dist/ai/__tests__/cost-tracker.test.d.ts.map +0 -1
- package/dist/ai/__tests__/cost-tracker.test.js +0 -201
- package/dist/ai/__tests__/cost-tracker.test.js.map +0 -1
- package/dist/ai/__tests__/explainability.test.d.ts +0 -2
- package/dist/ai/__tests__/explainability.test.d.ts.map +0 -1
- package/dist/ai/__tests__/explainability.test.js +0 -100
- package/dist/ai/__tests__/explainability.test.js.map +0 -1
- package/dist/ai/__tests__/model-pricing.test.d.ts +0 -2
- package/dist/ai/__tests__/model-pricing.test.d.ts.map +0 -1
- package/dist/ai/__tests__/model-pricing.test.js +0 -156
- package/dist/ai/__tests__/model-pricing.test.js.map +0 -1
- package/dist/ai/__tests__/prompt-registry.test.d.ts +0 -2
- package/dist/ai/__tests__/prompt-registry.test.d.ts.map +0 -1
- package/dist/ai/__tests__/prompt-registry.test.js +0 -56
- package/dist/ai/__tests__/prompt-registry.test.js.map +0 -1
- package/dist/ai/__tests__/provider.test.d.ts +0 -3
- package/dist/ai/__tests__/provider.test.d.ts.map +0 -1
- package/dist/ai/__tests__/provider.test.js +0 -979
- package/dist/ai/__tests__/provider.test.js.map +0 -1
- package/dist/ai/__tests__/rag-pipeline.test.d.ts +0 -2
- package/dist/ai/__tests__/rag-pipeline.test.d.ts.map +0 -1
- package/dist/ai/__tests__/rag-pipeline.test.js +0 -174
- package/dist/ai/__tests__/rag-pipeline.test.js.map +0 -1
- package/dist/ai/__tests__/security.test.d.ts +0 -2
- package/dist/ai/__tests__/security.test.d.ts.map +0 -1
- package/dist/ai/__tests__/security.test.js +0 -89
- package/dist/ai/__tests__/security.test.js.map +0 -1
- package/dist/ai/__tests__/usage-client.test.d.ts +0 -2
- package/dist/ai/__tests__/usage-client.test.d.ts.map +0 -1
- package/dist/ai/__tests__/usage-client.test.js +0 -120
- package/dist/ai/__tests__/usage-client.test.js.map +0 -1
- package/dist/ai/__tests__/validation.test.d.ts +0 -2
- package/dist/ai/__tests__/validation.test.d.ts.map +0 -1
- package/dist/ai/__tests__/validation.test.js +0 -384
- package/dist/ai/__tests__/validation.test.js.map +0 -1
- package/dist/ai/__tests__/zod-to-provider-schema.test.d.ts +0 -2
- package/dist/ai/__tests__/zod-to-provider-schema.test.d.ts.map +0 -1
- package/dist/ai/__tests__/zod-to-provider-schema.test.js +0 -60
- package/dist/ai/__tests__/zod-to-provider-schema.test.js.map +0 -1
- package/dist/api/__tests__/route-generator.test.d.ts +0 -2
- package/dist/api/__tests__/route-generator.test.d.ts.map +0 -1
- package/dist/api/__tests__/route-generator.test.js +0 -216
- package/dist/api/__tests__/route-generator.test.js.map +0 -1
- package/dist/audit/__tests__/service.test.d.ts +0 -2
- package/dist/audit/__tests__/service.test.d.ts.map +0 -1
- package/dist/audit/__tests__/service.test.js +0 -97
- package/dist/audit/__tests__/service.test.js.map +0 -1
- package/dist/auth/__tests__/adapter.test.d.ts +0 -2
- package/dist/auth/__tests__/adapter.test.d.ts.map +0 -1
- package/dist/auth/__tests__/adapter.test.js +0 -218
- package/dist/auth/__tests__/adapter.test.js.map +0 -1
- package/dist/auth/__tests__/oidc-adapter.test.d.ts +0 -2
- package/dist/auth/__tests__/oidc-adapter.test.d.ts.map +0 -1
- package/dist/auth/__tests__/oidc-adapter.test.js +0 -232
- package/dist/auth/__tests__/oidc-adapter.test.js.map +0 -1
- package/dist/auth/__tests__/password.test.d.ts +0 -2
- package/dist/auth/__tests__/password.test.d.ts.map +0 -1
- package/dist/auth/__tests__/password.test.js +0 -30
- package/dist/auth/__tests__/password.test.js.map +0 -1
- package/dist/auth/__tests__/saml-adapter.test.d.ts +0 -2
- package/dist/auth/__tests__/saml-adapter.test.d.ts.map +0 -1
- package/dist/auth/__tests__/saml-adapter.test.js +0 -188
- package/dist/auth/__tests__/saml-adapter.test.js.map +0 -1
- package/dist/auth/__tests__/scim.test.d.ts +0 -2
- package/dist/auth/__tests__/scim.test.d.ts.map +0 -1
- package/dist/auth/__tests__/scim.test.js +0 -306
- package/dist/auth/__tests__/scim.test.js.map +0 -1
- package/dist/cli/__tests__/agent.test.d.ts +0 -2
- package/dist/cli/__tests__/agent.test.d.ts.map +0 -1
- package/dist/cli/__tests__/agent.test.js +0 -63
- package/dist/cli/__tests__/agent.test.js.map +0 -1
- package/dist/cli/__tests__/certify.test.d.ts +0 -2
- package/dist/cli/__tests__/certify.test.d.ts.map +0 -1
- package/dist/cli/__tests__/certify.test.js +0 -97
- package/dist/cli/__tests__/certify.test.js.map +0 -1
- package/dist/cli/__tests__/cli.test.d.ts +0 -2
- package/dist/cli/__tests__/cli.test.d.ts.map +0 -1
- package/dist/cli/__tests__/cli.test.js +0 -123
- package/dist/cli/__tests__/cli.test.js.map +0 -1
- package/dist/cli/__tests__/create.test.d.ts +0 -2
- package/dist/cli/__tests__/create.test.d.ts.map +0 -1
- package/dist/cli/__tests__/create.test.js +0 -183
- package/dist/cli/__tests__/create.test.js.map +0 -1
- package/dist/cli/__tests__/dev.test.d.ts +0 -2
- package/dist/cli/__tests__/dev.test.d.ts.map +0 -1
- package/dist/cli/__tests__/dev.test.js +0 -132
- package/dist/cli/__tests__/dev.test.js.map +0 -1
- package/dist/cli/__tests__/discover.test.d.ts +0 -2
- package/dist/cli/__tests__/discover.test.d.ts.map +0 -1
- package/dist/cli/__tests__/discover.test.js +0 -48
- package/dist/cli/__tests__/discover.test.js.map +0 -1
- package/dist/cli/__tests__/doctor.test.d.ts +0 -2
- package/dist/cli/__tests__/doctor.test.d.ts.map +0 -1
- package/dist/cli/__tests__/doctor.test.js +0 -172
- package/dist/cli/__tests__/doctor.test.js.map +0 -1
- package/dist/cli/__tests__/generate.test.d.ts +0 -2
- package/dist/cli/__tests__/generate.test.d.ts.map +0 -1
- package/dist/cli/__tests__/generate.test.js +0 -478
- package/dist/cli/__tests__/generate.test.js.map +0 -1
- package/dist/cli/__tests__/init.test.d.ts +0 -2
- package/dist/cli/__tests__/init.test.d.ts.map +0 -1
- package/dist/cli/__tests__/init.test.js +0 -236
- package/dist/cli/__tests__/init.test.js.map +0 -1
- package/dist/cli/__tests__/migrate.test.d.ts +0 -2
- package/dist/cli/__tests__/migrate.test.d.ts.map +0 -1
- package/dist/cli/__tests__/migrate.test.js +0 -442
- package/dist/cli/__tests__/migrate.test.js.map +0 -1
- package/dist/cli/__tests__/start.test.d.ts +0 -2
- package/dist/cli/__tests__/start.test.d.ts.map +0 -1
- package/dist/cli/__tests__/start.test.js +0 -91
- package/dist/cli/__tests__/start.test.js.map +0 -1
- package/dist/cli/__tests__/templates.test.d.ts +0 -2
- package/dist/cli/__tests__/templates.test.d.ts.map +0 -1
- package/dist/cli/__tests__/templates.test.js +0 -49
- package/dist/cli/__tests__/templates.test.js.map +0 -1
- package/dist/cli/__tests__/test.test.d.ts +0 -2
- package/dist/cli/__tests__/test.test.d.ts.map +0 -1
- package/dist/cli/__tests__/test.test.js +0 -40
- package/dist/cli/__tests__/test.test.js.map +0 -1
- package/dist/cli/__tests__/ui.test.d.ts +0 -2
- package/dist/cli/__tests__/ui.test.d.ts.map +0 -1
- package/dist/cli/__tests__/ui.test.js +0 -76
- package/dist/cli/__tests__/ui.test.js.map +0 -1
- package/dist/cli/__tests__/upgrade.test.d.ts +0 -2
- package/dist/cli/__tests__/upgrade.test.d.ts.map +0 -1
- package/dist/cli/__tests__/upgrade.test.js +0 -20
- package/dist/cli/__tests__/upgrade.test.js.map +0 -1
- package/dist/cli/__tests__/utils.test.d.ts +0 -2
- package/dist/cli/__tests__/utils.test.d.ts.map +0 -1
- package/dist/cli/__tests__/utils.test.js +0 -255
- package/dist/cli/__tests__/utils.test.js.map +0 -1
- package/dist/cli/__tests__/verify.test.d.ts +0 -2
- package/dist/cli/__tests__/verify.test.d.ts.map +0 -1
- package/dist/cli/__tests__/verify.test.js +0 -138
- package/dist/cli/__tests__/verify.test.js.map +0 -1
- package/dist/cli/commands/__tests__/commands.test.d.ts +0 -2
- package/dist/cli/commands/__tests__/commands.test.d.ts.map +0 -1
- package/dist/cli/commands/__tests__/commands.test.js +0 -185
- package/dist/cli/commands/__tests__/commands.test.js.map +0 -1
- package/dist/config/__tests__/loader.test.d.ts +0 -2
- package/dist/config/__tests__/loader.test.d.ts.map +0 -1
- package/dist/config/__tests__/loader.test.js +0 -462
- package/dist/config/__tests__/loader.test.js.map +0 -1
- package/dist/data/__tests__/drift-inspector.test.d.ts +0 -2
- package/dist/data/__tests__/drift-inspector.test.d.ts.map +0 -1
- package/dist/data/__tests__/drift-inspector.test.js +0 -170
- package/dist/data/__tests__/drift-inspector.test.js.map +0 -1
- package/dist/data/__tests__/framework-migration-rewriter.test.d.ts +0 -2
- package/dist/data/__tests__/framework-migration-rewriter.test.d.ts.map +0 -1
- package/dist/data/__tests__/framework-migration-rewriter.test.js +0 -329
- package/dist/data/__tests__/framework-migration-rewriter.test.js.map +0 -1
- package/dist/data/__tests__/migration.test.d.ts +0 -2
- package/dist/data/__tests__/migration.test.d.ts.map +0 -1
- package/dist/data/__tests__/migration.test.js +0 -145
- package/dist/data/__tests__/migration.test.js.map +0 -1
- package/dist/data/__tests__/registry.test.d.ts +0 -2
- package/dist/data/__tests__/registry.test.d.ts.map +0 -1
- package/dist/data/__tests__/registry.test.js +0 -70
- package/dist/data/__tests__/registry.test.js.map +0 -1
- package/dist/data/__tests__/repository.test.d.ts +0 -2
- package/dist/data/__tests__/repository.test.d.ts.map +0 -1
- package/dist/data/__tests__/repository.test.js +0 -559
- package/dist/data/__tests__/repository.test.js.map +0 -1
- package/dist/data/__tests__/schema-generator.test.d.ts +0 -2
- package/dist/data/__tests__/schema-generator.test.d.ts.map +0 -1
- package/dist/data/__tests__/schema-generator.test.js +0 -112
- package/dist/data/__tests__/schema-generator.test.js.map +0 -1
- package/dist/define/__tests__/defineCapability.test.d.ts +0 -2
- package/dist/define/__tests__/defineCapability.test.d.ts.map +0 -1
- package/dist/define/__tests__/defineCapability.test.js +0 -66
- package/dist/define/__tests__/defineCapability.test.js.map +0 -1
- package/dist/define/__tests__/defineEntity.test.d.ts +0 -2
- package/dist/define/__tests__/defineEntity.test.d.ts.map +0 -1
- package/dist/define/__tests__/defineEntity.test.js +0 -50
- package/dist/define/__tests__/defineEntity.test.js.map +0 -1
- package/dist/define/__tests__/defineEvent.test.d.ts +0 -2
- package/dist/define/__tests__/defineEvent.test.d.ts.map +0 -1
- package/dist/define/__tests__/defineEvent.test.js +0 -35
- package/dist/define/__tests__/defineEvent.test.js.map +0 -1
- package/dist/define/__tests__/defineFlow.test.d.ts +0 -2
- package/dist/define/__tests__/defineFlow.test.d.ts.map +0 -1
- package/dist/define/__tests__/defineFlow.test.js +0 -49
- package/dist/define/__tests__/defineFlow.test.js.map +0 -1
- package/dist/define/__tests__/definePrompt.test.d.ts +0 -2
- package/dist/define/__tests__/definePrompt.test.d.ts.map +0 -1
- package/dist/define/__tests__/definePrompt.test.js +0 -77
- package/dist/define/__tests__/definePrompt.test.js.map +0 -1
- package/dist/define/__tests__/defineTranslation.test.d.ts +0 -2
- package/dist/define/__tests__/defineTranslation.test.d.ts.map +0 -1
- package/dist/define/__tests__/defineTranslation.test.js +0 -90
- package/dist/define/__tests__/defineTranslation.test.js.map +0 -1
- package/dist/errors/__tests__/errors.test.d.ts +0 -2
- package/dist/errors/__tests__/errors.test.d.ts.map +0 -1
- package/dist/errors/__tests__/errors.test.js +0 -70
- package/dist/errors/__tests__/errors.test.js.map +0 -1
- package/dist/errors/__tests__/http.test.d.ts +0 -2
- package/dist/errors/__tests__/http.test.d.ts.map +0 -1
- package/dist/errors/__tests__/http.test.js +0 -35
- package/dist/errors/__tests__/http.test.js.map +0 -1
- package/dist/events/__tests__/consumer-registry.test.d.ts +0 -2
- package/dist/events/__tests__/consumer-registry.test.d.ts.map +0 -1
- package/dist/events/__tests__/consumer-registry.test.js +0 -68
- package/dist/events/__tests__/consumer-registry.test.js.map +0 -1
- package/dist/events/__tests__/dispatcher.test.d.ts +0 -2
- package/dist/events/__tests__/dispatcher.test.d.ts.map +0 -1
- package/dist/events/__tests__/dispatcher.test.js +0 -168
- package/dist/events/__tests__/dispatcher.test.js.map +0 -1
- package/dist/events/__tests__/event-emitter.test.d.ts +0 -2
- package/dist/events/__tests__/event-emitter.test.d.ts.map +0 -1
- package/dist/events/__tests__/event-emitter.test.js +0 -172
- package/dist/events/__tests__/event-emitter.test.js.map +0 -1
- package/dist/events/__tests__/event-registry.test.d.ts +0 -2
- package/dist/events/__tests__/event-registry.test.d.ts.map +0 -1
- package/dist/events/__tests__/event-registry.test.js +0 -55
- package/dist/events/__tests__/event-registry.test.js.map +0 -1
- package/dist/events/__tests__/event-worker.test.d.ts +0 -2
- package/dist/events/__tests__/event-worker.test.d.ts.map +0 -1
- package/dist/events/__tests__/event-worker.test.js +0 -174
- package/dist/events/__tests__/event-worker.test.js.map +0 -1
- package/dist/events/__tests__/queue.test.d.ts +0 -2
- package/dist/events/__tests__/queue.test.d.ts.map +0 -1
- package/dist/events/__tests__/queue.test.js +0 -73
- package/dist/events/__tests__/queue.test.js.map +0 -1
- package/dist/execution/__tests__/authorization.test.d.ts +0 -2
- package/dist/execution/__tests__/authorization.test.d.ts.map +0 -1
- package/dist/execution/__tests__/authorization.test.js +0 -99
- package/dist/execution/__tests__/authorization.test.js.map +0 -1
- package/dist/execution/__tests__/capability-executor.test.d.ts +0 -2
- package/dist/execution/__tests__/capability-executor.test.d.ts.map +0 -1
- package/dist/execution/__tests__/capability-executor.test.js +0 -212
- package/dist/execution/__tests__/capability-executor.test.js.map +0 -1
- package/dist/execution/__tests__/capability-registry.test.d.ts +0 -2
- package/dist/execution/__tests__/capability-registry.test.d.ts.map +0 -1
- package/dist/execution/__tests__/capability-registry.test.js +0 -50
- package/dist/execution/__tests__/capability-registry.test.js.map +0 -1
- package/dist/execution/__tests__/context-factory.test.d.ts +0 -2
- package/dist/execution/__tests__/context-factory.test.d.ts.map +0 -1
- package/dist/execution/__tests__/context-factory.test.js +0 -133
- package/dist/execution/__tests__/context-factory.test.js.map +0 -1
- package/dist/explanation/__tests__/tracker.test.d.ts +0 -2
- package/dist/explanation/__tests__/tracker.test.d.ts.map +0 -1
- package/dist/explanation/__tests__/tracker.test.js +0 -169
- package/dist/explanation/__tests__/tracker.test.js.map +0 -1
- package/dist/fields/__tests__/fields.test.d.ts +0 -2
- package/dist/fields/__tests__/fields.test.d.ts.map +0 -1
- package/dist/fields/__tests__/fields.test.js +0 -60
- package/dist/fields/__tests__/fields.test.js.map +0 -1
- package/dist/flows/__tests__/flow-engine.test.d.ts +0 -2
- package/dist/flows/__tests__/flow-engine.test.d.ts.map +0 -1
- package/dist/flows/__tests__/flow-engine.test.js +0 -797
- package/dist/flows/__tests__/flow-engine.test.js.map +0 -1
- package/dist/flows/__tests__/flow-registry.test.d.ts +0 -2
- package/dist/flows/__tests__/flow-registry.test.d.ts.map +0 -1
- package/dist/flows/__tests__/flow-registry.test.js +0 -69
- package/dist/flows/__tests__/flow-registry.test.js.map +0 -1
- package/dist/flows/__tests__/flow-service.test.d.ts +0 -2
- package/dist/flows/__tests__/flow-service.test.d.ts.map +0 -1
- package/dist/flows/__tests__/flow-service.test.js +0 -55
- package/dist/flows/__tests__/flow-service.test.js.map +0 -1
- package/dist/flows/__tests__/scheduler.test.d.ts +0 -2
- package/dist/flows/__tests__/scheduler.test.d.ts.map +0 -1
- package/dist/flows/__tests__/scheduler.test.js +0 -120
- package/dist/flows/__tests__/scheduler.test.js.map +0 -1
- package/dist/flows/__tests__/state-machine.test.d.ts +0 -2
- package/dist/flows/__tests__/state-machine.test.d.ts.map +0 -1
- package/dist/flows/__tests__/state-machine.test.js +0 -62
- package/dist/flows/__tests__/state-machine.test.js.map +0 -1
- package/dist/flows/__tests__/step-executor.test.d.ts +0 -2
- package/dist/flows/__tests__/step-executor.test.d.ts.map +0 -1
- package/dist/flows/__tests__/step-executor.test.js +0 -201
- package/dist/flows/__tests__/step-executor.test.js.map +0 -1
- package/dist/flows/__tests__/triggers.test.d.ts +0 -2
- package/dist/flows/__tests__/triggers.test.d.ts.map +0 -1
- package/dist/flows/__tests__/triggers.test.js +0 -98
- package/dist/flows/__tests__/triggers.test.js.map +0 -1
- package/dist/governance/__tests__/overrides.test.d.ts +0 -2
- package/dist/governance/__tests__/overrides.test.d.ts.map +0 -1
- package/dist/governance/__tests__/overrides.test.js +0 -123
- package/dist/governance/__tests__/overrides.test.js.map +0 -1
- package/dist/governance/__tests__/policies.test.d.ts +0 -2
- package/dist/governance/__tests__/policies.test.d.ts.map +0 -1
- package/dist/governance/__tests__/policies.test.js +0 -203
- package/dist/governance/__tests__/policies.test.js.map +0 -1
- package/dist/governance/__tests__/rules.test.d.ts +0 -2
- package/dist/governance/__tests__/rules.test.d.ts.map +0 -1
- package/dist/governance/__tests__/rules.test.js +0 -342
- package/dist/governance/__tests__/rules.test.js.map +0 -1
- package/dist/observability/__tests__/metrics.test.d.ts +0 -2
- package/dist/observability/__tests__/metrics.test.d.ts.map +0 -1
- package/dist/observability/__tests__/metrics.test.js +0 -301
- package/dist/observability/__tests__/metrics.test.js.map +0 -1
- package/dist/server/__tests__/bootstrap.test.d.ts +0 -2
- package/dist/server/__tests__/bootstrap.test.d.ts.map +0 -1
- package/dist/server/__tests__/bootstrap.test.js +0 -381
- package/dist/server/__tests__/bootstrap.test.js.map +0 -1
- package/dist/testing/__tests__/context.test.d.ts +0 -2
- package/dist/testing/__tests__/context.test.d.ts.map +0 -1
- package/dist/testing/__tests__/context.test.js +0 -341
- package/dist/testing/__tests__/context.test.js.map +0 -1
- package/dist/testing/__tests__/e2e.test.d.ts +0 -2
- package/dist/testing/__tests__/e2e.test.d.ts.map +0 -1
- package/dist/testing/__tests__/e2e.test.js +0 -119
- package/dist/testing/__tests__/e2e.test.js.map +0 -1
- package/dist/testing/__tests__/field-validation.test.d.ts +0 -2
- package/dist/testing/__tests__/field-validation.test.d.ts.map +0 -1
- package/dist/testing/__tests__/field-validation.test.js +0 -178
- package/dist/testing/__tests__/field-validation.test.js.map +0 -1
- package/dist/testing/__tests__/governance.test.d.ts +0 -2
- package/dist/testing/__tests__/governance.test.d.ts.map +0 -1
- package/dist/testing/__tests__/governance.test.js +0 -222
- package/dist/testing/__tests__/governance.test.js.map +0 -1
- package/dist/testing/__tests__/run-capability.test.d.ts +0 -2
- package/dist/testing/__tests__/run-capability.test.d.ts.map +0 -1
- package/dist/testing/__tests__/run-capability.test.js +0 -114
- package/dist/testing/__tests__/run-capability.test.js.map +0 -1
- package/dist/testing/__tests__/scaffolding.test.d.ts +0 -2
- package/dist/testing/__tests__/scaffolding.test.d.ts.map +0 -1
- package/dist/testing/__tests__/scaffolding.test.js +0 -70
- package/dist/testing/__tests__/scaffolding.test.js.map +0 -1
- package/dist/testing/__tests__/security.test.d.ts +0 -2
- package/dist/testing/__tests__/security.test.d.ts.map +0 -1
- package/dist/testing/__tests__/security.test.js +0 -186
- package/dist/testing/__tests__/security.test.js.map +0 -1
- package/dist/testing/__tests__/simulate-flow.test.d.ts +0 -2
- package/dist/testing/__tests__/simulate-flow.test.d.ts.map +0 -1
- package/dist/testing/__tests__/simulate-flow.test.js +0 -230
- package/dist/testing/__tests__/simulate-flow.test.js.map +0 -1
- package/dist/translations/__tests__/translation-resolver.test.d.ts +0 -2
- package/dist/translations/__tests__/translation-resolver.test.d.ts.map +0 -1
- package/dist/translations/__tests__/translation-resolver.test.js +0 -140
- package/dist/translations/__tests__/translation-resolver.test.js.map +0 -1
- package/dist/types/__tests__/registry.test.d.ts +0 -2
- package/dist/types/__tests__/registry.test.d.ts.map +0 -1
- package/dist/types/__tests__/registry.test.js +0 -34
- package/dist/types/__tests__/registry.test.js.map +0 -1
- package/dist/worker/__tests__/bootstrap.test.d.ts +0 -2
- package/dist/worker/__tests__/bootstrap.test.d.ts.map +0 -1
- package/dist/worker/__tests__/bootstrap.test.js +0 -268
- package/dist/worker/__tests__/bootstrap.test.js.map +0 -1
- package/dist/worker/__tests__/flow-service-wiring.test.d.ts +0 -2
- package/dist/worker/__tests__/flow-service-wiring.test.d.ts.map +0 -1
- package/dist/worker/__tests__/flow-service-wiring.test.js +0 -64
- package/dist/worker/__tests__/flow-service-wiring.test.js.map +0 -1
|
@@ -1,797 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { defineFlow } from '../../define/defineFlow.js';
|
|
4
|
-
import { LeaseLostError } from '../../errors/index.js';
|
|
5
|
-
import { FlowStepType } from '../../types/enums.js';
|
|
6
|
-
import { createFlowEngine } from '../engine.js';
|
|
7
|
-
import { FlowRegistry } from '../registry.js';
|
|
8
|
-
import { FlowStatus } from '../state-machine.js';
|
|
9
|
-
/**
|
|
10
|
-
* Creates a mock DB that tracks inserts and updates,
|
|
11
|
-
* and returns rows from a provided store.
|
|
12
|
-
*/
|
|
13
|
-
function mockDb(rows = new Map()) {
|
|
14
|
-
const inserts = [];
|
|
15
|
-
const updates = [];
|
|
16
|
-
// Mutable state lets individual tests drive behavior: execute() return value and
|
|
17
|
-
// guardedUpdate rowCount (0 simulates a lost lease).
|
|
18
|
-
const state = {
|
|
19
|
-
executeResult: [],
|
|
20
|
-
updateRowCount: 1,
|
|
21
|
-
};
|
|
22
|
-
return {
|
|
23
|
-
_inserts: inserts,
|
|
24
|
-
_updates: updates,
|
|
25
|
-
_rows: rows,
|
|
26
|
-
_state: state,
|
|
27
|
-
insert: vi.fn().mockReturnValue({
|
|
28
|
-
values: vi.fn().mockImplementation((row) => {
|
|
29
|
-
inserts.push(row);
|
|
30
|
-
rows.set(row.id, {
|
|
31
|
-
...row,
|
|
32
|
-
createdAt: new Date(),
|
|
33
|
-
updatedAt: new Date(),
|
|
34
|
-
completedAt: null,
|
|
35
|
-
});
|
|
36
|
-
return Promise.resolve();
|
|
37
|
-
}),
|
|
38
|
-
}),
|
|
39
|
-
select: vi.fn().mockReturnValue({
|
|
40
|
-
from: vi.fn().mockReturnValue({
|
|
41
|
-
where: vi.fn().mockReturnValue({
|
|
42
|
-
limit: vi.fn().mockImplementation(() => {
|
|
43
|
-
// Return the most recently queried row from the map
|
|
44
|
-
const allRows = Array.from(rows.values());
|
|
45
|
-
return Promise.resolve(allRows.length > 0 ? [allRows[allRows.length - 1]] : []);
|
|
46
|
-
}),
|
|
47
|
-
}),
|
|
48
|
-
}),
|
|
49
|
-
}),
|
|
50
|
-
update: vi.fn().mockReturnValue({
|
|
51
|
-
set: vi.fn().mockImplementation((values) => ({
|
|
52
|
-
where: vi.fn().mockImplementation(() => {
|
|
53
|
-
updates.push(values);
|
|
54
|
-
const allRows = Array.from(rows.values());
|
|
55
|
-
if (allRows.length > 0 && state.updateRowCount > 0) {
|
|
56
|
-
const last = allRows[allRows.length - 1];
|
|
57
|
-
Object.assign(last, values);
|
|
58
|
-
}
|
|
59
|
-
return Promise.resolve({ rowCount: state.updateRowCount });
|
|
60
|
-
}),
|
|
61
|
-
})),
|
|
62
|
-
}),
|
|
63
|
-
execute: vi.fn().mockImplementation(() => Promise.resolve(state.executeResult)),
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
function makeTestFlow() {
|
|
67
|
-
return defineFlow({
|
|
68
|
-
name: 'order-processing',
|
|
69
|
-
domain: 'orders',
|
|
70
|
-
input: z.object({ orderId: z.string() }),
|
|
71
|
-
steps: [
|
|
72
|
-
{ name: 'validate', type: FlowStepType.Capability },
|
|
73
|
-
{ name: 'process', type: FlowStepType.Capability },
|
|
74
|
-
{ name: 'notify', type: FlowStepType.Capability },
|
|
75
|
-
],
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
function makeAuth() {
|
|
79
|
-
return {
|
|
80
|
-
userId: 'user-1',
|
|
81
|
-
tenantId: 'tenant-1',
|
|
82
|
-
roles: ['admin'],
|
|
83
|
-
scopes: [],
|
|
84
|
-
provider: 'test',
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
function makeCtx(overrides) {
|
|
88
|
-
return {
|
|
89
|
-
auth: makeAuth(),
|
|
90
|
-
data: {},
|
|
91
|
-
events: {
|
|
92
|
-
emit: vi.fn().mockResolvedValue(undefined),
|
|
93
|
-
emitMany: vi.fn().mockResolvedValue(undefined),
|
|
94
|
-
},
|
|
95
|
-
flows: {
|
|
96
|
-
start: vi.fn(),
|
|
97
|
-
resume: vi.fn(),
|
|
98
|
-
cancel: vi.fn(),
|
|
99
|
-
status: vi.fn(),
|
|
100
|
-
},
|
|
101
|
-
ai: {},
|
|
102
|
-
audit: { record: vi.fn() },
|
|
103
|
-
errors: {},
|
|
104
|
-
logger: {
|
|
105
|
-
debug: vi.fn(),
|
|
106
|
-
info: vi.fn(),
|
|
107
|
-
warn: vi.fn(),
|
|
108
|
-
error: vi.fn(),
|
|
109
|
-
},
|
|
110
|
-
time: { now: () => new Date() },
|
|
111
|
-
config: {},
|
|
112
|
-
security: {},
|
|
113
|
-
translations: { locale: 'en', t: (key) => key },
|
|
114
|
-
...overrides,
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
function makeStepDeps(succeeds = true) {
|
|
118
|
-
return {
|
|
119
|
-
executeCapability: vi
|
|
120
|
-
.fn()
|
|
121
|
-
.mockResolvedValue(succeeds ? { success: true, data: {} } : { success: false, error: 'step failed' }),
|
|
122
|
-
evaluateCondition: vi.fn().mockReturnValue(true),
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
describe('FlowEngine', () => {
|
|
126
|
-
it('starts a flow and creates execution record', async () => {
|
|
127
|
-
const registry = new FlowRegistry();
|
|
128
|
-
registry.register(makeTestFlow());
|
|
129
|
-
const db = mockDb();
|
|
130
|
-
const engine = createFlowEngine({ db, registry, stepDeps: makeStepDeps() });
|
|
131
|
-
const exec = await engine.start('order-processing', { orderId: '123' }, makeAuth());
|
|
132
|
-
expect(exec.flowName).toBe('order-processing');
|
|
133
|
-
expect(exec.status).toBe(FlowStatus.Created);
|
|
134
|
-
expect(exec.id).toBeTruthy();
|
|
135
|
-
expect(db._inserts).toHaveLength(1);
|
|
136
|
-
expect(db._inserts[0].flowName).toBe('order-processing');
|
|
137
|
-
});
|
|
138
|
-
it('throws on unregistered flow', async () => {
|
|
139
|
-
const registry = new FlowRegistry();
|
|
140
|
-
const db = mockDb();
|
|
141
|
-
const engine = createFlowEngine({ db, registry, stepDeps: makeStepDeps() });
|
|
142
|
-
await expect(engine.start('nope', {}, makeAuth())).rejects.toThrow('not registered');
|
|
143
|
-
});
|
|
144
|
-
it('validates input against flow schema', async () => {
|
|
145
|
-
const registry = new FlowRegistry();
|
|
146
|
-
registry.register(makeTestFlow());
|
|
147
|
-
const db = mockDb();
|
|
148
|
-
const engine = createFlowEngine({ db, registry, stepDeps: makeStepDeps() });
|
|
149
|
-
await expect(engine.start('order-processing', { wrong: 123 }, makeAuth())).rejects.toThrow('invalid input');
|
|
150
|
-
});
|
|
151
|
-
it('gets flow execution status', async () => {
|
|
152
|
-
const registry = new FlowRegistry();
|
|
153
|
-
registry.register(makeTestFlow());
|
|
154
|
-
const db = mockDb();
|
|
155
|
-
const engine = createFlowEngine({ db, registry, stepDeps: makeStepDeps() });
|
|
156
|
-
const exec = await engine.start('order-processing', { orderId: 'abc' }, makeAuth());
|
|
157
|
-
// Mock the select to return the created row
|
|
158
|
-
const row = db._rows.get(exec.id);
|
|
159
|
-
db.select = vi.fn().mockReturnValue({
|
|
160
|
-
from: vi.fn().mockReturnValue({
|
|
161
|
-
where: vi.fn().mockReturnValue({
|
|
162
|
-
limit: vi.fn().mockResolvedValue([row]),
|
|
163
|
-
}),
|
|
164
|
-
}),
|
|
165
|
-
});
|
|
166
|
-
const st = await engine.status(exec.id);
|
|
167
|
-
expect(st.id).toBe(exec.id);
|
|
168
|
-
expect(st.flowName).toBe('order-processing');
|
|
169
|
-
});
|
|
170
|
-
it('cancels a created flow', async () => {
|
|
171
|
-
const registry = new FlowRegistry();
|
|
172
|
-
registry.register(makeTestFlow());
|
|
173
|
-
const db = mockDb();
|
|
174
|
-
const engine = createFlowEngine({ db, registry, stepDeps: makeStepDeps() });
|
|
175
|
-
const exec = await engine.start('order-processing', { orderId: 'abc' }, makeAuth());
|
|
176
|
-
const row = db._rows.get(exec.id);
|
|
177
|
-
db.select = vi.fn().mockReturnValue({
|
|
178
|
-
from: vi.fn().mockReturnValue({
|
|
179
|
-
where: vi.fn().mockReturnValue({
|
|
180
|
-
limit: vi.fn().mockResolvedValue([row]),
|
|
181
|
-
}),
|
|
182
|
-
}),
|
|
183
|
-
});
|
|
184
|
-
await engine.cancel(exec.id);
|
|
185
|
-
expect(db._updates.some((u) => u.status === FlowStatus.Cancelled)).toBe(true);
|
|
186
|
-
});
|
|
187
|
-
it('throws when cancelling a terminal flow', async () => {
|
|
188
|
-
const registry = new FlowRegistry();
|
|
189
|
-
registry.register(makeTestFlow());
|
|
190
|
-
const rows = new Map();
|
|
191
|
-
rows.set('x', {
|
|
192
|
-
id: 'x',
|
|
193
|
-
flowName: 'order-processing',
|
|
194
|
-
status: FlowStatus.Completed,
|
|
195
|
-
currentStep: null,
|
|
196
|
-
stepHistory: [],
|
|
197
|
-
state: null,
|
|
198
|
-
retryCount: 0,
|
|
199
|
-
});
|
|
200
|
-
const db = mockDb(rows);
|
|
201
|
-
db.select = vi.fn().mockReturnValue({
|
|
202
|
-
from: vi.fn().mockReturnValue({
|
|
203
|
-
where: vi.fn().mockReturnValue({
|
|
204
|
-
limit: vi.fn().mockResolvedValue([rows.get('x')]),
|
|
205
|
-
}),
|
|
206
|
-
}),
|
|
207
|
-
});
|
|
208
|
-
const engine = createFlowEngine({ db, registry, stepDeps: makeStepDeps() });
|
|
209
|
-
await expect(engine.cancel('x')).rejects.toThrow('terminal state');
|
|
210
|
-
});
|
|
211
|
-
it('records audit events when audit service provided', async () => {
|
|
212
|
-
const registry = new FlowRegistry();
|
|
213
|
-
registry.register(makeTestFlow());
|
|
214
|
-
const db = mockDb();
|
|
215
|
-
const auditRecords = [];
|
|
216
|
-
const audit = {
|
|
217
|
-
record: vi.fn().mockImplementation(async (action, meta) => {
|
|
218
|
-
auditRecords.push({ action, ...meta });
|
|
219
|
-
}),
|
|
220
|
-
};
|
|
221
|
-
const engine = createFlowEngine({ db, registry, stepDeps: makeStepDeps(), audit });
|
|
222
|
-
await engine.start('order-processing', { orderId: 'abc' }, makeAuth());
|
|
223
|
-
expect(auditRecords).toHaveLength(1);
|
|
224
|
-
expect(auditRecords[0].action).toBe('flow.started.order-processing');
|
|
225
|
-
});
|
|
226
|
-
it('merges capability output into persisted state between steps', async () => {
|
|
227
|
-
const registry = new FlowRegistry();
|
|
228
|
-
registry.register(defineFlow({
|
|
229
|
-
name: 'stateful-flow',
|
|
230
|
-
domain: 'orders',
|
|
231
|
-
input: z.object({ orderId: z.string() }),
|
|
232
|
-
state: z.object({ manuscriptId: z.string().default('') }),
|
|
233
|
-
steps: [
|
|
234
|
-
{ name: 'plan', type: FlowStepType.Capability },
|
|
235
|
-
{ name: 'write', type: FlowStepType.Capability },
|
|
236
|
-
],
|
|
237
|
-
}));
|
|
238
|
-
const db = mockDb();
|
|
239
|
-
const stepDeps = {
|
|
240
|
-
executeCapability: vi
|
|
241
|
-
.fn()
|
|
242
|
-
.mockResolvedValueOnce({ success: true, data: { manuscriptId: 'm-1' } })
|
|
243
|
-
.mockResolvedValueOnce({ success: true, data: {} }),
|
|
244
|
-
evaluateCondition: vi.fn().mockReturnValue(true),
|
|
245
|
-
};
|
|
246
|
-
const engine = createFlowEngine({ db, registry, stepDeps });
|
|
247
|
-
const exec = await engine.start('stateful-flow', { orderId: 'abc' }, makeAuth());
|
|
248
|
-
const row = db._rows.get(exec.id);
|
|
249
|
-
db.select = vi.fn().mockReturnValue({
|
|
250
|
-
from: vi.fn().mockReturnValue({
|
|
251
|
-
where: vi.fn().mockReturnValue({
|
|
252
|
-
limit: vi.fn().mockResolvedValue([row]),
|
|
253
|
-
}),
|
|
254
|
-
}),
|
|
255
|
-
});
|
|
256
|
-
await engine.runNext(exec.id, makeCtx());
|
|
257
|
-
expect(row.state).toMatchObject({ manuscriptId: 'm-1' });
|
|
258
|
-
expect(row.currentStep).toBe('write');
|
|
259
|
-
});
|
|
260
|
-
it('passes the live DB connection to onFlowError hooks', async () => {
|
|
261
|
-
const registry = new FlowRegistry();
|
|
262
|
-
registry.register(makeTestFlow());
|
|
263
|
-
const db = mockDb();
|
|
264
|
-
const onFlowError = vi.fn();
|
|
265
|
-
const engine = createFlowEngine({ db, registry, stepDeps: makeStepDeps(false), onFlowError });
|
|
266
|
-
const exec = await engine.start('order-processing', { orderId: 'abc' }, makeAuth());
|
|
267
|
-
const row = db._rows.get(exec.id);
|
|
268
|
-
db.select = vi.fn().mockReturnValue({
|
|
269
|
-
from: vi.fn().mockReturnValue({
|
|
270
|
-
where: vi.fn().mockReturnValue({
|
|
271
|
-
limit: vi.fn().mockResolvedValue([row]),
|
|
272
|
-
}),
|
|
273
|
-
}),
|
|
274
|
-
});
|
|
275
|
-
await engine.runNext(exec.id, makeCtx());
|
|
276
|
-
expect(onFlowError).toHaveBeenCalledWith(expect.objectContaining({
|
|
277
|
-
db,
|
|
278
|
-
executionId: exec.id,
|
|
279
|
-
flowName: 'order-processing',
|
|
280
|
-
step: 'validate',
|
|
281
|
-
error: 'step failed',
|
|
282
|
-
}));
|
|
283
|
-
});
|
|
284
|
-
});
|
|
285
|
-
describe('FlowEngine — lease claiming', () => {
|
|
286
|
-
it('only one worker claims a row under a SKIP LOCKED race', async () => {
|
|
287
|
-
const registry = new FlowRegistry();
|
|
288
|
-
registry.register(makeTestFlow());
|
|
289
|
-
const claimedRow = {
|
|
290
|
-
id: 'race-1',
|
|
291
|
-
flowName: 'order-processing',
|
|
292
|
-
status: FlowStatus.Running,
|
|
293
|
-
currentStep: 'validate',
|
|
294
|
-
leaseOwner: null,
|
|
295
|
-
};
|
|
296
|
-
const dbA = mockDb();
|
|
297
|
-
dbA._state.executeResult = [claimedRow];
|
|
298
|
-
const dbB = mockDb();
|
|
299
|
-
dbB._state.executeResult = [];
|
|
300
|
-
const engineA = createFlowEngine({
|
|
301
|
-
db: dbA,
|
|
302
|
-
registry,
|
|
303
|
-
stepDeps: makeStepDeps(),
|
|
304
|
-
workerId: 'worker-a',
|
|
305
|
-
});
|
|
306
|
-
const engineB = createFlowEngine({
|
|
307
|
-
db: dbB,
|
|
308
|
-
registry,
|
|
309
|
-
stepDeps: makeStepDeps(),
|
|
310
|
-
workerId: 'worker-b',
|
|
311
|
-
});
|
|
312
|
-
const [claimedA, claimedB] = await Promise.all([engineA.claimNext(), engineB.claimNext()]);
|
|
313
|
-
expect(claimedA).toHaveLength(1);
|
|
314
|
-
expect(claimedA[0]?.id).toBe('race-1');
|
|
315
|
-
expect(claimedB).toHaveLength(0);
|
|
316
|
-
});
|
|
317
|
-
it("reclaims a crashed worker's expired lease and audits the reclaim", async () => {
|
|
318
|
-
const registry = new FlowRegistry();
|
|
319
|
-
registry.register(makeTestFlow());
|
|
320
|
-
const db = mockDb();
|
|
321
|
-
db._state.executeResult = [
|
|
322
|
-
{
|
|
323
|
-
id: 'exec-crash',
|
|
324
|
-
flowName: 'order-processing',
|
|
325
|
-
status: FlowStatus.Running,
|
|
326
|
-
currentStep: 'validate',
|
|
327
|
-
leaseOwner: 'crashed-worker',
|
|
328
|
-
leaseExpiresAt: new Date(Date.now() - 60_000),
|
|
329
|
-
},
|
|
330
|
-
];
|
|
331
|
-
const audits = [];
|
|
332
|
-
const audit = {
|
|
333
|
-
record: vi.fn().mockImplementation(async (action, meta) => {
|
|
334
|
-
audits.push({ action, ...meta });
|
|
335
|
-
}),
|
|
336
|
-
};
|
|
337
|
-
const engine = createFlowEngine({
|
|
338
|
-
db,
|
|
339
|
-
registry,
|
|
340
|
-
stepDeps: makeStepDeps(),
|
|
341
|
-
workerId: 'worker-fresh',
|
|
342
|
-
audit,
|
|
343
|
-
});
|
|
344
|
-
const claimed = await engine.claimNext();
|
|
345
|
-
expect(claimed).toHaveLength(1);
|
|
346
|
-
expect(claimed[0]?.id).toBe('exec-crash');
|
|
347
|
-
const expired = audits.find((a) => a.action === 'flow.lease.expired');
|
|
348
|
-
expect(expired).toBeDefined();
|
|
349
|
-
expect(expired.previousOwner).toBe('crashed-worker');
|
|
350
|
-
expect(expired.workerId).toBe('worker-fresh');
|
|
351
|
-
expect(audits.some((a) => a.action === 'flow.lease.claimed')).toBe(true);
|
|
352
|
-
});
|
|
353
|
-
it('throws LeaseLostError when heartbeat detects the lease was stolen mid-step', async () => {
|
|
354
|
-
const registry = new FlowRegistry();
|
|
355
|
-
registry.register(makeTestFlow());
|
|
356
|
-
const db = mockDb();
|
|
357
|
-
db._state.updateRowCount = 0; // every update says "0 rows affected" → heartbeat loses lease
|
|
358
|
-
const stepDeps = {
|
|
359
|
-
executeCapability: vi.fn().mockImplementation(async () => {
|
|
360
|
-
// Give the heartbeat interval at least one tick to fire during the step
|
|
361
|
-
await new Promise((r) => setTimeout(r, 40));
|
|
362
|
-
return { success: true, data: {} };
|
|
363
|
-
}),
|
|
364
|
-
evaluateCondition: vi.fn().mockReturnValue(true),
|
|
365
|
-
};
|
|
366
|
-
const engine = createFlowEngine({
|
|
367
|
-
db,
|
|
368
|
-
registry,
|
|
369
|
-
stepDeps,
|
|
370
|
-
workerId: 'worker-x',
|
|
371
|
-
flowHeartbeatIntervalMs: 5,
|
|
372
|
-
flowLeaseDurationMs: 60_000,
|
|
373
|
-
});
|
|
374
|
-
const exec = await engine.start('order-processing', { orderId: 'abc' }, makeAuth());
|
|
375
|
-
await expect(engine.runNext(exec.id, makeCtx())).rejects.toBeInstanceOf(LeaseLostError);
|
|
376
|
-
});
|
|
377
|
-
it('skips completion audit when guardedUpdate reports the lease was lost', async () => {
|
|
378
|
-
const registry = new FlowRegistry();
|
|
379
|
-
registry.register(defineFlow({
|
|
380
|
-
name: 'single-step',
|
|
381
|
-
domain: 'test',
|
|
382
|
-
input: z.object({}),
|
|
383
|
-
steps: [{ name: 'only', type: FlowStepType.Capability }],
|
|
384
|
-
}));
|
|
385
|
-
const db = mockDb();
|
|
386
|
-
db._state.updateRowCount = 0; // all guardedUpdates return false → completeFlow aborts before audit
|
|
387
|
-
const audits = [];
|
|
388
|
-
const audit = {
|
|
389
|
-
record: vi.fn().mockImplementation(async (action, meta) => {
|
|
390
|
-
audits.push({ action, ...meta });
|
|
391
|
-
}),
|
|
392
|
-
};
|
|
393
|
-
const engine = createFlowEngine({
|
|
394
|
-
db,
|
|
395
|
-
registry,
|
|
396
|
-
stepDeps: makeStepDeps(),
|
|
397
|
-
workerId: 'worker-x',
|
|
398
|
-
audit,
|
|
399
|
-
flowHeartbeatIntervalMs: 60_000, // keep the heartbeat timer idle during the fast test
|
|
400
|
-
});
|
|
401
|
-
const exec = await engine.start('single-step', {}, makeAuth());
|
|
402
|
-
await engine.runNext(exec.id, makeCtx());
|
|
403
|
-
expect(audits.some((a) => a.action === 'flow.completed.single-step')).toBe(false);
|
|
404
|
-
});
|
|
405
|
-
it('20 executions across 4 engines — each claimed exactly once', async () => {
|
|
406
|
-
const registry = new FlowRegistry();
|
|
407
|
-
registry.register(makeTestFlow());
|
|
408
|
-
const available = new Set(Array.from({ length: 20 }, (_, i) => `exec-${i}`));
|
|
409
|
-
const engines = ['w1', 'w2', 'w3', 'w4'].map((workerId) => {
|
|
410
|
-
const db = mockDb();
|
|
411
|
-
// Simulate FOR UPDATE SKIP LOCKED: each execute call atomically takes up to 5 rows
|
|
412
|
-
// from the shared `available` set (synchronous mutation — JS single-threaded).
|
|
413
|
-
db.execute = vi.fn().mockImplementation(async () => {
|
|
414
|
-
const taken = [];
|
|
415
|
-
for (const id of Array.from(available)) {
|
|
416
|
-
if (taken.length >= 5)
|
|
417
|
-
break;
|
|
418
|
-
available.delete(id);
|
|
419
|
-
taken.push({
|
|
420
|
-
id,
|
|
421
|
-
flowName: 'order-processing',
|
|
422
|
-
status: FlowStatus.Running,
|
|
423
|
-
currentStep: 'validate',
|
|
424
|
-
leaseOwner: workerId,
|
|
425
|
-
});
|
|
426
|
-
}
|
|
427
|
-
return taken;
|
|
428
|
-
});
|
|
429
|
-
return createFlowEngine({
|
|
430
|
-
db,
|
|
431
|
-
registry,
|
|
432
|
-
stepDeps: makeStepDeps(),
|
|
433
|
-
workerId,
|
|
434
|
-
});
|
|
435
|
-
});
|
|
436
|
-
const results = await Promise.all(engines.map((e) => e.claimNext(5)));
|
|
437
|
-
const claimedIds = results.flatMap((batch) => batch.map((row) => row.id));
|
|
438
|
-
expect(claimedIds).toHaveLength(20);
|
|
439
|
-
expect(new Set(claimedIds).size).toBe(20); // every ID is unique
|
|
440
|
-
expect(available.size).toBe(0);
|
|
441
|
-
});
|
|
442
|
-
it('ctx.flows.heartbeat() extends the lease and throws LeaseLostError when lost', async () => {
|
|
443
|
-
const registry = new FlowRegistry();
|
|
444
|
-
registry.register(makeTestFlow());
|
|
445
|
-
const db = mockDb();
|
|
446
|
-
db._state.updateRowCount = 1;
|
|
447
|
-
let firstHeartbeatOk = false;
|
|
448
|
-
let heartbeatError;
|
|
449
|
-
const stepDeps = {
|
|
450
|
-
executeCapability: vi.fn().mockImplementation(async (_name, stepCtx) => {
|
|
451
|
-
await stepCtx.flows.heartbeat();
|
|
452
|
-
firstHeartbeatOk = true;
|
|
453
|
-
db._state.updateRowCount = 0; // simulate another worker stealing the lease
|
|
454
|
-
try {
|
|
455
|
-
await stepCtx.flows.heartbeat();
|
|
456
|
-
}
|
|
457
|
-
catch (err) {
|
|
458
|
-
heartbeatError = err;
|
|
459
|
-
}
|
|
460
|
-
return { success: true, data: {} };
|
|
461
|
-
}),
|
|
462
|
-
evaluateCondition: vi.fn().mockReturnValue(true),
|
|
463
|
-
};
|
|
464
|
-
const engine = createFlowEngine({
|
|
465
|
-
db,
|
|
466
|
-
registry,
|
|
467
|
-
stepDeps,
|
|
468
|
-
workerId: 'worker-x',
|
|
469
|
-
flowHeartbeatIntervalMs: 60_000, // disable auto-heartbeat; manual heartbeat only
|
|
470
|
-
});
|
|
471
|
-
const exec = await engine.start('order-processing', { orderId: 'abc' }, makeAuth());
|
|
472
|
-
await engine.runNext(exec.id, makeCtx());
|
|
473
|
-
expect(firstHeartbeatOk).toBe(true);
|
|
474
|
-
expect(heartbeatError).toBeInstanceOf(LeaseLostError);
|
|
475
|
-
});
|
|
476
|
-
});
|
|
477
|
-
describe('FlowEngine — cooperative cancellation (ctx.signal)', () => {
|
|
478
|
-
it('exposes an un-aborted ctx.signal to step handlers', async () => {
|
|
479
|
-
const registry = new FlowRegistry();
|
|
480
|
-
registry.register(makeTestFlow());
|
|
481
|
-
const db = mockDb();
|
|
482
|
-
let capturedSignal;
|
|
483
|
-
const stepDeps = {
|
|
484
|
-
executeCapability: vi.fn().mockImplementation(async (_name, stepCtx) => {
|
|
485
|
-
capturedSignal = stepCtx.signal;
|
|
486
|
-
return { success: true, data: {} };
|
|
487
|
-
}),
|
|
488
|
-
evaluateCondition: vi.fn().mockReturnValue(true),
|
|
489
|
-
};
|
|
490
|
-
const engine = createFlowEngine({
|
|
491
|
-
db,
|
|
492
|
-
registry,
|
|
493
|
-
stepDeps,
|
|
494
|
-
workerId: 'worker-sig-1',
|
|
495
|
-
flowHeartbeatIntervalMs: 60_000,
|
|
496
|
-
});
|
|
497
|
-
const exec = await engine.start('order-processing', { orderId: 'abc' }, makeAuth());
|
|
498
|
-
await engine.runNext(exec.id, makeCtx());
|
|
499
|
-
expect(capturedSignal).toBeInstanceOf(AbortSignal);
|
|
500
|
-
// Step completed normally; signal.aborted before return should be false.
|
|
501
|
-
// (After runNext finishes the finally-block aborts it to wake detached tasks,
|
|
502
|
-
// but the assertion is about the state the handler observed during execution.)
|
|
503
|
-
});
|
|
504
|
-
it('cancel() aborts ctx.signal for the in-flight step in the same process', async () => {
|
|
505
|
-
const registry = new FlowRegistry();
|
|
506
|
-
registry.register(makeTestFlow());
|
|
507
|
-
const db = mockDb();
|
|
508
|
-
db._state.updateRowCount = 1;
|
|
509
|
-
let stepSignalAborted = false;
|
|
510
|
-
let stepSignalReason;
|
|
511
|
-
const stepDeps = {
|
|
512
|
-
executeCapability: vi.fn().mockImplementation(async (_name, stepCtx) => {
|
|
513
|
-
// Wait for the signal to abort — simulates a long-running AI call.
|
|
514
|
-
await new Promise((resolve) => {
|
|
515
|
-
if (stepCtx.signal.aborted) {
|
|
516
|
-
stepSignalAborted = true;
|
|
517
|
-
stepSignalReason = stepCtx.signal.reason;
|
|
518
|
-
resolve();
|
|
519
|
-
return;
|
|
520
|
-
}
|
|
521
|
-
stepCtx.signal.addEventListener('abort', () => {
|
|
522
|
-
stepSignalAborted = true;
|
|
523
|
-
stepSignalReason = stepCtx.signal.reason;
|
|
524
|
-
resolve();
|
|
525
|
-
}, { once: true });
|
|
526
|
-
});
|
|
527
|
-
return { success: true, data: {} };
|
|
528
|
-
}),
|
|
529
|
-
evaluateCondition: vi.fn().mockReturnValue(true),
|
|
530
|
-
};
|
|
531
|
-
const engine = createFlowEngine({
|
|
532
|
-
db,
|
|
533
|
-
registry,
|
|
534
|
-
stepDeps,
|
|
535
|
-
workerId: 'worker-cancel-1',
|
|
536
|
-
flowHeartbeatIntervalMs: 60_000,
|
|
537
|
-
});
|
|
538
|
-
const exec = await engine.start('order-processing', { orderId: 'x' }, makeAuth());
|
|
539
|
-
// Kick off runNext, then cancel while the step is mid-flight.
|
|
540
|
-
const running = engine.runNext(exec.id, makeCtx());
|
|
541
|
-
await new Promise((r) => setTimeout(r, 20));
|
|
542
|
-
await engine.cancel(exec.id);
|
|
543
|
-
await running;
|
|
544
|
-
expect(stepSignalAborted).toBe(true);
|
|
545
|
-
// reason is a FlowCancelledError
|
|
546
|
-
expect(stepSignalReason?.name).toBe('FlowCancelledError');
|
|
547
|
-
});
|
|
548
|
-
it('heartbeat-driven lease loss aborts ctx.signal with LeaseLostError', async () => {
|
|
549
|
-
const registry = new FlowRegistry();
|
|
550
|
-
registry.register(makeTestFlow());
|
|
551
|
-
const db = mockDb();
|
|
552
|
-
db._state.updateRowCount = 0; // every lease-extend reports 0 rows affected
|
|
553
|
-
let abortReason;
|
|
554
|
-
const stepDeps = {
|
|
555
|
-
executeCapability: vi.fn().mockImplementation(async (_name, stepCtx) => {
|
|
556
|
-
await new Promise((resolve) => {
|
|
557
|
-
stepCtx.signal.addEventListener('abort', () => {
|
|
558
|
-
abortReason = stepCtx.signal.reason;
|
|
559
|
-
resolve();
|
|
560
|
-
}, { once: true });
|
|
561
|
-
});
|
|
562
|
-
return { success: true, data: {} };
|
|
563
|
-
}),
|
|
564
|
-
evaluateCondition: vi.fn().mockReturnValue(true),
|
|
565
|
-
};
|
|
566
|
-
const engine = createFlowEngine({
|
|
567
|
-
db,
|
|
568
|
-
registry,
|
|
569
|
-
stepDeps,
|
|
570
|
-
workerId: 'worker-lease-1',
|
|
571
|
-
flowHeartbeatIntervalMs: 5,
|
|
572
|
-
flowLeaseDurationMs: 60_000,
|
|
573
|
-
});
|
|
574
|
-
const exec = await engine.start('order-processing', { orderId: 'x' }, makeAuth());
|
|
575
|
-
await expect(engine.runNext(exec.id, makeCtx())).rejects.toBeInstanceOf(LeaseLostError);
|
|
576
|
-
expect(abortReason?.name).toBe('LeaseLostError');
|
|
577
|
-
});
|
|
578
|
-
it('threads ctx.signal into ctx.ai.generate by default', async () => {
|
|
579
|
-
const registry = new FlowRegistry();
|
|
580
|
-
registry.register(makeTestFlow());
|
|
581
|
-
const db = mockDb();
|
|
582
|
-
const generate = vi.fn().mockResolvedValue({ ok: true });
|
|
583
|
-
const ctxWithAi = makeCtx({
|
|
584
|
-
ai: {
|
|
585
|
-
generate,
|
|
586
|
-
generateWithUsage: vi.fn(),
|
|
587
|
-
streamGenerate: vi.fn(),
|
|
588
|
-
extract: vi.fn(),
|
|
589
|
-
classify: vi.fn(),
|
|
590
|
-
retrieve: vi.fn(),
|
|
591
|
-
},
|
|
592
|
-
});
|
|
593
|
-
const stepDeps = {
|
|
594
|
-
executeCapability: vi.fn().mockImplementation(async (_name, stepCtx) => {
|
|
595
|
-
// Capability does NOT pass a signal — engine must default to ctx.signal.
|
|
596
|
-
await stepCtx.ai.generate({ prompt: 'p', input: {} });
|
|
597
|
-
return { success: true, data: {} };
|
|
598
|
-
}),
|
|
599
|
-
evaluateCondition: vi.fn().mockReturnValue(true),
|
|
600
|
-
};
|
|
601
|
-
const engine = createFlowEngine({
|
|
602
|
-
db,
|
|
603
|
-
registry,
|
|
604
|
-
stepDeps,
|
|
605
|
-
workerId: 'worker-ai-1',
|
|
606
|
-
flowHeartbeatIntervalMs: 60_000,
|
|
607
|
-
});
|
|
608
|
-
const exec = await engine.start('order-processing', { orderId: 'x' }, makeAuth());
|
|
609
|
-
await engine.runNext(exec.id, ctxWithAi);
|
|
610
|
-
expect(generate).toHaveBeenCalledTimes(1);
|
|
611
|
-
const firstCall = generate.mock.calls[0];
|
|
612
|
-
expect(firstCall).toBeDefined();
|
|
613
|
-
const calledWith = firstCall?.[0];
|
|
614
|
-
expect(calledWith.signal).toBeInstanceOf(AbortSignal);
|
|
615
|
-
});
|
|
616
|
-
describe('markFailedFromRunner (zombie recovery)', () => {
|
|
617
|
-
it('finalizes the row as failed with lease cleared after LeaseLostError from runNext', async () => {
|
|
618
|
-
const registry = new FlowRegistry();
|
|
619
|
-
registry.register(makeTestFlow());
|
|
620
|
-
const db = mockDb();
|
|
621
|
-
db._state.updateRowCount = 0; // every update says 0 rows → heartbeat loses lease
|
|
622
|
-
const stepDeps = {
|
|
623
|
-
executeCapability: vi.fn().mockImplementation(async () => {
|
|
624
|
-
await new Promise((r) => setTimeout(r, 40));
|
|
625
|
-
return { success: true, data: {} };
|
|
626
|
-
}),
|
|
627
|
-
evaluateCondition: vi.fn().mockReturnValue(true),
|
|
628
|
-
};
|
|
629
|
-
const engine = createFlowEngine({
|
|
630
|
-
db,
|
|
631
|
-
registry,
|
|
632
|
-
stepDeps,
|
|
633
|
-
workerId: 'worker-recover-1',
|
|
634
|
-
flowHeartbeatIntervalMs: 5,
|
|
635
|
-
flowLeaseDurationMs: 60_000,
|
|
636
|
-
});
|
|
637
|
-
const exec = await engine.start('order-processing', { orderId: 'abc' }, makeAuth());
|
|
638
|
-
let caught;
|
|
639
|
-
try {
|
|
640
|
-
await engine.runNext(exec.id, makeCtx());
|
|
641
|
-
}
|
|
642
|
-
catch (err) {
|
|
643
|
-
caught = err;
|
|
644
|
-
}
|
|
645
|
-
expect(caught).toBeInstanceOf(LeaseLostError);
|
|
646
|
-
// Now simulate the bootstrap runner finalizing. Flip the mock back to
|
|
647
|
-
// "updates succeed" so the finalizer's UPDATE reports 1 row affected.
|
|
648
|
-
db._updates.length = 0;
|
|
649
|
-
db._state.updateRowCount = 1;
|
|
650
|
-
const finalized = await engine.markFailedFromRunner(exec.id, caught);
|
|
651
|
-
expect(finalized).toBe(true);
|
|
652
|
-
const finalize = db._updates.at(-1);
|
|
653
|
-
expect(finalize).toMatchObject({
|
|
654
|
-
status: FlowStatus.Failed,
|
|
655
|
-
leaseOwner: null,
|
|
656
|
-
leaseExpiresAt: null,
|
|
657
|
-
});
|
|
658
|
-
expect(finalize.lastError).toMatch(/Lease lost/);
|
|
659
|
-
expect(finalize.completedAt).toBeInstanceOf(Date);
|
|
660
|
-
});
|
|
661
|
-
it('is a no-op when the row is already in a terminal state', async () => {
|
|
662
|
-
const registry = new FlowRegistry();
|
|
663
|
-
registry.register(makeTestFlow());
|
|
664
|
-
const db = mockDb();
|
|
665
|
-
const engine = createFlowEngine({
|
|
666
|
-
db,
|
|
667
|
-
registry,
|
|
668
|
-
stepDeps: makeStepDeps(),
|
|
669
|
-
workerId: 'worker-recover-2',
|
|
670
|
-
});
|
|
671
|
-
const exec = await engine.start('order-processing', { orderId: 'x' }, makeAuth());
|
|
672
|
-
// Simulate "row already finalized" — UPDATE with status-set guard matches 0 rows.
|
|
673
|
-
db._state.updateRowCount = 0;
|
|
674
|
-
db._updates.length = 0;
|
|
675
|
-
const finalized = await engine.markFailedFromRunner(exec.id, new Error('stale'));
|
|
676
|
-
expect(finalized).toBe(false);
|
|
677
|
-
// The finalizer did attempt the UPDATE, but the status guard is what
|
|
678
|
-
// makes it a no-op against terminal rows — we only assert the return
|
|
679
|
-
// value here since the mock filters by updateRowCount, not SQL.
|
|
680
|
-
});
|
|
681
|
-
it('does not emit flow.runner.finalized audit when the row was already terminal', async () => {
|
|
682
|
-
const registry = new FlowRegistry();
|
|
683
|
-
registry.register(makeTestFlow());
|
|
684
|
-
const db = mockDb();
|
|
685
|
-
const audits = [];
|
|
686
|
-
const audit = {
|
|
687
|
-
record: vi.fn().mockImplementation(async (action) => {
|
|
688
|
-
audits.push({ action });
|
|
689
|
-
}),
|
|
690
|
-
};
|
|
691
|
-
const engine = createFlowEngine({
|
|
692
|
-
db,
|
|
693
|
-
registry,
|
|
694
|
-
stepDeps: makeStepDeps(),
|
|
695
|
-
workerId: 'worker-recover-3',
|
|
696
|
-
audit,
|
|
697
|
-
});
|
|
698
|
-
const exec = await engine.start('order-processing', { orderId: 'x' }, makeAuth());
|
|
699
|
-
db._state.updateRowCount = 0;
|
|
700
|
-
await engine.markFailedFromRunner(exec.id, new Error('stale'));
|
|
701
|
-
expect(audits.some((a) => a.action === 'flow.runner.finalized')).toBe(false);
|
|
702
|
-
});
|
|
703
|
-
it('uses "Unknown error" when the thrown value is neither Error nor string', async () => {
|
|
704
|
-
const registry = new FlowRegistry();
|
|
705
|
-
registry.register(makeTestFlow());
|
|
706
|
-
const db = mockDb();
|
|
707
|
-
const engine = createFlowEngine({
|
|
708
|
-
db,
|
|
709
|
-
registry,
|
|
710
|
-
stepDeps: makeStepDeps(),
|
|
711
|
-
workerId: 'worker-recover-4',
|
|
712
|
-
});
|
|
713
|
-
const exec = await engine.start('order-processing', { orderId: 'x' }, makeAuth());
|
|
714
|
-
db._updates.length = 0;
|
|
715
|
-
await engine.markFailedFromRunner(exec.id, { weird: true });
|
|
716
|
-
const finalize = db._updates.at(-1);
|
|
717
|
-
expect(finalize.lastError).toBe('Unknown error');
|
|
718
|
-
});
|
|
719
|
-
});
|
|
720
|
-
});
|
|
721
|
-
// Regression: in production, a flow worker's baseCtx is bound to `systemAuth`
|
|
722
|
-
// (userId: 'system-flow-runner', no tenantId). If `ctx.flows.start(...)` from
|
|
723
|
-
// inside a flow step inherits that auth, the nested flow row stores
|
|
724
|
-
// `tenantId: null`, and any tenantScoped capability inside the nested flow
|
|
725
|
-
// rejects with "Tenant context required". The engine must rebind `start` in
|
|
726
|
-
// the flow-step ctx so nested flows inherit the calling flow's auth instead.
|
|
727
|
-
describe('FlowEngine — flow-step ctx.flows.start propagates flowAuth', () => {
|
|
728
|
-
it('uses the executing flow row auth, not the base worker auth, when starting nested flows', async () => {
|
|
729
|
-
const registry = new FlowRegistry();
|
|
730
|
-
registry.register(defineFlow({
|
|
731
|
-
name: 'outer',
|
|
732
|
-
domain: 'test',
|
|
733
|
-
input: z.object({}),
|
|
734
|
-
steps: [{ name: 'kickoff', type: FlowStepType.Capability }],
|
|
735
|
-
}));
|
|
736
|
-
registry.register(defineFlow({
|
|
737
|
-
name: 'inner',
|
|
738
|
-
domain: 'test',
|
|
739
|
-
input: z.object({ foo: z.string() }),
|
|
740
|
-
steps: [{ name: 'inner-step', type: FlowStepType.Capability }],
|
|
741
|
-
}));
|
|
742
|
-
// Outer step captures ctx and calls ctx.flows.start('inner', ...). We
|
|
743
|
-
// assert what auth reaches the recursive engine.start call.
|
|
744
|
-
let capturedCtx = null;
|
|
745
|
-
const stepDeps = {
|
|
746
|
-
executeCapability: vi.fn().mockImplementation(async (_name, ctx) => {
|
|
747
|
-
capturedCtx = ctx;
|
|
748
|
-
await ctx.flows.start('inner', { foo: 'bar' });
|
|
749
|
-
return { success: true, data: {} };
|
|
750
|
-
}),
|
|
751
|
-
evaluateCondition: vi.fn().mockReturnValue(true),
|
|
752
|
-
};
|
|
753
|
-
const db = mockDb();
|
|
754
|
-
const engine = createFlowEngine({ db, registry, stepDeps });
|
|
755
|
-
// Caller (the human/HTTP) is tenant t-A.
|
|
756
|
-
const callerAuth = {
|
|
757
|
-
userId: 'real-user',
|
|
758
|
-
tenantId: 't-A',
|
|
759
|
-
roles: ['user'],
|
|
760
|
-
scopes: [],
|
|
761
|
-
provider: 'http',
|
|
762
|
-
};
|
|
763
|
-
const exec = await engine.start('outer', {}, callerAuth);
|
|
764
|
-
// The worker's baseCtx — `systemAuth`, NO tenantId. This is the exact
|
|
765
|
-
// shape the worker's bootstrap creates and the regression we're guarding.
|
|
766
|
-
const workerBaseCtx = makeCtx({
|
|
767
|
-
auth: {
|
|
768
|
-
userId: 'system-flow-runner',
|
|
769
|
-
roles: ['system'],
|
|
770
|
-
scopes: [],
|
|
771
|
-
provider: 'worker',
|
|
772
|
-
},
|
|
773
|
-
});
|
|
774
|
-
const row = db._rows.get(exec.id);
|
|
775
|
-
db.select = vi.fn().mockReturnValue({
|
|
776
|
-
from: vi.fn().mockReturnValue({
|
|
777
|
-
where: vi.fn().mockReturnValue({
|
|
778
|
-
limit: vi.fn().mockResolvedValue([row]),
|
|
779
|
-
}),
|
|
780
|
-
}),
|
|
781
|
-
});
|
|
782
|
-
await engine.runNext(exec.id, workerBaseCtx);
|
|
783
|
-
// Two inserts: outer (from engine.start above), inner (from ctx.flows.start
|
|
784
|
-
// inside the step). The inner row must inherit the OUTER flow's tenantId
|
|
785
|
-
// and actor, not the worker's systemAuth.
|
|
786
|
-
expect(db._inserts).toHaveLength(2);
|
|
787
|
-
const innerRow = db._inserts.find((r) => r.flowName === 'inner');
|
|
788
|
-
expect(innerRow).toBeDefined();
|
|
789
|
-
expect(innerRow.tenantId).toBe('t-A');
|
|
790
|
-
expect(innerRow.actor).toBe('real-user');
|
|
791
|
-
// The captured ctx given to the step also has flowAuth (sanity).
|
|
792
|
-
expect(capturedCtx?.auth?.tenantId).toBe('t-A');
|
|
793
|
-
expect(capturedCtx?.auth?.userId).toBe('real-user');
|
|
794
|
-
expect(capturedCtx?.auth?.internal).toBe(true);
|
|
795
|
-
});
|
|
796
|
-
});
|
|
797
|
-
//# sourceMappingURL=flow-engine.test.js.map
|