takt 0.48.0 → 0.50.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/README.md +15 -0
- package/builtins/en/config.yaml +6 -0
- package/builtins/en/facets/instructions/ai-antipattern-review.md +5 -0
- package/builtins/en/facets/instructions/exec-loop-monitor.md +15 -0
- package/builtins/en/facets/instructions/exec-replan.md +5 -0
- package/builtins/en/facets/instructions/exec-review.md +10 -0
- package/builtins/en/facets/instructions/exec-worker.md +5 -0
- package/builtins/en/facets/instructions/findings-manager.md +6 -2
- package/builtins/en/facets/instructions/loop-monitor-fix-replan.md +21 -0
- package/builtins/en/facets/instructions/loop-monitor-replan.md +10 -0
- package/builtins/en/facets/instructions/plan-maintenance.md +10 -7
- package/builtins/en/facets/instructions/plan.md +17 -8
- package/builtins/en/facets/instructions/review-arch.md +5 -0
- package/builtins/en/facets/instructions/review-coding.md +15 -6
- package/builtins/en/facets/instructions/review-cqrs-es.md +9 -0
- package/builtins/en/facets/instructions/review-implementation-semantics.md +18 -0
- package/builtins/en/facets/instructions/review-merge-readiness.md +14 -0
- package/builtins/en/facets/instructions/supervise-maintenance.md +2 -2
- package/builtins/en/facets/instructions/supervise.md +8 -3
- package/builtins/en/facets/instructions/write-tests-first.md +23 -3
- package/builtins/en/facets/knowledge/architecture.md +25 -0
- package/builtins/en/facets/knowledge/cqrs-es.md +718 -235
- package/builtins/en/facets/knowledge/implementation-semantics.md +104 -0
- package/builtins/en/facets/knowledge/takt.md +26 -2
- package/builtins/en/facets/knowledge/unit-testing.md +14 -0
- package/builtins/en/facets/output-contracts/ai-antipattern-review.md +11 -1
- package/builtins/en/facets/output-contracts/architecture-review.md +6 -1
- package/builtins/en/facets/output-contracts/coding-review.md +11 -1
- package/builtins/en/facets/output-contracts/exec-review-result.md +13 -0
- package/builtins/en/facets/output-contracts/findings-manager.md +14 -0
- package/builtins/en/facets/output-contracts/implementation-semantics-review-finding-contract.md +28 -0
- package/builtins/en/facets/output-contracts/implementation-semantics-review.md +50 -0
- package/builtins/en/facets/output-contracts/{pure-review-finding-contract.md → merge-readiness-review-finding-contract.md} +14 -8
- package/builtins/en/facets/output-contracts/{pure-review.md → merge-readiness-review.md} +17 -11
- package/builtins/en/facets/output-contracts/supervisor-validation.md +6 -2
- package/builtins/en/facets/output-contracts/test-report.md +25 -0
- package/builtins/en/facets/personas/exec-assistant.md +3 -0
- package/builtins/en/facets/personas/exec-worker.md +3 -0
- package/builtins/en/facets/personas/implementation-semantics-reviewer.md +27 -0
- package/builtins/en/facets/personas/merge-readiness-reviewer.md +28 -0
- package/builtins/en/facets/personas/supervisor.md +1 -1
- package/builtins/en/facets/policies/ai-antipattern.md +18 -0
- package/builtins/en/facets/policies/existing-system-respect.md +13 -0
- package/builtins/en/facets/policies/review.md +17 -0
- package/builtins/en/facets/policies/testing.md +14 -0
- package/builtins/en/workflow-categories.yaml +13 -1
- package/builtins/en/workflows/audit-architecture-backend.yaml +1 -0
- package/builtins/en/workflows/audit-architecture-dual.yaml +1 -0
- package/builtins/en/workflows/audit-architecture-frontend.yaml +1 -0
- package/builtins/en/workflows/audit-architecture.yaml +1 -0
- package/builtins/en/workflows/audit-e2e.yaml +1 -0
- package/builtins/en/workflows/audit-security.yaml +1 -0
- package/builtins/en/workflows/audit-unit.yaml +1 -0
- package/builtins/en/workflows/backend-cqrs-for-local-llm.yaml +288 -0
- package/builtins/en/workflows/backend-cqrs-mini.yaml +1 -0
- package/builtins/en/workflows/backend-cqrs.yaml +28 -84
- package/builtins/en/workflows/backend-for-local-llm.yaml +284 -0
- package/builtins/en/workflows/backend-maintenance.yaml +32 -89
- package/builtins/en/workflows/backend-mini.yaml +1 -0
- package/builtins/en/workflows/backend.yaml +25 -83
- package/builtins/en/workflows/{takt-default-with-fc.yaml → cli.yaml} +16 -4
- package/builtins/en/workflows/deep-research.yaml +1 -0
- package/builtins/en/workflows/default-high.yaml +3 -32
- package/builtins/en/workflows/default-peer-review.yaml +16 -52
- package/builtins/en/workflows/default.yaml +2 -1
- package/builtins/en/workflows/draft.yaml +1 -1
- package/builtins/en/workflows/dual-cqrs-mini.yaml +1 -0
- package/builtins/en/workflows/dual-cqrs.yaml +28 -87
- package/builtins/en/workflows/dual-for-local-llm.yaml +312 -0
- package/builtins/en/workflows/dual-mini.yaml +1 -0
- package/builtins/en/workflows/dual.yaml +29 -57
- package/builtins/en/workflows/frontend-for-local-llm.yaml +289 -0
- package/builtins/en/workflows/frontend-maintenance.yaml +34 -91
- package/builtins/en/workflows/frontend-mini.yaml +1 -0
- package/builtins/en/workflows/frontend.yaml +27 -85
- package/builtins/en/workflows/merge-readiness-dual-final-gate.yaml +92 -0
- package/builtins/en/workflows/merge-readiness-final-gate.yaml +79 -0
- package/builtins/en/workflows/{peer-review-with-fc.yaml → peer-review-for-local-llm.yaml} +49 -38
- package/builtins/en/workflows/peer-review.yaml +26 -21
- package/builtins/en/workflows/research.yaml +1 -0
- package/builtins/en/workflows/review-backend-cqrs.yaml +63 -35
- package/builtins/en/workflows/review-backend.yaml +63 -35
- package/builtins/en/workflows/review-default.yaml +138 -107
- package/builtins/en/workflows/review-dual-cqrs.yaml +63 -35
- package/builtins/en/workflows/review-dual.yaml +63 -35
- package/builtins/en/workflows/review-fix-backend-cqrs.yaml +27 -87
- package/builtins/en/workflows/review-fix-backend.yaml +25 -86
- package/builtins/en/workflows/review-fix-default.yaml +17 -87
- package/builtins/en/workflows/review-fix-dual-cqrs.yaml +29 -90
- package/builtins/en/workflows/review-fix-dual.yaml +27 -89
- package/builtins/en/workflows/review-fix-frontend.yaml +25 -87
- package/builtins/en/workflows/review-fix-takt-default.yaml +67 -362
- package/builtins/en/workflows/review-frontend.yaml +63 -35
- package/builtins/en/workflows/review-takt-default.yaml +140 -109
- package/builtins/en/workflows/takt-default-for-local-llm.yaml +283 -0
- package/builtins/en/workflows/takt-default-refresh-all.yaml +20 -58
- package/builtins/en/workflows/takt-default-refresh-fast.yaml +19 -56
- package/builtins/en/workflows/takt-default.yaml +3 -25
- package/builtins/en/workflows/terraform.yaml +81 -44
- package/builtins/exec/presets/backend.yaml +31 -0
- package/builtins/exec/presets/dual.yaml +34 -0
- package/builtins/exec/presets/frontend.yaml +31 -0
- package/builtins/exec/presets/research.yaml +45 -0
- package/builtins/ja/config.yaml +6 -0
- package/builtins/ja/facets/instructions/ai-antipattern-review.md +5 -0
- package/builtins/ja/facets/instructions/exec-loop-monitor.md +15 -0
- package/builtins/ja/facets/instructions/exec-replan.md +5 -0
- package/builtins/ja/facets/instructions/exec-review.md +10 -0
- package/builtins/ja/facets/instructions/exec-worker.md +5 -0
- package/builtins/ja/facets/instructions/findings-manager.md +6 -2
- package/builtins/ja/facets/instructions/implement-maintenance.md +4 -15
- package/builtins/ja/facets/instructions/implement.md +2 -14
- package/builtins/ja/facets/instructions/loop-monitor-fix-replan.md +21 -0
- package/builtins/ja/facets/instructions/loop-monitor-replan.md +10 -0
- package/builtins/ja/facets/instructions/plan-maintenance.md +10 -7
- package/builtins/ja/facets/instructions/plan.md +9 -6
- package/builtins/ja/facets/instructions/review-arch.md +5 -0
- package/builtins/ja/facets/instructions/review-coding.md +15 -6
- package/builtins/ja/facets/instructions/review-cqrs-es.md +9 -0
- package/builtins/ja/facets/instructions/review-implementation-semantics.md +17 -0
- package/builtins/ja/facets/instructions/review-merge-readiness.md +14 -0
- package/builtins/ja/facets/instructions/supervise-maintenance.md +2 -2
- package/builtins/ja/facets/instructions/supervise.md +8 -3
- package/builtins/ja/facets/instructions/write-tests-first.md +23 -3
- package/builtins/ja/facets/knowledge/architecture.md +25 -0
- package/builtins/ja/facets/knowledge/cqrs-es.md +211 -16
- package/builtins/ja/facets/knowledge/implementation-semantics.md +104 -0
- package/builtins/ja/facets/knowledge/takt.md +26 -2
- package/builtins/ja/facets/knowledge/unit-testing.md +14 -0
- package/builtins/ja/facets/output-contracts/ai-antipattern-review.md +11 -1
- package/builtins/ja/facets/output-contracts/architecture-review.md +6 -1
- package/builtins/ja/facets/output-contracts/coding-review.md +11 -1
- package/builtins/ja/facets/output-contracts/exec-review-result.md +13 -0
- package/builtins/ja/facets/output-contracts/findings-manager.md +14 -0
- package/builtins/ja/facets/output-contracts/implementation-semantics-review-finding-contract.md +24 -0
- package/builtins/ja/facets/output-contracts/implementation-semantics-review.md +50 -0
- package/builtins/ja/facets/output-contracts/{pure-review-finding-contract.md → merge-readiness-review-finding-contract.md} +14 -8
- package/builtins/ja/facets/output-contracts/{pure-review.md → merge-readiness-review.md} +17 -11
- package/builtins/ja/facets/output-contracts/supervisor-validation.md +6 -2
- package/builtins/ja/facets/output-contracts/test-report.md +25 -0
- package/builtins/ja/facets/partials/instructions/implement-common.md +13 -0
- package/builtins/ja/facets/personas/exec-assistant.md +3 -0
- package/builtins/ja/facets/personas/exec-worker.md +3 -0
- package/builtins/ja/facets/personas/implementation-semantics-reviewer.md +27 -0
- package/builtins/ja/facets/personas/merge-readiness-reviewer.md +28 -0
- package/builtins/ja/facets/personas/supervisor.md +1 -1
- package/builtins/ja/facets/policies/ai-antipattern.md +18 -0
- package/builtins/ja/facets/policies/coding.md +1 -0
- package/builtins/ja/facets/policies/existing-system-respect.md +13 -0
- package/builtins/ja/facets/policies/review.md +18 -0
- package/builtins/ja/facets/policies/testing.md +19 -2
- package/builtins/ja/workflow-categories.yaml +13 -1
- package/builtins/ja/workflows/audit-architecture-backend.yaml +1 -0
- package/builtins/ja/workflows/audit-architecture-dual.yaml +1 -0
- package/builtins/ja/workflows/audit-architecture-frontend.yaml +1 -0
- package/builtins/ja/workflows/audit-architecture.yaml +1 -0
- package/builtins/ja/workflows/audit-e2e.yaml +1 -0
- package/builtins/ja/workflows/audit-security.yaml +1 -0
- package/builtins/ja/workflows/audit-unit.yaml +1 -0
- package/builtins/ja/workflows/backend-cqrs-for-local-llm.yaml +287 -0
- package/builtins/ja/workflows/backend-cqrs-mini.yaml +1 -0
- package/builtins/ja/workflows/backend-cqrs.yaml +28 -84
- package/builtins/ja/workflows/backend-for-local-llm.yaml +283 -0
- package/builtins/ja/workflows/backend-maintenance.yaml +32 -89
- package/builtins/ja/workflows/backend-mini.yaml +1 -0
- package/builtins/ja/workflows/backend.yaml +25 -83
- package/builtins/ja/workflows/{takt-default-with-fc.yaml → cli.yaml} +16 -4
- package/builtins/ja/workflows/deep-research.yaml +1 -0
- package/builtins/ja/workflows/default-high.yaml +3 -32
- package/builtins/ja/workflows/default-peer-review.yaml +16 -52
- package/builtins/ja/workflows/default.yaml +2 -1
- package/builtins/ja/workflows/draft.yaml +1 -1
- package/builtins/ja/workflows/dual-cqrs-mini.yaml +1 -0
- package/builtins/ja/workflows/dual-cqrs.yaml +28 -87
- package/builtins/ja/workflows/dual-for-local-llm.yaml +311 -0
- package/builtins/ja/workflows/dual-mini.yaml +1 -0
- package/builtins/ja/workflows/dual.yaml +29 -57
- package/builtins/ja/workflows/frontend-for-local-llm.yaml +288 -0
- package/builtins/ja/workflows/frontend-maintenance.yaml +34 -91
- package/builtins/ja/workflows/frontend-mini.yaml +1 -0
- package/builtins/ja/workflows/frontend.yaml +27 -85
- package/builtins/ja/workflows/merge-readiness-dual-final-gate.yaml +92 -0
- package/builtins/ja/workflows/merge-readiness-final-gate.yaml +79 -0
- package/builtins/ja/workflows/{peer-review-with-fc.yaml → peer-review-for-local-llm.yaml} +49 -38
- package/builtins/ja/workflows/peer-review.yaml +26 -21
- package/builtins/ja/workflows/research.yaml +1 -0
- package/builtins/ja/workflows/review-backend-cqrs.yaml +63 -35
- package/builtins/ja/workflows/review-backend.yaml +63 -35
- package/builtins/ja/workflows/review-default.yaml +111 -79
- package/builtins/ja/workflows/review-dual-cqrs.yaml +63 -35
- package/builtins/ja/workflows/review-dual.yaml +63 -35
- package/builtins/ja/workflows/review-fix-backend-cqrs.yaml +27 -87
- package/builtins/ja/workflows/review-fix-backend.yaml +25 -86
- package/builtins/ja/workflows/review-fix-default.yaml +17 -87
- package/builtins/ja/workflows/review-fix-dual-cqrs.yaml +29 -90
- package/builtins/ja/workflows/review-fix-dual.yaml +27 -89
- package/builtins/ja/workflows/review-fix-frontend.yaml +25 -87
- package/builtins/ja/workflows/review-fix-takt-default.yaml +67 -362
- package/builtins/ja/workflows/review-frontend.yaml +63 -35
- package/builtins/ja/workflows/review-takt-default.yaml +112 -80
- package/builtins/ja/workflows/takt-default-for-local-llm.yaml +282 -0
- package/builtins/ja/workflows/takt-default-refresh-all.yaml +20 -58
- package/builtins/ja/workflows/takt-default-refresh-fast.yaml +19 -56
- package/builtins/ja/workflows/takt-default.yaml +3 -25
- package/builtins/ja/workflows/terraform.yaml +81 -44
- package/builtins/skill/SKILL.md +1 -1
- package/builtins/skill/references/engine.md +1 -15
- package/builtins/skill-codex/SKILL.md +1 -1
- package/builtins/skill-codex/references/engine.md +0 -2
- package/dist/agents/decompose-task-usecase.d.ts +1 -1
- package/dist/agents/decompose-task-usecase.d.ts.map +1 -1
- package/dist/agents/decompose-task-usecase.js +2 -4
- package/dist/agents/decompose-task-usecase.js.map +1 -1
- package/dist/agents/judge-utils.d.ts.map +1 -1
- package/dist/agents/judge-utils.js +4 -3
- package/dist/agents/judge-utils.js.map +1 -1
- package/dist/agents/runner.d.ts.map +1 -1
- package/dist/agents/runner.js +7 -6
- package/dist/agents/runner.js.map +1 -1
- package/dist/agents/structured-caller/prompt-based-structured-caller.d.ts.map +1 -1
- package/dist/agents/structured-caller/prompt-based-structured-caller.js +2 -3
- package/dist/agents/structured-caller/prompt-based-structured-caller.js.map +1 -1
- package/dist/agents/structured-caller/shared.d.ts.map +1 -1
- package/dist/agents/structured-caller/shared.js +2 -9
- package/dist/agents/structured-caller/shared.js.map +1 -1
- package/dist/app/acp/agent.d.ts +28 -0
- package/dist/app/acp/agent.d.ts.map +1 -0
- package/dist/app/acp/agent.js +279 -0
- package/dist/app/acp/agent.js.map +1 -0
- package/dist/app/acp/confirmationBridge.d.ts +5 -0
- package/dist/app/acp/confirmationBridge.d.ts.map +1 -0
- package/dist/app/acp/confirmationBridge.js +194 -0
- package/dist/app/acp/confirmationBridge.js.map +1 -0
- package/dist/app/acp/conversationFactory.d.ts +4 -0
- package/dist/app/acp/conversationFactory.d.ts.map +1 -0
- package/dist/app/acp/conversationFactory.js +33 -0
- package/dist/app/acp/conversationFactory.js.map +1 -0
- package/dist/app/acp/enqueue.d.ts +33 -0
- package/dist/app/acp/enqueue.d.ts.map +1 -0
- package/dist/app/acp/enqueue.js +45 -0
- package/dist/app/acp/enqueue.js.map +1 -0
- package/dist/app/acp/index.d.ts +7 -0
- package/dist/app/acp/index.d.ts.map +1 -0
- package/dist/app/acp/index.js +125 -0
- package/dist/app/acp/index.js.map +1 -0
- package/dist/app/acp/intent.d.ts +10 -0
- package/dist/app/acp/intent.d.ts.map +1 -0
- package/dist/app/acp/intent.js +210 -0
- package/dist/app/acp/intent.js.map +1 -0
- package/dist/app/acp/mcpServers.d.ts +4 -0
- package/dist/app/acp/mcpServers.d.ts.map +1 -0
- package/dist/app/acp/mcpServers.js +51 -0
- package/dist/app/acp/mcpServers.js.map +1 -0
- package/dist/app/acp/promptContent.d.ts +3 -0
- package/dist/app/acp/promptContent.d.ts.map +1 -0
- package/dist/app/acp/promptContent.js +23 -0
- package/dist/app/acp/promptContent.js.map +1 -0
- package/dist/app/acp/sessionStore.d.ts +19 -0
- package/dist/app/acp/sessionStore.d.ts.map +1 -0
- package/dist/app/acp/sessionStore.js +50 -0
- package/dist/app/acp/sessionStore.js.map +1 -0
- package/dist/app/acp/sessionUpdates.d.ts +8 -0
- package/dist/app/acp/sessionUpdates.d.ts.map +1 -0
- package/dist/app/acp/sessionUpdates.js +108 -0
- package/dist/app/acp/sessionUpdates.js.map +1 -0
- package/dist/app/acp/taskContext.d.ts +17 -0
- package/dist/app/acp/taskContext.d.ts.map +1 -0
- package/dist/app/acp/taskContext.js +81 -0
- package/dist/app/acp/taskContext.js.map +1 -0
- package/dist/app/acp/types.d.ts +33 -0
- package/dist/app/acp/types.d.ts.map +1 -0
- package/dist/app/acp/types.js +2 -0
- package/dist/app/acp/types.js.map +1 -0
- package/dist/app/cli/commands.js +13 -0
- package/dist/app/cli/commands.js.map +1 -1
- package/dist/app/cli/program.d.ts.map +1 -1
- package/dist/app/cli/program.js +2 -3
- package/dist/app/cli/program.js.map +1 -1
- package/dist/app/mcp/index.d.ts +3 -0
- package/dist/app/mcp/index.d.ts.map +1 -0
- package/dist/app/mcp/index.js +16 -0
- package/dist/app/mcp/index.js.map +1 -0
- package/dist/app/mcp/server.d.ts +2 -0
- package/dist/app/mcp/server.d.ts.map +1 -0
- package/dist/app/mcp/server.js +2 -0
- package/dist/app/mcp/server.js.map +1 -0
- package/dist/core/config/provider-resolution.d.ts +1 -0
- package/dist/core/config/provider-resolution.d.ts.map +1 -1
- package/dist/core/config/provider-resolution.js +19 -0
- package/dist/core/config/provider-resolution.js.map +1 -1
- package/dist/core/models/finding-schemas.d.ts +129 -10
- package/dist/core/models/finding-schemas.d.ts.map +1 -1
- package/dist/core/models/finding-schemas.js +79 -9
- package/dist/core/models/finding-schemas.js.map +1 -1
- package/dist/core/models/finding-types.d.ts +39 -2
- package/dist/core/models/finding-types.d.ts.map +1 -1
- package/dist/core/models/finding-types.js +3 -2
- package/dist/core/models/finding-types.js.map +1 -1
- package/dist/core/models/workflow-condition-expression.d.ts +17 -0
- package/dist/core/models/workflow-condition-expression.d.ts.map +1 -1
- package/dist/core/models/workflow-condition-expression.js +85 -2
- package/dist/core/models/workflow-condition-expression.js.map +1 -1
- package/dist/core/models/workflow-schemas.d.ts +222 -22
- package/dist/core/models/workflow-schemas.d.ts.map +1 -1
- package/dist/core/models/workflow-schemas.js +38 -4
- package/dist/core/models/workflow-schemas.js.map +1 -1
- package/dist/core/models/workflow-types.d.ts +14 -0
- package/dist/core/models/workflow-types.d.ts.map +1 -1
- package/dist/core/runtime/runtime-environment.d.ts.map +1 -1
- package/dist/core/runtime/runtime-environment.js +19 -0
- package/dist/core/runtime/runtime-environment.js.map +1 -1
- package/dist/core/workflow/engine/LoopMonitorJudgeRunner.d.ts +2 -2
- package/dist/core/workflow/engine/LoopMonitorJudgeRunner.d.ts.map +1 -1
- package/dist/core/workflow/engine/LoopMonitorJudgeRunner.js +8 -7
- package/dist/core/workflow/engine/LoopMonitorJudgeRunner.js.map +1 -1
- package/dist/core/workflow/engine/OptionsBuilder.d.ts +15 -5
- package/dist/core/workflow/engine/OptionsBuilder.d.ts.map +1 -1
- package/dist/core/workflow/engine/OptionsBuilder.js +93 -16
- package/dist/core/workflow/engine/OptionsBuilder.js.map +1 -1
- package/dist/core/workflow/engine/ParallelRunner.d.ts +2 -0
- package/dist/core/workflow/engine/ParallelRunner.d.ts.map +1 -1
- package/dist/core/workflow/engine/ParallelRunner.js +131 -23
- package/dist/core/workflow/engine/ParallelRunner.js.map +1 -1
- package/dist/core/workflow/engine/StepExecutor.d.ts +10 -0
- package/dist/core/workflow/engine/StepExecutor.d.ts.map +1 -1
- package/dist/core/workflow/engine/StepExecutor.js +90 -38
- package/dist/core/workflow/engine/StepExecutor.js.map +1 -1
- package/dist/core/workflow/engine/TeamLeaderRunner.d.ts.map +1 -1
- package/dist/core/workflow/engine/TeamLeaderRunner.js +3 -0
- package/dist/core/workflow/engine/TeamLeaderRunner.js.map +1 -1
- package/dist/core/workflow/engine/WorkflowEngine.d.ts.map +1 -1
- package/dist/core/workflow/engine/WorkflowEngine.js +8 -3
- package/dist/core/workflow/engine/WorkflowEngine.js.map +1 -1
- package/dist/core/workflow/engine/WorkflowEngineSetup.d.ts.map +1 -1
- package/dist/core/workflow/engine/WorkflowEngineSetup.js +5 -3
- package/dist/core/workflow/engine/WorkflowEngineSetup.js.map +1 -1
- package/dist/core/workflow/engine/WorkflowRunLoop.d.ts.map +1 -1
- package/dist/core/workflow/engine/WorkflowRunLoop.js +31 -0
- package/dist/core/workflow/engine/WorkflowRunLoop.js.map +1 -1
- package/dist/core/workflow/engine/WorkflowValidator.d.ts.map +1 -1
- package/dist/core/workflow/engine/WorkflowValidator.js +25 -12
- package/dist/core/workflow/engine/WorkflowValidator.js.map +1 -1
- package/dist/core/workflow/engine/engine-provider-options.d.ts +2 -1
- package/dist/core/workflow/engine/engine-provider-options.d.ts.map +1 -1
- package/dist/core/workflow/engine/engine-provider-options.js +30 -6
- package/dist/core/workflow/engine/engine-provider-options.js.map +1 -1
- package/dist/core/workflow/engine/team-leader-part-runner.d.ts.map +1 -1
- package/dist/core/workflow/engine/team-leader-part-runner.js +3 -1
- package/dist/core/workflow/engine/team-leader-part-runner.js.map +1 -1
- package/dist/core/workflow/evaluation/RuleEvaluator.d.ts +0 -1
- package/dist/core/workflow/evaluation/RuleEvaluator.d.ts.map +1 -1
- package/dist/core/workflow/evaluation/RuleEvaluator.js +22 -34
- package/dist/core/workflow/evaluation/RuleEvaluator.js.map +1 -1
- package/dist/core/workflow/evaluation/rule-utils.d.ts +33 -1
- package/dist/core/workflow/evaluation/rule-utils.d.ts.map +1 -1
- package/dist/core/workflow/evaluation/rule-utils.js +77 -5
- package/dist/core/workflow/evaluation/rule-utils.js.map +1 -1
- package/dist/core/workflow/evaluation/when-evaluator.d.ts +1 -0
- package/dist/core/workflow/evaluation/when-evaluator.d.ts.map +1 -1
- package/dist/core/workflow/evaluation/when-evaluator.js +18 -40
- package/dist/core/workflow/evaluation/when-evaluator.js.map +1 -1
- package/dist/core/workflow/findings/context.d.ts +4 -0
- package/dist/core/workflow/findings/context.d.ts.map +1 -1
- package/dist/core/workflow/findings/context.js +27 -0
- package/dist/core/workflow/findings/context.js.map +1 -1
- package/dist/core/workflow/findings/manager-output-validation.d.ts +2 -0
- package/dist/core/workflow/findings/manager-output-validation.d.ts.map +1 -1
- package/dist/core/workflow/findings/manager-output-validation.js +145 -8
- package/dist/core/workflow/findings/manager-output-validation.js.map +1 -1
- package/dist/core/workflow/findings/manager-runner.d.ts +13 -3
- package/dist/core/workflow/findings/manager-runner.d.ts.map +1 -1
- package/dist/core/workflow/findings/manager-runner.js +28 -27
- package/dist/core/workflow/findings/manager-runner.js.map +1 -1
- package/dist/core/workflow/findings/reconciler.d.ts +1 -0
- package/dist/core/workflow/findings/reconciler.d.ts.map +1 -1
- package/dist/core/workflow/findings/reconciler.js +90 -4
- package/dist/core/workflow/findings/reconciler.js.map +1 -1
- package/dist/core/workflow/instruction/InstructionBuilder.d.ts.map +1 -1
- package/dist/core/workflow/instruction/InstructionBuilder.js +18 -2
- package/dist/core/workflow/instruction/InstructionBuilder.js.map +1 -1
- package/dist/core/workflow/instruction/instruction-context.d.ts +4 -0
- package/dist/core/workflow/instruction/instruction-context.d.ts.map +1 -1
- package/dist/core/workflow/instruction/instruction-context.js.map +1 -1
- package/dist/core/workflow/instruction/status-rules.d.ts.map +1 -1
- package/dist/core/workflow/instruction/status-rules.js +7 -2
- package/dist/core/workflow/instruction/status-rules.js.map +1 -1
- package/dist/core/workflow/phase-runner.d.ts +17 -10
- package/dist/core/workflow/phase-runner.d.ts.map +1 -1
- package/dist/core/workflow/phase-runner.js +1 -1
- package/dist/core/workflow/phase-runner.js.map +1 -1
- package/dist/core/workflow/provider-model-requirements.d.ts +6 -0
- package/dist/core/workflow/provider-model-requirements.d.ts.map +1 -0
- package/dist/core/workflow/provider-model-requirements.js +15 -0
- package/dist/core/workflow/provider-model-requirements.js.map +1 -0
- package/dist/core/workflow/provider-resolution.d.ts +4 -6
- package/dist/core/workflow/provider-resolution.d.ts.map +1 -1
- package/dist/core/workflow/provider-resolution.js +19 -11
- package/dist/core/workflow/provider-resolution.js.map +1 -1
- package/dist/core/workflow/report-phase-runner.d.ts +5 -2
- package/dist/core/workflow/report-phase-runner.d.ts.map +1 -1
- package/dist/core/workflow/report-phase-runner.js +135 -43
- package/dist/core/workflow/report-phase-runner.js.map +1 -1
- package/dist/core/workflow/session-key.d.ts +4 -1
- package/dist/core/workflow/session-key.d.ts.map +1 -1
- package/dist/core/workflow/session-key.js +5 -2
- package/dist/core/workflow/session-key.js.map +1 -1
- package/dist/core/workflow/status-judgment-phase.d.ts +2 -2
- package/dist/core/workflow/status-judgment-phase.d.ts.map +1 -1
- package/dist/core/workflow/status-judgment-phase.js +1 -7
- package/dist/core/workflow/status-judgment-phase.js.map +1 -1
- package/dist/core/workflow/system/system-step-effect-runner.js +2 -2
- package/dist/core/workflow/system/system-step-effect-runner.js.map +1 -1
- package/dist/core/workflow/system/system-step-services.d.ts +101 -0
- package/dist/core/workflow/system/system-step-services.d.ts.map +1 -1
- package/dist/core/workflow/types.d.ts +7 -3
- package/dist/core/workflow/types.d.ts.map +1 -1
- package/dist/features/analytics/events.d.ts +2 -1
- package/dist/features/analytics/events.d.ts.map +1 -1
- package/dist/features/catalog/catalogFacets.d.ts +11 -1
- package/dist/features/catalog/catalogFacets.d.ts.map +1 -1
- package/dist/features/catalog/catalogFacets.js +36 -10
- package/dist/features/catalog/catalogFacets.js.map +1 -1
- package/dist/features/exec/assistantSession.d.ts +18 -0
- package/dist/features/exec/assistantSession.d.ts.map +1 -0
- package/dist/features/exec/assistantSession.js +51 -0
- package/dist/features/exec/assistantSession.js.map +1 -0
- package/dist/features/exec/command.d.ts +9 -0
- package/dist/features/exec/command.d.ts.map +1 -0
- package/dist/features/exec/command.js +143 -0
- package/dist/features/exec/command.js.map +1 -0
- package/dist/features/exec/commandAvailability.d.ts +4 -0
- package/dist/features/exec/commandAvailability.d.ts.map +1 -0
- package/dist/features/exec/commandAvailability.js +10 -0
- package/dist/features/exec/commandAvailability.js.map +1 -0
- package/dist/features/exec/configOps.d.ts +13 -0
- package/dist/features/exec/configOps.d.ts.map +1 -0
- package/dist/features/exec/configOps.js +129 -0
- package/dist/features/exec/configOps.js.map +1 -0
- package/dist/features/exec/configValidation.d.ts +15 -0
- package/dist/features/exec/configValidation.d.ts.map +1 -0
- package/dist/features/exec/configValidation.js +146 -0
- package/dist/features/exec/configValidation.js.map +1 -0
- package/dist/features/exec/defaults.d.ts +3 -0
- package/dist/features/exec/defaults.d.ts.map +1 -0
- package/dist/features/exec/defaults.js +30 -0
- package/dist/features/exec/defaults.js.map +1 -0
- package/dist/features/exec/facetEditor.d.ts +5 -0
- package/dist/features/exec/facetEditor.d.ts.map +1 -0
- package/dist/features/exec/facetEditor.js +293 -0
- package/dist/features/exec/facetEditor.js.map +1 -0
- package/dist/features/exec/index.d.ts +2 -0
- package/dist/features/exec/index.d.ts.map +1 -0
- package/dist/features/exec/index.js +2 -0
- package/dist/features/exec/index.js.map +1 -0
- package/dist/features/exec/labels.d.ts +11 -0
- package/dist/features/exec/labels.d.ts.map +1 -0
- package/dist/features/exec/labels.js +20 -0
- package/dist/features/exec/labels.js.map +1 -0
- package/dist/features/exec/presetSelection.d.ts +3 -0
- package/dist/features/exec/presetSelection.d.ts.map +1 -0
- package/dist/features/exec/presetSelection.js +16 -0
- package/dist/features/exec/presetSelection.js.map +1 -0
- package/dist/features/exec/presetSetup.d.ts +7 -0
- package/dist/features/exec/presetSetup.d.ts.map +1 -0
- package/dist/features/exec/presetSetup.js +120 -0
- package/dist/features/exec/presetSetup.js.map +1 -0
- package/dist/features/exec/presetStore.d.ts +25 -0
- package/dist/features/exec/presetStore.d.ts.map +1 -0
- package/dist/features/exec/presetStore.js +326 -0
- package/dist/features/exec/presetStore.js.map +1 -0
- package/dist/features/exec/projectLocalFiles.d.ts +9 -0
- package/dist/features/exec/projectLocalFiles.d.ts.map +1 -0
- package/dist/features/exec/projectLocalFiles.js +111 -0
- package/dist/features/exec/projectLocalFiles.js.map +1 -0
- package/dist/features/exec/promptUtils.d.ts +8 -0
- package/dist/features/exec/promptUtils.d.ts.map +1 -0
- package/dist/features/exec/promptUtils.js +32 -0
- package/dist/features/exec/promptUtils.js.map +1 -0
- package/dist/features/exec/runtimeConfig.d.ts +13 -0
- package/dist/features/exec/runtimeConfig.d.ts.map +1 -0
- package/dist/features/exec/runtimeConfig.js +59 -0
- package/dist/features/exec/runtimeConfig.js.map +1 -0
- package/dist/features/exec/setupMenu.d.ts +5 -0
- package/dist/features/exec/setupMenu.d.ts.map +1 -0
- package/dist/features/exec/setupMenu.js +431 -0
- package/dist/features/exec/setupMenu.js.map +1 -0
- package/dist/features/exec/types.d.ts +52 -0
- package/dist/features/exec/types.d.ts.map +1 -0
- package/dist/features/exec/types.js +2 -0
- package/dist/features/exec/types.js.map +1 -0
- package/dist/features/exec/workflowRunner.d.ts +9 -0
- package/dist/features/exec/workflowRunner.d.ts.map +1 -0
- package/dist/features/exec/workflowRunner.js +96 -0
- package/dist/features/exec/workflowRunner.js.map +1 -0
- package/dist/features/exec/workflowTemplate.d.ts +9 -0
- package/dist/features/exec/workflowTemplate.d.ts.map +1 -0
- package/dist/features/exec/workflowTemplate.js +159 -0
- package/dist/features/exec/workflowTemplate.js.map +1 -0
- package/dist/features/interactive/aiCaller.d.ts +5 -0
- package/dist/features/interactive/aiCaller.d.ts.map +1 -1
- package/dist/features/interactive/aiCaller.js +35 -10
- package/dist/features/interactive/aiCaller.js.map +1 -1
- package/dist/features/interactive/commandMatcher.d.ts +2 -1
- package/dist/features/interactive/commandMatcher.d.ts.map +1 -1
- package/dist/features/interactive/commandMatcher.js +13 -2
- package/dist/features/interactive/commandMatcher.js.map +1 -1
- package/dist/features/interactive/conversationSession.d.ts +44 -0
- package/dist/features/interactive/conversationSession.d.ts.map +1 -0
- package/dist/features/interactive/conversationSession.js +133 -0
- package/dist/features/interactive/conversationSession.js.map +1 -0
- package/dist/features/interactive/index.d.ts +1 -0
- package/dist/features/interactive/index.d.ts.map +1 -1
- package/dist/features/interactive/index.js +1 -0
- package/dist/features/interactive/index.js.map +1 -1
- package/dist/features/interactive/interactive-summary.d.ts.map +1 -1
- package/dist/features/interactive/interactive-summary.js +45 -23
- package/dist/features/interactive/interactive-summary.js.map +1 -1
- package/dist/features/interactive/interactive.d.ts +5 -4
- package/dist/features/interactive/interactive.d.ts.map +1 -1
- package/dist/features/interactive/interactive.js +12 -6
- package/dist/features/interactive/interactive.js.map +1 -1
- package/dist/features/interactive/interactiveApplication.d.ts +5 -0
- package/dist/features/interactive/interactiveApplication.d.ts.map +1 -0
- package/dist/features/interactive/interactiveApplication.js +10 -0
- package/dist/features/interactive/interactiveApplication.js.map +1 -0
- package/dist/features/interactive/promptSections.d.ts.map +1 -1
- package/dist/features/interactive/promptSections.js +3 -6
- package/dist/features/interactive/promptSections.js.map +1 -1
- package/dist/features/interactive/retryMode.d.ts.map +1 -1
- package/dist/features/interactive/retryMode.js +3 -3
- package/dist/features/interactive/retryMode.js.map +1 -1
- package/dist/features/interactive/runSessionReader.d.ts +4 -37
- package/dist/features/interactive/runSessionReader.d.ts.map +1 -1
- package/dist/features/interactive/runSessionReader.js +130 -49
- package/dist/features/interactive/runSessionReader.js.map +1 -1
- package/dist/features/interactive/slashCommandRegistry.d.ts +2 -0
- package/dist/features/interactive/slashCommandRegistry.d.ts.map +1 -1
- package/dist/features/interactive/slashCommandRegistry.js +5 -0
- package/dist/features/interactive/slashCommandRegistry.js.map +1 -1
- package/dist/features/mcp/operations.d.ts +25 -0
- package/dist/features/mcp/operations.d.ts.map +1 -0
- package/dist/features/mcp/operations.js +150 -0
- package/dist/features/mcp/operations.js.map +1 -0
- package/dist/features/mcp/schemas.d.ts +50 -0
- package/dist/features/mcp/schemas.d.ts.map +1 -0
- package/dist/features/mcp/schemas.js +59 -0
- package/dist/features/mcp/schemas.js.map +1 -0
- package/dist/features/mcp/server.d.ts +7 -0
- package/dist/features/mcp/server.d.ts.map +1 -0
- package/dist/features/mcp/server.js +36 -0
- package/dist/features/mcp/server.js.map +1 -0
- package/dist/features/prompt/preview.d.ts.map +1 -1
- package/dist/features/prompt/preview.js +58 -32
- package/dist/features/prompt/preview.js.map +1 -1
- package/dist/features/tasks/add/index.d.ts +8 -20
- package/dist/features/tasks/add/index.d.ts.map +1 -1
- package/dist/features/tasks/add/index.js +42 -61
- package/dist/features/tasks/add/index.js.map +1 -1
- package/dist/features/tasks/attachments.d.ts +2 -5
- package/dist/features/tasks/attachments.d.ts.map +1 -1
- package/dist/features/tasks/attachments.js +11 -35
- package/dist/features/tasks/attachments.js.map +1 -1
- package/dist/features/tasks/execute/outputFns.d.ts +1 -1
- package/dist/features/tasks/execute/outputFns.d.ts.map +1 -1
- package/dist/features/tasks/execute/outputFns.js +17 -1
- package/dist/features/tasks/execute/outputFns.js.map +1 -1
- package/dist/features/tasks/execute/postExecution.d.ts +4 -1
- package/dist/features/tasks/execute/postExecution.d.ts.map +1 -1
- package/dist/features/tasks/execute/postExecution.js +33 -14
- package/dist/features/tasks/execute/postExecution.js.map +1 -1
- package/dist/features/tasks/execute/resolveTask.d.ts +9 -3
- package/dist/features/tasks/execute/resolveTask.d.ts.map +1 -1
- package/dist/features/tasks/execute/resolveTask.js +45 -13
- package/dist/features/tasks/execute/resolveTask.js.map +1 -1
- package/dist/features/tasks/execute/runTaskExecution.d.ts +7 -1
- package/dist/features/tasks/execute/runTaskExecution.d.ts.map +1 -1
- package/dist/features/tasks/execute/runTaskExecution.js +11 -8
- package/dist/features/tasks/execute/runTaskExecution.js.map +1 -1
- package/dist/features/tasks/execute/selectAndExecute.d.ts.map +1 -1
- package/dist/features/tasks/execute/selectAndExecute.js +6 -2
- package/dist/features/tasks/execute/selectAndExecute.js.map +1 -1
- package/dist/features/tasks/execute/taskExecution.d.ts +12 -3
- package/dist/features/tasks/execute/taskExecution.d.ts.map +1 -1
- package/dist/features/tasks/execute/taskExecution.js +63 -16
- package/dist/features/tasks/execute/taskExecution.js.map +1 -1
- package/dist/features/tasks/execute/taskResultHandler.d.ts +2 -2
- package/dist/features/tasks/execute/taskResultHandler.d.ts.map +1 -1
- package/dist/features/tasks/execute/taskResultHandler.js +8 -4
- package/dist/features/tasks/execute/taskResultHandler.js.map +1 -1
- package/dist/features/tasks/execute/taskWorkflowExecution.d.ts.map +1 -1
- package/dist/features/tasks/execute/taskWorkflowExecution.js +101 -10
- package/dist/features/tasks/execute/taskWorkflowExecution.js.map +1 -1
- package/dist/features/tasks/execute/traceTaskMetadata.js +3 -6
- package/dist/features/tasks/execute/traceTaskMetadata.js.map +1 -1
- package/dist/features/tasks/execute/types.d.ts +103 -2
- package/dist/features/tasks/execute/types.d.ts.map +1 -1
- package/dist/features/tasks/execute/workflowExecution.d.ts +3 -1
- package/dist/features/tasks/execute/workflowExecution.d.ts.map +1 -1
- package/dist/features/tasks/execute/workflowExecution.js +41 -3
- package/dist/features/tasks/execute/workflowExecution.js.map +1 -1
- package/dist/features/tasks/execute/workflowExecutionApi.d.ts +6 -0
- package/dist/features/tasks/execute/workflowExecutionApi.d.ts.map +1 -0
- package/dist/features/tasks/execute/workflowExecutionApi.js +25 -0
- package/dist/features/tasks/execute/workflowExecutionApi.js.map +1 -0
- package/dist/features/tasks/execute/workflowExecutionBootstrap.d.ts +2 -1
- package/dist/features/tasks/execute/workflowExecutionBootstrap.d.ts.map +1 -1
- package/dist/features/tasks/execute/workflowExecutionBootstrap.js +18 -14
- package/dist/features/tasks/execute/workflowExecutionBootstrap.js.map +1 -1
- package/dist/features/tasks/execute/workflowExecutionEvents.d.ts +14 -1
- package/dist/features/tasks/execute/workflowExecutionEvents.d.ts.map +1 -1
- package/dist/features/tasks/execute/workflowExecutionEvents.js +244 -3
- package/dist/features/tasks/execute/workflowExecutionEvents.js.map +1 -1
- package/dist/features/tasks/list/instructMode.d.ts.map +1 -1
- package/dist/features/tasks/list/instructMode.js +3 -3
- package/dist/features/tasks/list/instructMode.js.map +1 -1
- package/dist/features/tasks/list/requeueHelpers.d.ts +1 -0
- package/dist/features/tasks/list/requeueHelpers.d.ts.map +1 -1
- package/dist/features/tasks/list/requeueHelpers.js +3 -0
- package/dist/features/tasks/list/requeueHelpers.js.map +1 -1
- package/dist/features/tasks/list/taskInstructionActions.d.ts.map +1 -1
- package/dist/features/tasks/list/taskInstructionActions.js +2 -2
- package/dist/features/tasks/list/taskInstructionActions.js.map +1 -1
- package/dist/features/tasks/list/taskRetryActions.d.ts.map +1 -1
- package/dist/features/tasks/list/taskRetryActions.js +1 -4
- package/dist/features/tasks/list/taskRetryActions.js.map +1 -1
- package/dist/features/tasks/resume/directInstructMode.d.ts.map +1 -1
- package/dist/features/tasks/resume/directInstructMode.js +3 -3
- package/dist/features/tasks/resume/directInstructMode.js.map +1 -1
- package/dist/features/tasks/taskContextValidation.d.ts +10 -0
- package/dist/features/tasks/taskContextValidation.d.ts.map +1 -0
- package/dist/features/tasks/taskContextValidation.js +73 -0
- package/dist/features/tasks/taskContextValidation.js.map +1 -0
- package/dist/infra/codex/client.d.ts.map +1 -1
- package/dist/infra/codex/client.js +5 -0
- package/dist/infra/codex/client.js.map +1 -1
- package/dist/infra/config/configNormalizers.d.ts.map +1 -1
- package/dist/infra/config/configNormalizers.js +14 -11
- package/dist/infra/config/configNormalizers.js.map +1 -1
- package/dist/infra/config/global/globalConfigCore.js +2 -2
- package/dist/infra/config/global/globalConfigCore.js.map +1 -1
- package/dist/infra/config/loaders/resource-resolver.d.ts +3 -33
- package/dist/infra/config/loaders/resource-resolver.d.ts.map +1 -1
- package/dist/infra/config/loaders/resource-resolver.js +140 -52
- package/dist/infra/config/loaders/resource-resolver.js.map +1 -1
- package/dist/infra/config/loaders/workflowCallContracts.d.ts.map +1 -1
- package/dist/infra/config/loaders/workflowCallContracts.js +5 -0
- package/dist/infra/config/loaders/workflowCallContracts.js.map +1 -1
- package/dist/infra/config/loaders/workflowCallableArgResolver.d.ts.map +1 -1
- package/dist/infra/config/loaders/workflowCallableArgResolver.js +42 -7
- package/dist/infra/config/loaders/workflowCallableArgResolver.js.map +1 -1
- package/dist/infra/config/loaders/workflowCallableRuleValidation.d.ts.map +1 -1
- package/dist/infra/config/loaders/workflowCallableRuleValidation.js +5 -0
- package/dist/infra/config/loaders/workflowCallableRuleValidation.js.map +1 -1
- package/dist/infra/config/loaders/workflowDoctor.d.ts.map +1 -1
- package/dist/infra/config/loaders/workflowDoctor.js +19 -7
- package/dist/infra/config/loaders/workflowDoctor.js.map +1 -1
- package/dist/infra/config/loaders/workflowLoopMonitorNormalizer.d.ts +3 -1
- package/dist/infra/config/loaders/workflowLoopMonitorNormalizer.d.ts.map +1 -1
- package/dist/infra/config/loaders/workflowLoopMonitorNormalizer.js +12 -2
- package/dist/infra/config/loaders/workflowLoopMonitorNormalizer.js.map +1 -1
- package/dist/infra/config/loaders/workflowPackageScope.d.ts +1 -0
- package/dist/infra/config/loaders/workflowPackageScope.d.ts.map +1 -1
- package/dist/infra/config/loaders/workflowPackageScope.js +20 -2
- package/dist/infra/config/loaders/workflowPackageScope.js.map +1 -1
- package/dist/infra/config/loaders/workflowParser.d.ts.map +1 -1
- package/dist/infra/config/loaders/workflowParser.js +17 -14
- package/dist/infra/config/loaders/workflowParser.js.map +1 -1
- package/dist/infra/config/loaders/workflowPreview.d.ts +3 -0
- package/dist/infra/config/loaders/workflowPreview.d.ts.map +1 -1
- package/dist/infra/config/loaders/workflowPreview.js +17 -9
- package/dist/infra/config/loaders/workflowPreview.js.map +1 -1
- package/dist/infra/config/loaders/workflowRuleNormalizer.d.ts +10 -0
- package/dist/infra/config/loaders/workflowRuleNormalizer.d.ts.map +1 -1
- package/dist/infra/config/loaders/workflowRuleNormalizer.js +70 -1
- package/dist/infra/config/loaders/workflowRuleNormalizer.js.map +1 -1
- package/dist/infra/config/loaders/workflowStepNormalizer.d.ts +1 -1
- package/dist/infra/config/loaders/workflowStepNormalizer.d.ts.map +1 -1
- package/dist/infra/config/loaders/workflowStepNormalizer.js +33 -11
- package/dist/infra/config/loaders/workflowStepNormalizer.js.map +1 -1
- package/dist/infra/config/project/sessionStore.d.ts +2 -2
- package/dist/infra/config/project/sessionStore.d.ts.map +1 -1
- package/dist/infra/config/project/sessionStore.js +12 -4
- package/dist/infra/config/project/sessionStore.js.map +1 -1
- package/dist/infra/config/providerModelRequirements.d.ts +2 -0
- package/dist/infra/config/providerModelRequirements.d.ts.map +1 -0
- package/dist/infra/config/providerModelRequirements.js +2 -0
- package/dist/infra/config/providerModelRequirements.js.map +1 -0
- package/dist/infra/git/format.d.ts +1 -0
- package/dist/infra/git/format.d.ts.map +1 -1
- package/dist/infra/git/format.js +21 -0
- package/dist/infra/git/format.js.map +1 -1
- package/dist/infra/git/index.d.ts +1 -1
- package/dist/infra/git/index.d.ts.map +1 -1
- package/dist/infra/git/types.d.ts +16 -4
- package/dist/infra/git/types.d.ts.map +1 -1
- package/dist/infra/github/GitHubProvider.d.ts +2 -1
- package/dist/infra/github/GitHubProvider.d.ts.map +1 -1
- package/dist/infra/github/GitHubProvider.js +4 -1
- package/dist/infra/github/GitHubProvider.js.map +1 -1
- package/dist/infra/github/issue.d.ts +2 -1
- package/dist/infra/github/issue.d.ts.map +1 -1
- package/dist/infra/github/issue.js +23 -2
- package/dist/infra/github/issue.js.map +1 -1
- package/dist/infra/gitlab/GitLabProvider.d.ts +2 -1
- package/dist/infra/gitlab/GitLabProvider.d.ts.map +1 -1
- package/dist/infra/gitlab/GitLabProvider.js +4 -1
- package/dist/infra/gitlab/GitLabProvider.js.map +1 -1
- package/dist/infra/gitlab/issue.d.ts +2 -1
- package/dist/infra/gitlab/issue.d.ts.map +1 -1
- package/dist/infra/gitlab/issue.js +30 -2
- package/dist/infra/gitlab/issue.js.map +1 -1
- package/dist/infra/opencode/OpenCodeStreamHandler.d.ts +4 -0
- package/dist/infra/opencode/OpenCodeStreamHandler.d.ts.map +1 -1
- package/dist/infra/opencode/OpenCodeStreamHandler.js.map +1 -1
- package/dist/infra/opencode/allowedTools.d.ts +1 -0
- package/dist/infra/opencode/allowedTools.d.ts.map +1 -1
- package/dist/infra/opencode/allowedTools.js +6 -0
- package/dist/infra/opencode/allowedTools.js.map +1 -1
- package/dist/infra/opencode/client.d.ts +2 -0
- package/dist/infra/opencode/client.d.ts.map +1 -1
- package/dist/infra/opencode/client.js +392 -169
- package/dist/infra/opencode/client.js.map +1 -1
- package/dist/infra/opencode/types.d.ts +35 -0
- package/dist/infra/opencode/types.d.ts.map +1 -1
- package/dist/infra/opencode/types.js +98 -11
- package/dist/infra/opencode/types.js.map +1 -1
- package/dist/infra/opencode/unavailable-tool-loop.d.ts +13 -0
- package/dist/infra/opencode/unavailable-tool-loop.d.ts.map +1 -1
- package/dist/infra/opencode/unavailable-tool-loop.js +46 -0
- package/dist/infra/opencode/unavailable-tool-loop.js.map +1 -1
- package/dist/infra/providers/allowed-tool-edit-policy.d.ts +1 -0
- package/dist/infra/providers/allowed-tool-edit-policy.d.ts.map +1 -1
- package/dist/infra/providers/allowed-tool-edit-policy.js +35 -1
- package/dist/infra/providers/allowed-tool-edit-policy.js.map +1 -1
- package/dist/infra/providers/claude-headless.d.ts.map +1 -1
- package/dist/infra/providers/claude-headless.js +0 -2
- package/dist/infra/providers/claude-headless.js.map +1 -1
- package/dist/infra/providers/claude-terminal.d.ts.map +1 -1
- package/dist/infra/providers/claude-terminal.js +0 -2
- package/dist/infra/providers/claude-terminal.js.map +1 -1
- package/dist/infra/providers/claude.d.ts.map +1 -1
- package/dist/infra/providers/claude.js +0 -2
- package/dist/infra/providers/claude.js.map +1 -1
- package/dist/infra/providers/opencode.d.ts +3 -2
- package/dist/infra/providers/opencode.d.ts.map +1 -1
- package/dist/infra/providers/opencode.js +19 -8
- package/dist/infra/providers/opencode.js.map +1 -1
- package/dist/infra/providers/types.d.ts +1 -1
- package/dist/infra/providers/types.d.ts.map +1 -1
- package/dist/infra/task/clone-exec.d.ts.map +1 -1
- package/dist/infra/task/clone-exec.js +31 -0
- package/dist/infra/task/clone-exec.js.map +1 -1
- package/dist/infra/task/enqueueService.d.ts +115 -0
- package/dist/infra/task/enqueueService.d.ts.map +1 -0
- package/dist/infra/task/enqueueService.js +191 -0
- package/dist/infra/task/enqueueService.js.map +1 -0
- package/dist/infra/task/enqueuedTaskFile.d.ts +6 -0
- package/dist/infra/task/enqueuedTaskFile.d.ts.map +1 -0
- package/dist/infra/task/enqueuedTaskFile.js +55 -0
- package/dist/infra/task/enqueuedTaskFile.js.map +1 -0
- package/dist/infra/task/issueTask.d.ts +20 -0
- package/dist/infra/task/issueTask.d.ts.map +1 -0
- package/dist/{features/tasks/add → infra/task}/issueTask.js +61 -43
- package/dist/infra/task/issueTask.js.map +1 -0
- package/dist/infra/task/listSerializer.d.ts +1 -0
- package/dist/infra/task/listSerializer.d.ts.map +1 -1
- package/dist/infra/task/listSerializer.js +1 -0
- package/dist/infra/task/listSerializer.js.map +1 -1
- package/dist/infra/task/mapper.d.ts.map +1 -1
- package/dist/infra/task/mapper.js +1 -0
- package/dist/infra/task/mapper.js.map +1 -1
- package/dist/infra/task/taskExecutionSchemas.d.ts +3 -0
- package/dist/infra/task/taskExecutionSchemas.d.ts.map +1 -1
- package/dist/infra/task/taskExecutionSchemas.js +4 -2
- package/dist/infra/task/taskExecutionSchemas.js.map +1 -1
- package/dist/infra/task/taskRecordSchemas.d.ts +2 -0
- package/dist/infra/task/taskRecordSchemas.d.ts.map +1 -1
- package/dist/infra/workflow/system/DefaultSystemStepServices.d.ts.map +1 -1
- package/dist/infra/workflow/system/DefaultSystemStepServices.js +7 -7
- package/dist/infra/workflow/system/DefaultSystemStepServices.js.map +1 -1
- package/dist/infra/workflow/system/system-enqueue-effect.d.ts.map +1 -1
- package/dist/infra/workflow/system/system-enqueue-effect.js +78 -20
- package/dist/infra/workflow/system/system-enqueue-effect.js.map +1 -1
- package/dist/infra/workflow/system/system-git-context.d.ts +6 -6
- package/dist/infra/workflow/system/system-git-context.d.ts.map +1 -1
- package/dist/infra/workflow/system/system-git-context.js +13 -13
- package/dist/infra/workflow/system/system-git-context.js.map +1 -1
- package/dist/infra/workflow/system/system-issue-input-resolver.d.ts +3 -3
- package/dist/infra/workflow/system/system-issue-input-resolver.d.ts.map +1 -1
- package/dist/infra/workflow/system/system-issue-input-resolver.js +8 -8
- package/dist/infra/workflow/system/system-issue-input-resolver.js.map +1 -1
- package/dist/infra/workflow/system/system-pr-effects.d.ts.map +1 -1
- package/dist/infra/workflow/system/system-pr-effects.js +6 -3
- package/dist/infra/workflow/system/system-pr-effects.js.map +1 -1
- package/dist/infra/workflow/system/system-pr-input-resolver.d.ts +3 -3
- package/dist/infra/workflow/system/system-pr-input-resolver.d.ts.map +1 -1
- package/dist/infra/workflow/system/system-pr-input-resolver.js +8 -8
- package/dist/infra/workflow/system/system-pr-input-resolver.js.map +1 -1
- package/dist/infra/workflow/system/system-sync-effects.js +2 -2
- package/dist/infra/workflow/system/system-sync-effects.js.map +1 -1
- package/dist/shared/constants.d.ts +1 -0
- package/dist/shared/constants.d.ts.map +1 -1
- package/dist/shared/constants.js +1 -0
- package/dist/shared/constants.js.map +1 -1
- package/dist/shared/i18n/labels_en.yaml +115 -0
- package/dist/shared/i18n/labels_ja.yaml +115 -0
- package/dist/shared/package-info.d.ts +2 -0
- package/dist/shared/package-info.d.ts.map +1 -0
- package/dist/shared/package-info.js +5 -0
- package/dist/shared/package-info.js.map +1 -0
- package/dist/shared/prompts/en/exec_assistant_clarify.md +14 -0
- package/dist/shared/prompts/en/exec_assistant_instruct.md +14 -0
- package/dist/shared/prompts/en/exec_assistant_summary.md +12 -0
- package/dist/shared/prompts/en/exec_facet_create.md +8 -0
- package/dist/shared/prompts/en/exec_facet_edit.md +8 -0
- package/dist/shared/prompts/en/finding_manager_instruction.md +35 -0
- package/dist/shared/prompts/en/opencode_agent_prompt.md +52 -0
- package/dist/shared/prompts/en/opencode_report_agent_prompt.md +8 -0
- package/dist/shared/prompts/en/opencode_review_agent_prompt.md +32 -0
- package/dist/shared/prompts/en/perform_phase1_message.md +1 -4
- package/dist/shared/prompts/en/perform_phase2_message.md +1 -0
- package/dist/shared/prompts/en/perform_phase3_message.md +1 -0
- package/dist/shared/prompts/en/provider_runtime_system_prompt.md +1 -0
- package/dist/shared/prompts/en/score_slug_system_prompt.md +1 -0
- package/dist/shared/prompts/en/score_slug_user_prompt.md +1 -0
- package/dist/shared/prompts/en/score_summary_system_prompt.md +1 -0
- package/dist/shared/prompts/en/source_context_section_guidance.md +6 -0
- package/dist/shared/prompts/en/source_context_system_guard.md +8 -0
- package/dist/shared/prompts/en/structured_json_schema_instruction.md +12 -0
- package/dist/shared/prompts/en/structured_json_step_instruction.md +12 -0
- package/dist/shared/prompts/en/sync_conflict_resolver_message.md +1 -0
- package/dist/shared/prompts/en/sync_conflict_resolver_system_prompt.md +1 -0
- package/dist/shared/prompts/index.d.ts.map +1 -1
- package/dist/shared/prompts/index.js +3 -1
- package/dist/shared/prompts/index.js.map +1 -1
- package/dist/shared/prompts/ja/exec_assistant_clarify.md +14 -0
- package/dist/shared/prompts/ja/exec_assistant_instruct.md +14 -0
- package/dist/shared/prompts/ja/exec_assistant_summary.md +12 -0
- package/dist/shared/prompts/ja/exec_facet_create.md +8 -0
- package/dist/shared/prompts/ja/exec_facet_edit.md +8 -0
- package/dist/shared/prompts/ja/finding_manager_instruction.md +35 -0
- package/dist/shared/prompts/ja/opencode_agent_prompt.md +52 -0
- package/dist/shared/prompts/ja/opencode_report_agent_prompt.md +8 -0
- package/dist/shared/prompts/ja/opencode_review_agent_prompt.md +32 -0
- package/dist/shared/prompts/ja/perform_phase1_message.md +1 -4
- package/dist/shared/prompts/ja/perform_phase2_message.md +1 -0
- package/dist/shared/prompts/ja/perform_phase3_message.md +1 -0
- package/dist/shared/prompts/ja/provider_runtime_system_prompt.md +1 -0
- package/dist/shared/prompts/ja/score_slug_system_prompt.md +1 -0
- package/dist/shared/prompts/ja/score_slug_user_prompt.md +1 -0
- package/dist/shared/prompts/ja/score_summary_system_prompt.md +1 -0
- package/dist/shared/prompts/ja/source_context_section_guidance.md +6 -0
- package/dist/shared/prompts/ja/source_context_system_guard.md +8 -0
- package/dist/shared/prompts/ja/structured_json_schema_instruction.md +12 -0
- package/dist/shared/prompts/ja/structured_json_step_instruction.md +12 -0
- package/dist/shared/prompts/ja/sync_conflict_resolver_message.md +1 -0
- package/dist/shared/prompts/ja/sync_conflict_resolver_system_prompt.md +1 -0
- package/dist/shared/utils/entrypoint.d.ts +2 -0
- package/dist/shared/utils/entrypoint.d.ts.map +1 -0
- package/dist/shared/utils/entrypoint.js +15 -0
- package/dist/shared/utils/entrypoint.js.map +1 -0
- package/dist/shared/utils/index.d.ts +1 -0
- package/dist/shared/utils/index.d.ts.map +1 -1
- package/dist/shared/utils/index.js +1 -0
- package/dist/shared/utils/index.js.map +1 -1
- package/dist/shared/utils/pathBoundary.d.ts +6 -0
- package/dist/shared/utils/pathBoundary.d.ts.map +1 -1
- package/dist/shared/utils/pathBoundary.js +38 -1
- package/dist/shared/utils/pathBoundary.js.map +1 -1
- package/dist/shared/utils/safeExternalErrorMessage.d.ts +2 -0
- package/dist/shared/utils/safeExternalErrorMessage.d.ts.map +1 -0
- package/dist/shared/utils/safeExternalErrorMessage.js +22 -0
- package/dist/shared/utils/safeExternalErrorMessage.js.map +1 -0
- package/package.json +29 -10
- package/builtins/en/facets/instructions/review-pure.md +0 -9
- package/builtins/en/facets/personas/pure-reviewer.md +0 -22
- package/builtins/en/provider-options/review-web.yaml +0 -14
- package/builtins/ja/facets/instructions/review-pure.md +0 -9
- package/builtins/ja/facets/personas/pure-reviewer.md +0 -22
- package/builtins/ja/provider-options/review-web.yaml +0 -14
- package/dist/core/workflow/claude-effort-compatibility.d.ts +0 -3
- package/dist/core/workflow/claude-effort-compatibility.d.ts.map +0 -1
- package/dist/core/workflow/claude-effort-compatibility.js +0 -28
- package/dist/core/workflow/claude-effort-compatibility.js.map +0 -1
- package/dist/core/workflow/provider-model-compatibility.d.ts +0 -7
- package/dist/core/workflow/provider-model-compatibility.d.ts.map +0 -1
- package/dist/core/workflow/provider-model-compatibility.js +0 -20
- package/dist/core/workflow/provider-model-compatibility.js.map +0 -1
- package/dist/features/interactive/policyPrompt.d.ts +0 -2
- package/dist/features/interactive/policyPrompt.d.ts.map +0 -1
- package/dist/features/interactive/policyPrompt.js +0 -18
- package/dist/features/interactive/policyPrompt.js.map +0 -1
- package/dist/features/tasks/add/issueTask.d.ts +0 -7
- package/dist/features/tasks/add/issueTask.d.ts.map +0 -1
- package/dist/features/tasks/add/issueTask.js.map +0 -1
- package/dist/infra/config/providerModelCompatibility.d.ts +0 -2
- package/dist/infra/config/providerModelCompatibility.d.ts.map +0 -1
- package/dist/infra/config/providerModelCompatibility.js +0 -2
- package/dist/infra/config/providerModelCompatibility.js.map +0 -1
- package/dist/shared/prompts/en/score_interactive_policy.md +0 -83
- package/dist/shared/prompts/ja/score_interactive_policy.md +0 -83
|
@@ -1,29 +1,125 @@
|
|
|
1
1
|
# CQRS+ES Knowledge
|
|
2
2
|
|
|
3
|
+
## CQRS+ES Adoption Decision
|
|
4
|
+
|
|
5
|
+
CQRS+ES is a design in which state changes are stored as domain events, and current state and Read Models are derived from them. Even when the backend as a whole or the workflow handles CQRS+ES, not every new feature needs to be implemented with event sourcing.
|
|
6
|
+
|
|
7
|
+
| Criteria | Judgment |
|
|
8
|
+
|----------|----------|
|
|
9
|
+
| User request, design material, or existing boundaries explicitly require CQRS+ES | Adopt CQRS+ES |
|
|
10
|
+
| State transitions, lifecycle, and business invariants are central to the feature | Consider CQRS+ES |
|
|
11
|
+
| Change events trigger other Aggregates, Sagas, or downstream processes | Consider CQRS+ES |
|
|
12
|
+
| Restoring state at a past point, event replay, or audit evidence itself is a requirement | Consider CQRS+ES |
|
|
13
|
+
| Read Models need to be projected asynchronously for multiple uses | Consider CQRS+ES |
|
|
14
|
+
| The feature is complete with current-value reference and updates only | Prefer CRUD |
|
|
15
|
+
| Security settings, feature flags, allowlists, thresholds, or similar values require immediate reflection | Prefer CRUD |
|
|
16
|
+
| There is no domain vocabulary beyond "create/update/delete" | Prefer CRUD |
|
|
17
|
+
| The task is merely being implemented in a CQRS+ES workflow | Do not use as adoption rationale |
|
|
18
|
+
| Adding CQRS+ES requirements to a task specification when they were absent from the original task | REJECT |
|
|
19
|
+
|
|
20
|
+
Adopt CQRS+ES based on requirements. An existing system containing CQRS+ES can justify aligning dependencies and boundaries, but it does not justify event-sourcing simple settings tables.
|
|
21
|
+
|
|
22
|
+
### Requirement Transformation
|
|
23
|
+
|
|
24
|
+
If the original task or user request states only CRUD-equivalent business requirements, do not add "commands, events, and projections" as new requirements in the task specification. If it is unclear whether CQRS+ES is needed, state the adoption rationale or leave it as an open question.
|
|
25
|
+
|
|
26
|
+
| Original Request | How to Convert into a Specification |
|
|
27
|
+
|------------------|-------------------------------------|
|
|
28
|
+
| "Manage allowed IPs per facility" | Treat as CRUD-managed settings. The domain vocabulary is only "add/delete" and has no business rules |
|
|
29
|
+
| "Manage order approval, cancellation, and returns, and trigger billing or inventory depending on state" | Candidate for CQRS+ES. Complex state transitions and business invariants, with multiple Aggregates interacting |
|
|
30
|
+
| "For insurance contract changes, review rules differ by change type, and past assessment history affects future decisions" | Candidate for CQRS+ES. Business rules are complex and change over time; history itself is input to business decisions |
|
|
31
|
+
| "Show who changed what and when on the screen" | Check whether CRUD + audit logs is enough. If the requirement is only displaying change history, audit columns are often sufficient |
|
|
32
|
+
| "Toggle notification settings on/off" | Treat as CRUD-managed settings. It only references and updates current values |
|
|
33
|
+
|
|
34
|
+
CQRS+ES is most valuable in complex business domains, such as finance, insurance, or healthcare, where business rules are complex and change over time. Simple audit requirements or technical asynchronous processing alone are not sufficient conditions for CQRS+ES. The decision axis is business-logic complexity.
|
|
35
|
+
|
|
3
36
|
## Aggregate Design
|
|
4
37
|
|
|
5
38
|
Aggregates hold only fields necessary for decision-making.
|
|
6
39
|
|
|
7
|
-
Command Model (Aggregate) role is to "receive commands, make decisions, and emit events". Query data
|
|
40
|
+
The Command Model (Aggregate) role is to "receive commands, make decisions, and emit events". Query data belongs to the Read Model (Projection).
|
|
8
41
|
|
|
9
|
-
"Necessary for decision" means:
|
|
10
|
-
- Used in `if
|
|
11
|
-
- Field
|
|
42
|
+
"Necessary for decision-making" means:
|
|
43
|
+
- Used in `if` / `require` conditional branches
|
|
44
|
+
- Field values are referenced by instance methods when emitting events
|
|
12
45
|
|
|
13
46
|
| Criteria | Judgment |
|
|
14
47
|
|----------|----------|
|
|
15
48
|
| Aggregate spans multiple transaction boundaries | REJECT |
|
|
16
|
-
| Direct references between Aggregates
|
|
49
|
+
| Direct references between Aggregates, not ID references | REJECT |
|
|
17
50
|
| Aggregate exceeds 100 lines | Consider splitting |
|
|
18
|
-
| Business invariants exist outside Aggregate | REJECT |
|
|
51
|
+
| Business invariants exist outside the Aggregate | REJECT |
|
|
19
52
|
| Holding fields not used for decisions | REJECT |
|
|
53
|
+
| Branching state transitions with origin metadata such as `source` / `input` / `origin` / `channel` / `type` | REJECT by default |
|
|
54
|
+
| Rejecting, only for a specific input source, a state allowed by the existing Aggregate's normal lifecycle | REJECT |
|
|
55
|
+
|
|
56
|
+
Being used in an `if` / `require` branch is not enough to justify keeping a field in Aggregate state. First verify that the branch or validation is an essential invariant of the whole Aggregate.
|
|
57
|
+
|
|
58
|
+
### Origin Metadata and Invariants
|
|
59
|
+
|
|
60
|
+
Origin metadata such as input source, channel, producer, or integration source can be needed for display, search, audit, or integration tracing. That need alone does not justify restoring it as Aggregate state.
|
|
61
|
+
|
|
62
|
+
| Criteria | Judgment |
|
|
63
|
+
|----------|----------|
|
|
64
|
+
| Origin metadata is needed only for display, search, audit, or integration tracing | Keep it in the Event payload or Read Model |
|
|
65
|
+
| A branch based on origin metadata creates constraints that differ from the existing Aggregate's normal lifecycle | REJECT |
|
|
66
|
+
| A field that is normally optional becomes required only for one input source | REJECT |
|
|
67
|
+
| Invariants truly differ by input source | Consider a separate Aggregate, Command, or UseCase boundary |
|
|
68
|
+
| Adding origin metadata to Aggregate state only to support a `require` | REJECT |
|
|
69
|
+
|
|
70
|
+
```kotlin
|
|
71
|
+
// NG - Origin metadata narrows the normal lifecycle of an existing Aggregate
|
|
72
|
+
data class Note(
|
|
73
|
+
val noteId: String,
|
|
74
|
+
val sourceType: SourceType?,
|
|
75
|
+
val targetIds: List<String>,
|
|
76
|
+
) {
|
|
77
|
+
fun update(text: String, targetIds: List<String>): NoteUpdatedEvent {
|
|
78
|
+
if (sourceType == SourceType.EXTERNAL_IMPORT) {
|
|
79
|
+
require(targetIds.isNotEmpty())
|
|
80
|
+
}
|
|
81
|
+
return NoteUpdatedEvent(noteId, text, targetIds)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// OK - Track origin in events/read models and align Aggregate invariants with the normal lifecycle
|
|
86
|
+
data class Note(
|
|
87
|
+
val noteId: String,
|
|
88
|
+
val confirmed: Boolean,
|
|
89
|
+
) {
|
|
90
|
+
fun update(text: String, targetIds: List<String>): NoteUpdatedEvent {
|
|
91
|
+
check(!confirmed)
|
|
92
|
+
return NoteUpdatedEvent(noteId, text, targetIds)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
data class NoteCreatedEvent(
|
|
97
|
+
val noteId: String,
|
|
98
|
+
val text: String,
|
|
99
|
+
val targetIds: List<String>,
|
|
100
|
+
val sourceType: SourceType?, // Origin fact used by projections or audit
|
|
101
|
+
)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Existing Lifecycle Priority
|
|
105
|
+
|
|
106
|
+
When integrating a new input flow into an existing Aggregate, prefer the existing normal lifecycle. Do not add input-source-specific commands, wrappers, services, or deletion paths merely because the input source differs.
|
|
107
|
+
|
|
108
|
+
| Criteria | Judgment |
|
|
109
|
+
|----------|----------|
|
|
110
|
+
| The existing Aggregate's normal command / event can represent the same fact | Use the existing lifecycle |
|
|
111
|
+
| An input-source-specific wrapper only thinly delegates to a normal command | REJECT |
|
|
112
|
+
| An input-source-specific command adds stricter required fields than the normal lifecycle | REJECT |
|
|
113
|
+
| Existing Aggregate deletion or update events can trigger derived processing | Separate into EventHandler |
|
|
114
|
+
| Display or search fields needed only by a specialized flow | Keep them in the Read Model |
|
|
115
|
+
| State transitions or invariants truly differ by input source | Consider a separate Aggregate / bounded context |
|
|
20
116
|
|
|
21
117
|
Good Aggregate:
|
|
22
118
|
```kotlin
|
|
23
119
|
// Only fields necessary for decisions
|
|
24
120
|
data class Order(
|
|
25
121
|
val orderId: String, // Used when emitting events
|
|
26
|
-
val status: OrderStatus // Used for state
|
|
122
|
+
val status: OrderStatus // Used for state checks
|
|
27
123
|
) {
|
|
28
124
|
fun confirm(confirmedBy: String): OrderConfirmedEvent {
|
|
29
125
|
require(status == OrderStatus.PENDING) { "Cannot confirm in this state" }
|
|
@@ -38,15 +134,15 @@ data class Order(
|
|
|
38
134
|
// Holding fields not used for decisions (NG)
|
|
39
135
|
data class Order(
|
|
40
136
|
val orderId: String,
|
|
41
|
-
val customerId: String,
|
|
137
|
+
val customerId: String, // Not used for decisions
|
|
42
138
|
val shippingAddress: Address, // Not used for decisions
|
|
43
139
|
val status: OrderStatus
|
|
44
140
|
)
|
|
45
141
|
```
|
|
46
142
|
|
|
47
|
-
Aggregates with no additional operations have ID
|
|
143
|
+
Aggregates with no additional operations have only an ID:
|
|
48
144
|
```kotlin
|
|
49
|
-
//
|
|
145
|
+
// Creation only, no additional operations
|
|
50
146
|
data class Notification(val notificationId: String) {
|
|
51
147
|
companion object {
|
|
52
148
|
fun create(customerId: String, message: String): NotificationCreatedEvent {
|
|
@@ -60,61 +156,182 @@ data class Notification(val notificationId: String) {
|
|
|
60
156
|
}
|
|
61
157
|
```
|
|
62
158
|
|
|
159
|
+
### Adapter Pattern: Separating Domain from Framework
|
|
160
|
+
|
|
161
|
+
Do not put framework annotations such as `@Aggregate` or `@CommandHandler` directly on domain models. Adapter classes handle framework integration, and domain models focus on business logic.
|
|
162
|
+
|
|
163
|
+
```kotlin
|
|
164
|
+
// Domain model: framework-independent business logic only
|
|
165
|
+
data class Order(
|
|
166
|
+
val orderId: String,
|
|
167
|
+
val status: OrderStatus = OrderStatus.PENDING
|
|
168
|
+
) {
|
|
169
|
+
companion object {
|
|
170
|
+
fun place(orderId: String, customerId: String): OrderPlacedEvent {
|
|
171
|
+
require(customerId.isNotBlank()) { "Customer ID cannot be blank" }
|
|
172
|
+
return OrderPlacedEvent(orderId, customerId)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
fun from(event: OrderPlacedEvent): Order {
|
|
176
|
+
return Order(orderId = event.orderId, status = OrderStatus.PENDING)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
fun confirm(confirmedBy: String): OrderConfirmedEvent {
|
|
181
|
+
require(status == OrderStatus.PENDING) { "Cannot confirm in this state" }
|
|
182
|
+
return OrderConfirmedEvent(orderId, confirmedBy, LocalDateTime.now())
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
fun apply(event: OrderEvent): Order = when (event) {
|
|
186
|
+
is OrderPlacedEvent -> from(event)
|
|
187
|
+
is OrderConfirmedEvent -> copy(status = OrderStatus.CONFIRMED)
|
|
188
|
+
is OrderCancelledEvent -> copy(status = OrderStatus.CANCELLED)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Adapter: framework integration. Domain call -> event publication bridge
|
|
193
|
+
@Aggregate
|
|
194
|
+
class OrderAggregateAdapter() {
|
|
195
|
+
private var order: Order? = null
|
|
196
|
+
|
|
197
|
+
@AggregateIdentifier
|
|
198
|
+
fun orderId(): String? = order?.orderId
|
|
199
|
+
|
|
200
|
+
@CommandHandler
|
|
201
|
+
constructor(command: PlaceOrderCommand) : this() {
|
|
202
|
+
val event = Order.place(command.orderId, command.customerId)
|
|
203
|
+
AggregateLifecycle.apply(event)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
@CommandHandler
|
|
207
|
+
fun handle(command: ConfirmOrderCommand) {
|
|
208
|
+
val event = order!!.confirm(command.confirmedBy)
|
|
209
|
+
AggregateLifecycle.apply(event)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
@EventSourcingHandler
|
|
213
|
+
fun on(event: OrderEvent) {
|
|
214
|
+
this.order = when (event) {
|
|
215
|
+
is OrderPlacedEvent -> Order.from(event)
|
|
216
|
+
else -> order?.apply(event)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Benefits of separation:
|
|
223
|
+
- Domain models can be unit-tested without a framework
|
|
224
|
+
- Domain models do not need to change when the framework changes
|
|
225
|
+
- Adapters are boilerplate for receiving commands, calling the domain, and publishing events
|
|
226
|
+
|
|
227
|
+
### apply/from Pattern: Event Replay
|
|
228
|
+
|
|
229
|
+
A pattern in which a domain model rebuilds its own state from events.
|
|
230
|
+
|
|
231
|
+
- `from(event)`: factory that builds initial state from a creation event
|
|
232
|
+
- `apply(event)`: returns new state from an event, using immutable updates with `copy()`
|
|
233
|
+
- `when` expressions plus sealed interfaces let the compiler guarantee exhaustiveness over event types
|
|
234
|
+
|
|
235
|
+
```kotlin
|
|
236
|
+
fun apply(event: OrderEvent): Order = when (event) {
|
|
237
|
+
is OrderPlacedEvent -> from(event)
|
|
238
|
+
is OrderConfirmedEvent -> copy(status = OrderStatus.CONFIRMED)
|
|
239
|
+
is OrderShippedEvent -> copy(status = OrderStatus.SHIPPED)
|
|
240
|
+
// Because the interface is sealed, adding an event type without handling it is a compile error
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
| Criteria | Judgment |
|
|
245
|
+
|----------|----------|
|
|
246
|
+
| Business logic such as validation inside `apply` | REJECT. `apply` is state restoration only |
|
|
247
|
+
| `apply` has side effects such as DB operations or event emission | REJECT |
|
|
248
|
+
| `apply` throws exceptions | REJECT. Replay failures are not acceptable |
|
|
249
|
+
|
|
63
250
|
## Event Design
|
|
64
251
|
|
|
65
252
|
| Criteria | Judgment |
|
|
66
253
|
|----------|----------|
|
|
67
|
-
| Event not in past tense
|
|
254
|
+
| Event is not in past tense, such as Created -> Create | REJECT |
|
|
68
255
|
| Event contains logic | REJECT |
|
|
69
|
-
| Event contains internal state of
|
|
70
|
-
| Event schema not
|
|
71
|
-
| CRUD-style events
|
|
256
|
+
| Event contains internal state of another Aggregate | REJECT |
|
|
257
|
+
| Event schema is not versioned | Warning |
|
|
258
|
+
| CRUD-style events such as Updated or Deleted | Needs review |
|
|
72
259
|
|
|
73
|
-
Good
|
|
260
|
+
Good events:
|
|
74
261
|
```kotlin
|
|
75
|
-
// Good:
|
|
262
|
+
// Good: domain intent is clear
|
|
76
263
|
OrderPlaced, PaymentReceived, ItemShipped
|
|
77
264
|
|
|
78
265
|
// Bad: CRUD style
|
|
79
266
|
OrderUpdated, OrderDeleted
|
|
80
267
|
```
|
|
81
268
|
|
|
82
|
-
Event
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
269
|
+
### Event Type Hierarchy with sealed interface
|
|
270
|
+
|
|
271
|
+
Aggregate events should use a sealed interface type hierarchy. The Aggregate root ID should be required as a common field, enabling exhaustive `when` checks.
|
|
272
|
+
|
|
273
|
+
```kotlin
|
|
274
|
+
sealed interface OrderEvent {
|
|
275
|
+
val orderId: String // Required on every event
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
data class OrderPlacedEvent(
|
|
279
|
+
override val orderId: String,
|
|
280
|
+
val customerId: String
|
|
281
|
+
) : OrderEvent
|
|
282
|
+
|
|
283
|
+
data class OrderConfirmedEvent(
|
|
284
|
+
override val orderId: String,
|
|
285
|
+
val approvalInfo: ApprovalInfo
|
|
286
|
+
) : OrderEvent
|
|
287
|
+
|
|
288
|
+
data class OrderCancelledEvent(
|
|
289
|
+
override val orderId: String,
|
|
290
|
+
val cancellationInfo: CancellationInfo
|
|
291
|
+
) : OrderEvent
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Benefits:
|
|
295
|
+
- A `when (event)` expression must list every event type, otherwise compilation fails. This is especially important in `apply`
|
|
296
|
+
- The compiler guarantees that the Aggregate root ID exists
|
|
297
|
+
- Event-handler branching by type is safer
|
|
298
|
+
|
|
299
|
+
Event granularity:
|
|
300
|
+
- Too fine: `OrderFieldChanged` -> domain intent is unclear
|
|
301
|
+
- Appropriate: `ShippingAddressChanged` -> intent is clear
|
|
302
|
+
- Too coarse: `OrderModified` -> unclear what changed
|
|
86
303
|
|
|
87
304
|
## Event Evolution
|
|
88
305
|
|
|
89
|
-
Events are persisted contracts.
|
|
306
|
+
Events are persisted contracts. If the current event type changes, old events must still be replayable. Translating old events belongs in the upcaster / migration layer at the event-store restoration boundary, not in event classes or domain logic.
|
|
90
307
|
|
|
91
308
|
| Criteria | Judgment |
|
|
92
309
|
|----------|----------|
|
|
93
|
-
| Persisted event type or fields changed
|
|
94
|
-
| Current event type keeps aliases or compatibility-only properties
|
|
95
|
-
| Aggregate or apply directly interprets old event shapes | REJECT. Convert to current events before replay |
|
|
96
|
-
| Event
|
|
97
|
-
| Upcaster converts old payloads
|
|
98
|
-
| Tests verify old payloads
|
|
310
|
+
| Persisted event type or fields changed without a translation path | REJECT |
|
|
311
|
+
| Current event type keeps old field aliases or compatibility-only properties | REJECT. Keep history compatibility in upcasters |
|
|
312
|
+
| Aggregate or `apply` directly interprets old event shapes | REJECT. Convert to current events before replay |
|
|
313
|
+
| Event adds "previous value" only for compatibility | REJECT. Events represent the fact after it happened |
|
|
314
|
+
| Upcaster converts old payloads into current event meaning | OK |
|
|
315
|
+
| Tests verify conversion from old payloads to current events | OK |
|
|
99
316
|
|
|
100
|
-
|
|
317
|
+
Responsibilities in event evolution:
|
|
101
318
|
|
|
102
|
-
| Responsibility |
|
|
103
|
-
|
|
319
|
+
| Responsibility | Location |
|
|
320
|
+
|----------------|----------|
|
|
104
321
|
| Current event meaning and fields | Event type |
|
|
105
|
-
|
|
|
106
|
-
| State restoration
|
|
107
|
-
| Guarantee that old events can
|
|
322
|
+
| Reading old payloads | Upcaster / migration layer |
|
|
323
|
+
| State restoration from event replay | Aggregate `apply` |
|
|
324
|
+
| Guarantee that old events can be converted to current events | Upcaster tests |
|
|
108
325
|
|
|
109
326
|
```kotlin
|
|
110
|
-
// NG -
|
|
327
|
+
// NG - Mixing old field compatibility into the current event type
|
|
111
328
|
data class OrderAssignedEvent(
|
|
112
329
|
val orderId: String,
|
|
113
330
|
@JsonAlias("assigneeId")
|
|
114
331
|
val assigneeIds: List<String>
|
|
115
332
|
)
|
|
116
333
|
|
|
117
|
-
// OK -
|
|
334
|
+
// OK - Current event type represents only the current contract
|
|
118
335
|
data class OrderAssignedEvent(
|
|
119
336
|
val orderId: String,
|
|
120
337
|
val assigneeIds: List<String>
|
|
@@ -122,7 +339,7 @@ data class OrderAssignedEvent(
|
|
|
122
339
|
```
|
|
123
340
|
|
|
124
341
|
```kotlin
|
|
125
|
-
// OK -
|
|
342
|
+
// OK - Convert old payloads to current payloads in an upcaster
|
|
126
343
|
when (eventType) {
|
|
127
344
|
OrderAssignedEvent::class.java.typeName -> {
|
|
128
345
|
event.moveTextFieldToArray("assigneeId", "assigneeIds")
|
|
@@ -130,135 +347,312 @@ when (eventType) {
|
|
|
130
347
|
}
|
|
131
348
|
```
|
|
132
349
|
|
|
133
|
-
Whether to keep old event
|
|
350
|
+
Whether to keep old event types in application code depends on the framework and operational policy. In general, it is better not to treat old types as normal domain events. Instead, test the old serialized type and payload as input contracts for the upcaster, keeping the current model clean.
|
|
351
|
+
|
|
352
|
+
### Migration Scope Decomposition
|
|
353
|
+
|
|
354
|
+
CQRS+ES migration must distinguish DB schema migration, data migration, event upcasters, Read Model rebuilds, and API compatibility work. Do not treat "migrate / do not migrate" as a single decision.
|
|
355
|
+
|
|
356
|
+
| Criteria | Judgment |
|
|
357
|
+
|----------|----------|
|
|
358
|
+
| The user says migration is unnecessary, but the migration type is not decomposed | REJECT |
|
|
359
|
+
| DB schema migration or data migration is added while the scope is unclear | REJECT |
|
|
360
|
+
| An upcaster is added even though no persisted old events exist | REJECT |
|
|
361
|
+
| Data migration is created even though the Read Model can be rebuilt from events | REJECT. Check whether rebuild is enough |
|
|
362
|
+
| Add an upcaster only when event payload compatibility is required | OK |
|
|
363
|
+
| Treat API compatibility and event compatibility as separate decisions | OK |
|
|
134
364
|
|
|
135
365
|
## Command Handlers
|
|
136
366
|
|
|
137
367
|
| Criteria | Judgment |
|
|
138
368
|
|----------|----------|
|
|
139
|
-
| Handler directly manipulates DB | REJECT |
|
|
140
|
-
| Handler
|
|
141
|
-
|
|
|
369
|
+
| Handler directly manipulates the DB | REJECT |
|
|
370
|
+
| Handler changes multiple Aggregates | REJECT |
|
|
371
|
+
| Command has no validation | REJECT |
|
|
142
372
|
| Handler executes queries to make decisions | Needs review |
|
|
143
373
|
|
|
144
|
-
Good
|
|
374
|
+
Good command handler:
|
|
145
375
|
```
|
|
146
|
-
1. Receive command
|
|
147
|
-
2. Restore Aggregate from event store
|
|
148
|
-
3. Apply command to Aggregate
|
|
149
|
-
4.
|
|
376
|
+
1. Receive a command
|
|
377
|
+
2. Restore the Aggregate from the event store
|
|
378
|
+
3. Apply the command to the Aggregate
|
|
379
|
+
4. Store the emitted events
|
|
150
380
|
```
|
|
151
381
|
|
|
382
|
+
### Multi-layer Validation
|
|
383
|
+
|
|
384
|
+
Validation responsibilities differ by layer. Do not collect every validation in one place.
|
|
385
|
+
|
|
386
|
+
| Layer | Responsibility | Means | Example |
|
|
387
|
+
|-------|----------------|-------|---------|
|
|
388
|
+
| API layer | Structural validation | `@NotBlank`, `init` block | Required fields, type, format |
|
|
389
|
+
| UseCase layer | Business-rule validation | Querying Read Models | Duplicate checks, existence of prerequisites |
|
|
390
|
+
| Domain layer | State-transition invariants | `require` | "Can only approve when PENDING" |
|
|
391
|
+
|
|
152
392
|
### Aggregate Decision Boundary
|
|
153
393
|
|
|
154
|
-
Aggregates make decisions only from state
|
|
394
|
+
Aggregates make decisions only from state restored from their own event history and facts explicitly supplied as commands. They are not the place to interpret, normalize, or verify ownership of boundary-originated input.
|
|
395
|
+
|
|
396
|
+
Validation inside an Aggregate must be limited to state that can be reproduced solely by event replay. Other validation should be resolved at the boundary before command dispatch, and resolved facts should be passed to the Aggregate.
|
|
397
|
+
|
|
398
|
+
| Decision Target | Location |
|
|
399
|
+
|-----------------|----------|
|
|
400
|
+
| Whether the operation is possible in the current state | Aggregate |
|
|
401
|
+
| Whether command executor matches Aggregate owner | Aggregate |
|
|
402
|
+
| HTTP/API input shape is valid | API layer |
|
|
403
|
+
| Interpreting formats of external identifiers such as object keys, URLs, paths | UseCase layer or boundary policy/verifier |
|
|
404
|
+
| External identifier belongs to the current user/tenant | UseCase layer or boundary policy/verifier |
|
|
405
|
+
| Checking another Aggregate's Read Model or external facts | UseCase layer |
|
|
406
|
+
| State-transition decisions based on the same Aggregate's current state | Aggregate |
|
|
407
|
+
| Entity exists in an external service | Application-layer external-service integration |
|
|
408
|
+
|
|
409
|
+
Example: in an upload-completion command, the Aggregate decides whether the session owner matches the executor and whether the current state allows completion. The string shape of the object key and whether that key belongs to the current user/tenant area are verified in the UseCase layer before the command is sent.
|
|
410
|
+
|
|
411
|
+
### Command Intent and Pre-querying
|
|
412
|
+
|
|
413
|
+
A Command represents what the user or external process intends to do, not which command should be selected after reading the current state. Decisions such as Add / Update / Delete / Noop based on current state should be pushed into the restored Aggregate, not decided from the same Aggregate's Read Model.
|
|
414
|
+
|
|
415
|
+
| Criteria | Judgment |
|
|
416
|
+
|----------|----------|
|
|
417
|
+
| Check existence or scope of other Aggregates or external facts, then pass resolved facts to the command | OK |
|
|
418
|
+
| Read the same Aggregate's Read Model to choose the command type | REJECT |
|
|
419
|
+
| The UseCase decides "update if it exists, add if it does not" from Query results | REJECT. Send an intent command such as Set / Attach / Upsert to the Aggregate |
|
|
420
|
+
| Aggregate or AggregateAdapter decides existence and transition validity from restored state | OK |
|
|
421
|
+
| EventHandler or UseCase suppresses duplicate commands with a pre-query even though the Aggregate could ignore them idempotently | REJECT. Protect with Aggregate state transitions |
|
|
422
|
+
|
|
423
|
+
```kotlin
|
|
424
|
+
// NG - Query result chooses the command type
|
|
425
|
+
if (readService.exists(orderId)) {
|
|
426
|
+
commandGateway.send(UpdateOrderCommand(orderId, value))
|
|
427
|
+
} else {
|
|
428
|
+
commandGateway.send(AddOrderCommand(orderId, value))
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// OK - Send an intent command; the Aggregate decides from restored state
|
|
432
|
+
commandGateway.send(SetOrderValueCommand(orderId, value))
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
```kotlin
|
|
436
|
+
// API layer: structural validation
|
|
437
|
+
data class OrderPostRequest(
|
|
438
|
+
@field:NotBlank val customerId: String,
|
|
439
|
+
@field:NotNull val items: List<OrderItemRequest>
|
|
440
|
+
) {
|
|
441
|
+
init {
|
|
442
|
+
require(items.isNotEmpty()) { "An order must have at least one item" }
|
|
443
|
+
}
|
|
444
|
+
}
|
|
155
445
|
|
|
156
|
-
|
|
446
|
+
// UseCase layer: business-rule validation by Read Model reference
|
|
447
|
+
@Service
|
|
448
|
+
class PlaceOrderUseCase(
|
|
449
|
+
private val commandGateway: CommandGateway,
|
|
450
|
+
private val customerRepository: CustomerRepository,
|
|
451
|
+
private val inventoryRepository: InventoryRepository
|
|
452
|
+
) {
|
|
453
|
+
fun execute(input: PlaceOrderInput): Mono<PlaceOrderOutput> {
|
|
454
|
+
return Mono.fromCallable {
|
|
455
|
+
// Customer existence check
|
|
456
|
+
customerRepository.findById(input.customerId)
|
|
457
|
+
?: throw CustomerNotFoundException("Customer does not exist")
|
|
458
|
+
// Inventory precheck
|
|
459
|
+
validateInventory(input.items)
|
|
460
|
+
// Command dispatch
|
|
461
|
+
val orderId = UUID.randomUUID().toString()
|
|
462
|
+
commandGateway.send<Any>(PlaceOrderCommand(orderId, input.customerId, input.items))
|
|
463
|
+
PlaceOrderOutput(orderId)
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
157
467
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
| Whether an external identifier belongs to the current user/tenant | UseCase layer or boundary-side Policy/Verifier |
|
|
165
|
-
| Checking Read Models or other Aggregate state | UseCase layer |
|
|
166
|
-
| Checking that an external resource exists | Application-layer integration with the external service |
|
|
468
|
+
// Domain layer: state-transition invariant
|
|
469
|
+
fun confirm(confirmedBy: String): OrderConfirmedEvent {
|
|
470
|
+
require(status == OrderStatus.PENDING) { "Cannot confirm in this state" }
|
|
471
|
+
return OrderConfirmedEvent(orderId, confirmedBy, LocalDateTime.now())
|
|
472
|
+
}
|
|
473
|
+
```
|
|
167
474
|
|
|
168
|
-
|
|
475
|
+
| Criteria | Judgment |
|
|
476
|
+
|----------|----------|
|
|
477
|
+
| Domain-layer validation exists in API layer | REJECT. State-transition rules belong in the domain |
|
|
478
|
+
| UseCase-layer validation exists in Controller | REJECT. Separate into UseCase layer |
|
|
479
|
+
| API-layer validation such as `@NotBlank` exists in domain | REJECT. Structural validation belongs in API layer |
|
|
169
480
|
|
|
170
|
-
## UseCase Layer
|
|
481
|
+
## UseCase Layer: Orchestration
|
|
171
482
|
|
|
172
|
-
|
|
483
|
+
Place a UseCase layer between Controller and CommandGateway. The UseCase layer gathers facts that must be resolved at the boundary and normally sends one intent command. Subsequent state changes are driven by EventHandlers for committed events.
|
|
173
484
|
|
|
174
485
|
```
|
|
175
|
-
Controller
|
|
176
|
-
|
|
177
|
-
QueryGateway / Repository (Read Model
|
|
486
|
+
Controller -> UseCase -> CommandGateway -> Aggregate
|
|
487
|
+
|
|
|
488
|
+
QueryGateway / Repository (Read Model reference)
|
|
178
489
|
```
|
|
179
490
|
|
|
180
|
-
Cases
|
|
181
|
-
- Read Model
|
|
182
|
-
-
|
|
183
|
-
-
|
|
184
|
-
- External integration or multiple command dispatches
|
|
491
|
+
Cases that need a UseCase:
|
|
492
|
+
- Checking another Aggregate's Read Model or external facts before command dispatch
|
|
493
|
+
- Running multiple validations sequentially
|
|
494
|
+
- Waiting for eventual consistency after command dispatch only for a synchronous API contract
|
|
185
495
|
|
|
186
|
-
Cases
|
|
187
|
-
-
|
|
188
|
-
-
|
|
189
|
-
-
|
|
496
|
+
Cases that do not need a UseCase:
|
|
497
|
+
- A simple operation where the Controller sends one command and is done
|
|
498
|
+
- A simple read where the Controller queries the Query side and converts to a response
|
|
499
|
+
- An operation that only checks existence/scope of an existing resource and then sends one command
|
|
190
500
|
|
|
191
501
|
| Criteria | Judgment |
|
|
192
502
|
|----------|----------|
|
|
193
503
|
| Controller directly references Repository for validation | Separate into UseCase layer |
|
|
194
|
-
| UseCase depends on HTTP
|
|
195
|
-
| UseCase directly
|
|
196
|
-
| UseCase
|
|
197
|
-
| UseCase
|
|
504
|
+
| UseCase depends on HTTP request/response | REJECT. UseCase must be protocol-independent |
|
|
505
|
+
| UseCase directly changes Aggregate internal state | REJECT. Use CommandGateway |
|
|
506
|
+
| UseCase sends multiple commands sequentially for the same state transition | REJECT. Separate into EventHandlers for committed events |
|
|
507
|
+
| UseCase queries the same Aggregate's state to choose the command type | REJECT. Push the decision into the Aggregate |
|
|
508
|
+
| UseCase validates another Aggregate or external facts and passes resolved facts to one command | OK |
|
|
509
|
+
| EventHandler receives a committed event and sends a command to another Aggregate | OK |
|
|
510
|
+
| processStore / ProcessStore / operationProcess / completeStep stores projection completion or procedural progress | REJECT. Model it with Projection and EventHandlers |
|
|
511
|
+
| There is an explicit long-running business process, retry, compensation, or user-visible progress | Consider Saga / Process Manager |
|
|
512
|
+
| UseCase only thinly delegates to another query layer or command dispatch | Consider removing |
|
|
513
|
+
|
|
514
|
+
## Event-driven Chaining
|
|
515
|
+
|
|
516
|
+
In CQRS+ES, chains of state changes start from committed events. Application Services, UseCases, and Controllers must not synchronously control the order of multiple Aggregate changes by sending commands sequentially for the same state transition.
|
|
517
|
+
|
|
518
|
+
Basic shape:
|
|
519
|
+
|
|
520
|
+
```text
|
|
521
|
+
UseCase -> Command -> Aggregate -> Event
|
|
522
|
+
|
|
|
523
|
+
EventHandler -> Command -> another Aggregate
|
|
524
|
+
|
|
|
525
|
+
Projection -> Read Model
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
| Criteria | Judgment |
|
|
529
|
+
|----------|----------|
|
|
530
|
+
| UseCase sends command B immediately after command A for the same state transition | REJECT. Let an EventHandler receive A's event and send B |
|
|
531
|
+
| Another command is sent after `sendAndWait` returns to create consistency | REJECT. Separate into event chaining |
|
|
532
|
+
| A normal event from an existing Aggregate becomes the trigger for derived processing | OK |
|
|
533
|
+
| EventHandler receives a committed event and sends an idempotent command to another Aggregate | OK |
|
|
534
|
+
| Projection update and next-command dispatch are mixed in the same handler | REJECT. Separate Projection from EventHandler |
|
|
535
|
+
| There is contention, compensation, long-running retry, or user-visible progress | Consider Saga / Process Manager |
|
|
536
|
+
| processStore is created only to remember intermediate progress | REJECT. Split responsibilities into Aggregate events, Projections, or Saga |
|
|
198
537
|
|
|
199
538
|
## Projection Design
|
|
200
539
|
|
|
201
540
|
| Criteria | Judgment |
|
|
202
541
|
|----------|----------|
|
|
203
|
-
| Projection
|
|
204
|
-
| Projection references Write
|
|
205
|
-
|
|
|
206
|
-
|
|
|
542
|
+
| Projection dispatches commands | REJECT |
|
|
543
|
+
| Projection references Write Model | REJECT |
|
|
544
|
+
| One projection supports multiple use cases | Needs review |
|
|
545
|
+
| Cannot be rebuilt | REJECT |
|
|
546
|
+
|
|
547
|
+
Good projections:
|
|
548
|
+
- Optimized for a specific read use case
|
|
549
|
+
- Rebuildable idempotently from events
|
|
550
|
+
- Completely independent from Write Model
|
|
551
|
+
|
|
552
|
+
### Distinguishing Projections from EventHandlers for Side Effects
|
|
553
|
+
|
|
554
|
+
Both use `@EventHandler`, but their responsibilities differ. Do not confuse them.
|
|
555
|
+
|
|
556
|
+
| Type | Responsibility | Does | Does Not Do |
|
|
557
|
+
|------|----------------|------|-------------|
|
|
558
|
+
| Projection | Read Model update | Save/update Entity | Dispatch commands, call external APIs |
|
|
559
|
+
| EventHandler | Side effect | Dispatch commands to other Aggregates | Update Read Models |
|
|
560
|
+
|
|
561
|
+
```kotlin
|
|
562
|
+
// Projection: Read Model update only
|
|
563
|
+
@Component
|
|
564
|
+
class OrderProjection(private val orderRepository: OrderRepository) {
|
|
565
|
+
@EventHandler
|
|
566
|
+
fun on(event: OrderPlacedEvent) {
|
|
567
|
+
val entity = OrderEntity(
|
|
568
|
+
orderId = event.orderId,
|
|
569
|
+
customerId = event.customerId,
|
|
570
|
+
status = OrderStatus.PENDING
|
|
571
|
+
)
|
|
572
|
+
orderRepository.save(entity)
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
@EventHandler
|
|
576
|
+
fun on(event: OrderConfirmedEvent) {
|
|
577
|
+
orderRepository.findById(event.orderId).ifPresent { entity ->
|
|
578
|
+
entity.status = OrderStatus.CONFIRMED
|
|
579
|
+
orderRepository.save(entity)
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
207
583
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
584
|
+
// EventHandler: side effect, command dispatch to another Aggregate
|
|
585
|
+
@Component
|
|
586
|
+
class InventoryReleaseHandler(private val commandGateway: CommandGateway) {
|
|
587
|
+
@EventHandler
|
|
588
|
+
fun on(event: OrderCancelledEvent) {
|
|
589
|
+
val command = ReleaseInventoryCommand(
|
|
590
|
+
productId = event.productId,
|
|
591
|
+
quantity = event.quantity
|
|
592
|
+
)
|
|
593
|
+
commandGateway.send<Any>(command)
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
```
|
|
212
597
|
|
|
213
|
-
|
|
598
|
+
| Criteria | Judgment |
|
|
599
|
+
|----------|----------|
|
|
600
|
+
| Projection uses CommandGateway | REJECT. Separate into EventHandler |
|
|
601
|
+
| EventHandler saves with Repository | REJECT. Separate into Projection |
|
|
602
|
+
| One class mixes Projection and EventHandler responsibilities | REJECT. Split classes |
|
|
214
603
|
|
|
215
|
-
|
|
604
|
+
### Starting External Processing
|
|
605
|
+
|
|
606
|
+
Starting external workers or asynchronous processing should be triggered by a domain event that an Aggregate has committed. An Application Service or Coordinator must not bundle command dispatch and external side effects in the same control flow for the same state transition.
|
|
216
607
|
|
|
217
608
|
| Criteria | Judgment |
|
|
218
609
|
|----------|----------|
|
|
219
|
-
| Application Service or Coordinator
|
|
220
|
-
| Aggregate emits an event that represents generation or processing start, and
|
|
221
|
-
| EventHandler
|
|
222
|
-
|
|
|
223
|
-
|
|
|
224
|
-
| Saga is used
|
|
610
|
+
| Application Service or Coordinator starts external processing immediately after command dispatch for the same state transition | REJECT. Separate into EventHandler for committed events |
|
|
611
|
+
| Aggregate emits an event that represents generation start or processing start, and EventHandler starts external processing | OK |
|
|
612
|
+
| EventHandler reports external-processing start failure back to the Aggregate with a failure command | OK |
|
|
613
|
+
| Input needed for external processing is represented by the event or stable IDs that can be reloaded | OK |
|
|
614
|
+
| External-processing input exists only in local variables during command processing | REJECT. Move to events or reloadable references |
|
|
615
|
+
| Saga is used for simple external processing with no contention or compensation | REJECT. EventHandler is enough |
|
|
225
616
|
|
|
226
|
-
## Query
|
|
617
|
+
## Query-side Design
|
|
227
618
|
|
|
228
|
-
Query side operates
|
|
619
|
+
The Query side operates as an event-driven PubSub model. Projections update Read Models with EventHandlers, and the Query side references those Read Models.
|
|
229
620
|
|
|
230
|
-
Event
|
|
621
|
+
Event delivery should be PubSub, through a message broker, to all instances. Do not rely on mechanisms that deliver only to the same instance unless delivery guarantees are confirmed.
|
|
231
622
|
|
|
232
|
-
- **Subscription Query** (
|
|
233
|
-
- **Subscribing event processor** (
|
|
623
|
+
- **Subscription Query** (for example Axon's `subscriptionQuery()`): a mechanism that returns change notifications for query results to the subscriber. Use it only when it is already adopted as infrastructure and delivery to subscribers is guaranteed. In systems based on tracking processors or trackers, do not introduce subscription query only for a feature implementation.
|
|
624
|
+
- **Subscribing event processor** (for example Axon's `SubscribingEventProcessor`): depends on direct subscription from the local event bus, so only the instance that published the event receives it. In distributed environments, projections on other instances are not updated. Configure PubSub delivery to all instances.
|
|
234
625
|
|
|
235
626
|
| Criteria | Judgment |
|
|
236
627
|
|----------|----------|
|
|
237
|
-
|
|
|
238
|
-
|
|
|
239
|
-
|
|
|
240
|
-
|
|
|
241
|
-
|
|
|
628
|
+
| Use of Subscription Query already adopted as infrastructure with confirmed delivery guarantee, such as Axon Server `subscriptionQuery()` | OK |
|
|
629
|
+
| Introducing Subscription Query only for a feature implementation | REJECT. Use the existing tracker / Read Model polling |
|
|
630
|
+
| Use of Subscription Query with unknown delivery guarantee, such as Axon `subscriptionQuery()` | REJECT. Use the existing tracker / Read Model polling |
|
|
631
|
+
| Use of Subscribing event processor, such as Axon `SubscribingEventProcessor` | REJECT. Local delivery only; other instances are not updated in distributed environments |
|
|
632
|
+
| Controller directly references Repository | REJECT. Go through UseCase layer |
|
|
633
|
+
| Query side references Command Model | REJECT |
|
|
634
|
+
| QueryHandler dispatches commands | REJECT |
|
|
242
635
|
| Query-side service or handler saves, deletes, or calls external APIs | REJECT |
|
|
243
|
-
| Command and Query
|
|
244
|
-
| Query side
|
|
636
|
+
| Command and Query are mixed in the same service | REJECT. Separate responsibilities and naming |
|
|
637
|
+
| Query side or ReadService reads Query results to choose the command type for the same Aggregate | REJECT |
|
|
638
|
+
| Query side checks existence/scope of another Aggregate or external facts, and the caller dispatches one command | OK |
|
|
245
639
|
|
|
246
640
|
### QueryHandler and ApplicationService Naming
|
|
247
641
|
|
|
248
|
-
In CQRS, the component that receives
|
|
642
|
+
In CQRS, the component that receives queries is called a QueryHandler, and the entry point that sends queries is treated as QueryGateway / QueryBus. A facade called from a Controller to coordinate read use cases should be named ApplicationService or ReadService so it is not confused with QueryHandler.
|
|
249
643
|
|
|
250
644
|
| Criteria | Judgment |
|
|
251
645
|
|----------|----------|
|
|
252
|
-
| Receives a Query,
|
|
253
|
-
| Coordinates multiple Queries, authorization
|
|
254
|
-
|
|
|
255
|
-
| QueryHandler knows HTTP
|
|
256
|
-
|
|
|
646
|
+
| Receives a Query, references Read Model, and returns a query-result type | QueryHandler |
|
|
647
|
+
| Coordinates multiple Queries, authorization boundary, paging, and DTO assembly from Controller | ApplicationService or ReadService |
|
|
648
|
+
| A class that only sends queries or coordinates reads is called QueryService | Warning. Easy to confuse with QueryHandler |
|
|
649
|
+
| QueryHandler knows HTTP request/response or Controller-specific error conversion | REJECT |
|
|
650
|
+
| Adds a simple read wrapper with no additional decision | Consider removing. Controller may call QueryGateway directly |
|
|
257
651
|
|
|
258
652
|
Types between layers:
|
|
259
|
-
- `application/query/` -
|
|
260
|
-
- `adapter/protocol/` - REST response
|
|
261
|
-
- QueryHandler returns application
|
|
653
|
+
- `application/query/` - query-result type, for example `OrderDetail`
|
|
654
|
+
- `adapter/protocol/` - REST response type, for example `OrderDetailResponse`
|
|
655
|
+
- QueryHandler returns application-layer types; Controller converts them to adapter-layer types
|
|
262
656
|
|
|
263
657
|
```kotlin
|
|
264
658
|
// application/query/OrderDetail.kt
|
|
@@ -275,14 +669,14 @@ data class OrderDetailResponse(...) {
|
|
|
275
669
|
}
|
|
276
670
|
}
|
|
277
671
|
|
|
278
|
-
// QueryHandler - returns application
|
|
672
|
+
// QueryHandler - returns application-layer type
|
|
279
673
|
@QueryHandler
|
|
280
674
|
fun handle(query: GetOrderDetailQuery): OrderDetail? {
|
|
281
675
|
val entity = repository.findById(query.id) ?: return null
|
|
282
676
|
return OrderDetail(...)
|
|
283
677
|
}
|
|
284
678
|
|
|
285
|
-
// Controller -
|
|
679
|
+
// Controller - simple reference can return synchronously
|
|
286
680
|
@GetMapping("/{id}")
|
|
287
681
|
fun getById(@PathVariable id: String): ResponseEntity<OrderDetailResponse> {
|
|
288
682
|
val detail = queryGateway.query(
|
|
@@ -296,45 +690,57 @@ fun getById(@PathVariable id: String): ResponseEntity<OrderDetailResponse> {
|
|
|
296
690
|
|
|
297
691
|
Structure:
|
|
298
692
|
```
|
|
299
|
-
Controller (adapter)
|
|
300
|
-
|
|
693
|
+
Controller (adapter) -> QueryGateway -> QueryHandler (application) -> Repository
|
|
694
|
+
| |
|
|
301
695
|
Response.from(detail) OrderDetail
|
|
302
696
|
|
|
303
697
|
Event flow (PubSub):
|
|
304
|
-
Aggregate
|
|
305
|
-
|
|
306
|
-
QueryHandler
|
|
698
|
+
Aggregate -> Event Bus -> Projection(@EventHandler) -> Repository(Read Model)
|
|
699
|
+
^
|
|
700
|
+
QueryHandler references this
|
|
307
701
|
```
|
|
308
702
|
|
|
309
|
-
###
|
|
703
|
+
### Asynchronous Callbacks and Concurrency Control
|
|
310
704
|
|
|
311
|
-
|
|
705
|
+
Design asynchronous completion callbacks assuming duplicates, delays, and ordering inversions. Protect with Aggregate state transitions and command idempotency, not Controller or single-process locks.
|
|
312
706
|
|
|
313
707
|
| Criteria | Judgment |
|
|
314
708
|
|----------|----------|
|
|
315
|
-
| Controller or application-process
|
|
316
|
-
|
|
|
317
|
-
| Aggregate verifies callback attempt
|
|
318
|
-
|
|
|
709
|
+
| Prevent duplicate callbacks with Controller or application-process locks | REJECT. Does not work across instances |
|
|
710
|
+
| Determine processing state from Aggregate state | OK |
|
|
711
|
+
| Aggregate verifies callback attempt ID or generation | OK |
|
|
712
|
+
| Idempotently ignore old or duplicate callbacks by state transition | OK |
|
|
319
713
|
| Concurrency control is duplicated across Controller, UseCase, and Aggregate | REJECT |
|
|
320
714
|
|
|
321
715
|
## Eventual Consistency
|
|
322
716
|
|
|
323
|
-
|
|
717
|
+
Wait for Projection updates after command dispatch only when there is an explicit synchronous contract to return the updated Read Model in the same API response. If the client can keep the input values or generated ID, the server should not wait; Read Model convergence is handled through normal read APIs.
|
|
324
718
|
|
|
325
719
|
| Criteria | Judgment |
|
|
326
720
|
|----------|----------|
|
|
327
|
-
|
|
|
328
|
-
|
|
|
329
|
-
|
|
|
330
|
-
|
|
|
721
|
+
| No explicit contract to return the updated Read Model in the same response | Do not wait |
|
|
722
|
+
| The client or caller can keep the command input or generated ID | Do not wait |
|
|
723
|
+
| There is infrastructure guaranteeing Projection update notification delivery to the waiting process | OK. Notification-driven waiting is acceptable |
|
|
724
|
+
| Existing infrastructure such as Subscription Query confirms update notifications reach subscribers | OK |
|
|
725
|
+
| Kafka or similar guarantees destination, redelivery, and missing-message handling operationally | OK |
|
|
726
|
+
| Subscription Query or event notification destination assumes single process/single instance, or guarantee is unknown | REJECT. Use the existing tracker / Read Model polling |
|
|
727
|
+
| `Thread.sleep` or equivalent blocks request threads while waiting for Projection updates | REJECT. Causes thread starvation under high concurrency |
|
|
728
|
+
| `delayedExecutor` / `CompletableFuture` is used to implement custom Projection-wait retry | REJECT. Use a reactive HTTP stack or the existing tracker |
|
|
729
|
+
| processStore / ProcessStore / materialStore / completeStep manages Projection update progress | REJECT. Projections should update idempotently from events |
|
|
730
|
+
| Updated state must be returned in the same HTTP response | Wait non-blockingly on a reactive HTTP stack |
|
|
731
|
+
| Same response does not need to wait | `202 Accepted` plus frontend long polling, normal polling, SSE, or WebSocket |
|
|
732
|
+
| UI expects immediate update | Frontend polling, SSE, or WebSocket. Server-side waiting only for a synchronous API contract |
|
|
733
|
+
| Consistency delay exceeds acceptable range | Reconsider architecture |
|
|
734
|
+
| Compensation transaction is undefined | Require failure-scenario review |
|
|
331
735
|
|
|
332
736
|
### Reactive Polling
|
|
333
737
|
|
|
334
|
-
|
|
738
|
+
Reactive polling is the pattern of dispatching a command and then waiting non-blockingly for Projection update completion. It does not occupy a request thread and is not a synchronous `while` loop with `Thread.sleep`.
|
|
739
|
+
|
|
740
|
+
The polling condition should be checked by re-fetching the Read Model and testing whether it has reached the expected state, not by event notifications. Re-fetch at a fixed interval until the condition is met, timeout occurs, or max attempts are reached.
|
|
335
741
|
|
|
336
742
|
```kotlin
|
|
337
|
-
// UseCase:
|
|
743
|
+
// UseCase: command dispatch -> wait for completion with polling
|
|
338
744
|
fun execute(input: PlaceOrderInput): Mono<PlaceOrderOutput> {
|
|
339
745
|
val orderId = UUID.randomUUID().toString()
|
|
340
746
|
return Mono.fromCallable { validatePreConditions(input) }
|
|
@@ -359,40 +765,56 @@ private fun pollForCompletion(orderId: String): Mono<Void> {
|
|
|
359
765
|
}
|
|
360
766
|
```
|
|
361
767
|
|
|
362
|
-
|
|
363
|
-
- Need to wait for Saga completion before returning response
|
|
364
|
-
- Need to return created resource ID after command dispatch
|
|
768
|
+
Avoid blocking waits:
|
|
365
769
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
770
|
+
```kotlin
|
|
771
|
+
// NG - Occupies request threads and causes thread starvation under load
|
|
772
|
+
while (Instant.now().isBefore(deadline)) {
|
|
773
|
+
val order = orderRepository.findById(orderId).orElse(null)
|
|
774
|
+
if (order?.status == OrderStatus.CONFIRMED) return PlaceOrderOutput(orderId)
|
|
775
|
+
Thread.sleep(100)
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// OK - If the same response must wait, put it on reactive waiting
|
|
779
|
+
return pollForCompletion(orderId).thenReturn(PlaceOrderOutput(orderId))
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
Cases where polling is appropriate:
|
|
783
|
+
- The response should not return until Saga completion
|
|
784
|
+
- The command dispatch creates a resource ID and the response needs to return it
|
|
785
|
+
|
|
786
|
+
Cases where polling is unnecessary:
|
|
787
|
+
- A simple operation where command dispatch alone completes the work and the result is not waited on
|
|
788
|
+
- The UI does not need real-time update
|
|
789
|
+
|
|
790
|
+
If the server does not wait, return `202 Accepted` with a tracking ID after accepting the command, and let the frontend use long polling or normal polling on the read API. SSE or WebSocket can also be considered if the user experience requires immediacy.
|
|
369
791
|
|
|
370
792
|
## Saga vs EventHandler
|
|
371
793
|
|
|
372
|
-
Saga
|
|
794
|
+
Use Saga only for operations involving contention between multiple Aggregates.
|
|
373
795
|
|
|
374
|
-
Cases
|
|
796
|
+
Cases that need Saga:
|
|
375
797
|
```
|
|
376
|
-
|
|
377
|
-
Example:
|
|
798
|
+
Multiple actors compete for the same resource
|
|
799
|
+
Example: inventory reservation, where 10 people order the same product at the same time
|
|
378
800
|
|
|
379
801
|
OrderPlacedEvent
|
|
380
|
-
|
|
381
|
-
ReserveInventoryCommand
|
|
382
|
-
|
|
383
|
-
InventoryReservedEvent
|
|
384
|
-
InventoryReservationFailedEvent
|
|
802
|
+
-> InventoryReservationSaga
|
|
803
|
+
ReserveInventoryCommand -> Inventory Aggregate (serializes concurrency)
|
|
804
|
+
->
|
|
805
|
+
InventoryReservedEvent -> ConfirmOrderCommand
|
|
806
|
+
InventoryReservationFailedEvent -> CancelOrderCommand
|
|
385
807
|
```
|
|
386
808
|
|
|
387
|
-
Cases
|
|
809
|
+
Cases that do not need Saga:
|
|
388
810
|
```
|
|
389
|
-
|
|
390
|
-
Example:
|
|
811
|
+
Operation with no contention
|
|
812
|
+
Example: releasing inventory on order cancellation
|
|
391
813
|
|
|
392
814
|
OrderCancelledEvent
|
|
393
|
-
|
|
815
|
+
-> InventoryReleaseHandler (simple EventHandler)
|
|
394
816
|
ReleaseInventoryCommand
|
|
395
|
-
|
|
817
|
+
->
|
|
396
818
|
InventoryReleasedEvent
|
|
397
819
|
```
|
|
398
820
|
|
|
@@ -401,35 +823,35 @@ Decision criteria:
|
|
|
401
823
|
| Situation | Saga | EventHandler |
|
|
402
824
|
|-----------|------|--------------|
|
|
403
825
|
| Resource contention exists | Use | - |
|
|
404
|
-
|
|
|
405
|
-
|
|
|
406
|
-
| Retry on failure
|
|
826
|
+
| Compensation transaction is needed | Use | - |
|
|
827
|
+
| Simple integration with no contention | - | Use |
|
|
828
|
+
| Retry is enough on failure | - | Use |
|
|
407
829
|
|
|
408
830
|
Anti-pattern:
|
|
409
831
|
```kotlin
|
|
410
832
|
// NG - Using Saga for lifecycle management
|
|
411
833
|
@Saga
|
|
412
834
|
class OrderLifecycleSaga {
|
|
413
|
-
//
|
|
414
|
-
// PLACED
|
|
835
|
+
// Tracks every order state transition
|
|
836
|
+
// PLACED -> CONFIRMED -> SHIPPED -> DELIVERED
|
|
415
837
|
}
|
|
416
838
|
|
|
417
|
-
// OK - Saga only for operations
|
|
839
|
+
// OK - Saga only for operations that need eventual consistency
|
|
418
840
|
@Saga
|
|
419
841
|
class InventoryReservationSaga {
|
|
420
|
-
//
|
|
842
|
+
// Concurrency control for inventory reservation only
|
|
421
843
|
}
|
|
422
844
|
```
|
|
423
845
|
|
|
424
|
-
Saga is not a lifecycle
|
|
846
|
+
Saga is not a lifecycle-management tool. Create it for an operation that needs eventual consistency.
|
|
425
847
|
|
|
426
|
-
## Exception vs Event
|
|
848
|
+
## Exception vs Event: Failure Choice
|
|
427
849
|
|
|
428
|
-
Failures not
|
|
850
|
+
Failures that do not require audit are exceptions; failures that require audit are events.
|
|
429
851
|
|
|
430
|
-
Exception approach
|
|
852
|
+
Exception approach, recommended in most cases:
|
|
431
853
|
```kotlin
|
|
432
|
-
// Domain model:
|
|
854
|
+
// Domain model: throw an exception on validation failure
|
|
433
855
|
fun reserveInventory(orderId: String, quantity: Int): InventoryReservedEvent {
|
|
434
856
|
if (availableQuantity < quantity) {
|
|
435
857
|
throw InsufficientInventoryException("Insufficient inventory")
|
|
@@ -437,7 +859,7 @@ fun reserveInventory(orderId: String, quantity: Int): InventoryReservedEvent {
|
|
|
437
859
|
return InventoryReservedEvent(productId, orderId, quantity)
|
|
438
860
|
}
|
|
439
861
|
|
|
440
|
-
// Saga:
|
|
862
|
+
// Saga: catch with exceptionally and issue compensation action
|
|
441
863
|
commandGateway.send<Any>(command)
|
|
442
864
|
.exceptionally { ex ->
|
|
443
865
|
commandGateway.send<Any>(CancelOrderCommand(
|
|
@@ -448,7 +870,7 @@ commandGateway.send<Any>(command)
|
|
|
448
870
|
}
|
|
449
871
|
```
|
|
450
872
|
|
|
451
|
-
Event approach
|
|
873
|
+
Event approach, rare cases:
|
|
452
874
|
```kotlin
|
|
453
875
|
// Only when audit is required
|
|
454
876
|
data class PaymentFailedEvent(
|
|
@@ -462,37 +884,37 @@ Decision criteria:
|
|
|
462
884
|
|
|
463
885
|
| Question | Exception | Event |
|
|
464
886
|
|----------|-----------|-------|
|
|
465
|
-
|
|
|
466
|
-
|
|
|
467
|
-
|
|
|
468
|
-
| Is there value in
|
|
887
|
+
| Does this failure need to be reviewed later? | No | Yes |
|
|
888
|
+
| Is a record required by regulation or compliance? | No | Yes |
|
|
889
|
+
| Is only the Saga interested in the failure? | Yes | No |
|
|
890
|
+
| Is there value in storing it in the Event Store? | No | Yes |
|
|
469
891
|
|
|
470
|
-
Default
|
|
892
|
+
Default to the exception approach. Consider events only when there is an audit requirement.
|
|
471
893
|
|
|
472
|
-
## Abstraction
|
|
894
|
+
## Abstraction-level Evaluation
|
|
473
895
|
|
|
474
|
-
**
|
|
896
|
+
**Detecting bloated conditional branches**
|
|
475
897
|
|
|
476
898
|
| Pattern | Judgment |
|
|
477
899
|
|---------|----------|
|
|
478
|
-
| Same if-else pattern in 3
|
|
479
|
-
| switch/case
|
|
480
|
-
|
|
|
481
|
-
|
|
|
900
|
+
| Same if-else pattern appears in 3 or more places | Abstract with polymorphism -> REJECT |
|
|
901
|
+
| switch/case has 5 or more branches | Consider Strategy/Map pattern |
|
|
902
|
+
| Branching by event type grows repeatedly | Split EventHandlers -> REJECT |
|
|
903
|
+
| State branching inside Aggregate is complex | Consider State Pattern |
|
|
482
904
|
|
|
483
|
-
**
|
|
905
|
+
**Detecting mismatched abstraction levels**
|
|
484
906
|
|
|
485
907
|
| Pattern | Problem | Fix |
|
|
486
908
|
|---------|---------|-----|
|
|
487
|
-
| DB operation details in CommandHandler | Responsibility violation | Separate
|
|
909
|
+
| DB operation details in CommandHandler | Responsibility violation | Separate into Repository layer |
|
|
488
910
|
| Business logic in EventHandler | Responsibility violation | Extract to domain service |
|
|
489
|
-
| Persistence in Aggregate | Layer violation |
|
|
911
|
+
| Persistence processing in Aggregate | Layer violation | Move behind EventStore |
|
|
490
912
|
| Calculation logic in Projection | Hard to maintain | Extract to dedicated service |
|
|
491
913
|
|
|
492
914
|
Good abstraction examples:
|
|
493
915
|
|
|
494
916
|
```kotlin
|
|
495
|
-
// Event
|
|
917
|
+
// Event-type branching grows repeatedly (NG)
|
|
496
918
|
@EventHandler
|
|
497
919
|
fun on(event: DomainEvent) {
|
|
498
920
|
when (event) {
|
|
@@ -503,7 +925,7 @@ fun on(event: DomainEvent) {
|
|
|
503
925
|
}
|
|
504
926
|
}
|
|
505
927
|
|
|
506
|
-
//
|
|
928
|
+
// Split by event (OK)
|
|
507
929
|
@EventHandler
|
|
508
930
|
fun on(event: OrderPlacedEvent) { ... }
|
|
509
931
|
|
|
@@ -520,11 +942,11 @@ fun process(command: ProcessCommand) {
|
|
|
520
942
|
when (status) {
|
|
521
943
|
PENDING -> if (command.type == "approve") { ... } else if (command.type == "reject") { ... }
|
|
522
944
|
APPROVED -> if (command.type == "ship") { ... }
|
|
523
|
-
// ...
|
|
945
|
+
// ...complexity grows
|
|
524
946
|
}
|
|
525
947
|
}
|
|
526
948
|
|
|
527
|
-
//
|
|
949
|
+
// State Pattern (OK)
|
|
528
950
|
sealed class OrderState {
|
|
529
951
|
abstract fun handle(command: ProcessCommand): List<DomainEvent>
|
|
530
952
|
}
|
|
@@ -539,37 +961,37 @@ class PendingState : OrderState() {
|
|
|
539
961
|
|
|
540
962
|
## Anti-pattern Detection
|
|
541
963
|
|
|
542
|
-
|
|
964
|
+
Reject when any of the following is found:
|
|
543
965
|
|
|
544
966
|
| Anti-pattern | Problem |
|
|
545
967
|
|--------------|---------|
|
|
546
|
-
| CRUD
|
|
547
|
-
| Anemic Domain Model | Aggregate is
|
|
548
|
-
| Event Soup | Meaningless events
|
|
968
|
+
| CRUD disguise | Only mimics the shape of CQRS while implementing CRUD |
|
|
969
|
+
| Anemic Domain Model | Aggregate is only a data structure |
|
|
970
|
+
| Event Soup | Meaningless events are emitted repeatedly |
|
|
549
971
|
| Temporal Coupling | Implicit dependency on event order |
|
|
550
972
|
| Missing Events | Important domain events are missing |
|
|
551
|
-
| God Aggregate |
|
|
973
|
+
| God Aggregate | One Aggregate concentrates all responsibilities |
|
|
552
974
|
|
|
553
975
|
## Test Strategy
|
|
554
976
|
|
|
555
|
-
Separate test
|
|
977
|
+
Separate test strategy by layer.
|
|
556
978
|
|
|
557
|
-
Test
|
|
979
|
+
Test pyramid:
|
|
558
980
|
```
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
981
|
+
+-------------+
|
|
982
|
+
| E2E Test | <- Few: full-flow confirmation
|
|
983
|
+
+-------------+
|
|
984
|
+
| Integration | <- Command -> Event -> Projection -> Query integration
|
|
985
|
+
+-------------+
|
|
986
|
+
| Unit Test | <- Many: each layer isolated
|
|
987
|
+
+-------------+
|
|
566
988
|
```
|
|
567
989
|
|
|
568
990
|
Command side (Aggregate):
|
|
569
991
|
```kotlin
|
|
570
992
|
// Using AggregateTestFixture
|
|
571
993
|
@Test
|
|
572
|
-
fun `
|
|
994
|
+
fun `confirmation command emits event`() {
|
|
573
995
|
fixture
|
|
574
996
|
.given(OrderPlacedEvent(...))
|
|
575
997
|
.`when`(ConfirmOrderCommand(orderId, confirmedBy))
|
|
@@ -582,11 +1004,11 @@ Query side:
|
|
|
582
1004
|
```kotlin
|
|
583
1005
|
// Direct Read Model setup + QueryGateway
|
|
584
1006
|
@Test
|
|
585
|
-
fun `
|
|
586
|
-
// Given:
|
|
1007
|
+
fun `order detail is returned`() {
|
|
1008
|
+
// Given: set up Read Model directly
|
|
587
1009
|
orderRepository.save(OrderEntity(...))
|
|
588
1010
|
|
|
589
|
-
// When:
|
|
1011
|
+
// When: execute query through QueryGateway
|
|
590
1012
|
val detail = queryGateway.query(GetOrderDetailQuery(orderId), ...).join()
|
|
591
1013
|
|
|
592
1014
|
// Then
|
|
@@ -596,41 +1018,102 @@ fun `can get order details`() {
|
|
|
596
1018
|
|
|
597
1019
|
Checklist:
|
|
598
1020
|
|
|
599
|
-
|
|
|
600
|
-
|
|
601
|
-
| Aggregate tests verify events not state | Required |
|
|
602
|
-
| Query
|
|
603
|
-
| Integration tests
|
|
1021
|
+
| Perspective | Judgment |
|
|
1022
|
+
|-------------|----------|
|
|
1023
|
+
| Aggregate tests verify events, not state | Required |
|
|
1024
|
+
| Query-side tests do not create data through commands | Recommended |
|
|
1025
|
+
| Integration tests account for Axon asynchronous processing | Required |
|
|
1026
|
+
|
|
1027
|
+
## Value Object Design
|
|
1028
|
+
|
|
1029
|
+
Use value objects as Aggregate and event components. Do not rely only on primitive types such as String or Int.
|
|
1030
|
+
|
|
1031
|
+
```kotlin
|
|
1032
|
+
// NG - primitives only
|
|
1033
|
+
data class OrderPlacedEvent(
|
|
1034
|
+
val orderId: String,
|
|
1035
|
+
val categoryId: String, // Just a string
|
|
1036
|
+
val from: LocalDateTime, // Meaning is unclear
|
|
1037
|
+
val to: LocalDateTime
|
|
1038
|
+
)
|
|
1039
|
+
|
|
1040
|
+
// OK - Value objects express meaning and constraints
|
|
1041
|
+
data class OrderPlacedEvent(
|
|
1042
|
+
val orderId: String,
|
|
1043
|
+
val categoryId: CategoryId,
|
|
1044
|
+
val period: OrderPeriod
|
|
1045
|
+
)
|
|
1046
|
+
```
|
|
1047
|
+
|
|
1048
|
+
Value object design rules:
|
|
1049
|
+
- Use `data class` to auto-generate equals/hashCode, comparing by value
|
|
1050
|
+
- Guarantee invariants in `init` blocks, validating at creation
|
|
1051
|
+
- Do not include domain logic such as state transitions; keep them as pure data holders
|
|
1052
|
+
- Use `@JsonValue` to control serialization
|
|
1053
|
+
|
|
1054
|
+
```kotlin
|
|
1055
|
+
// ID type: single-value wrapper
|
|
1056
|
+
data class CategoryId(@get:JsonValue val value: String) {
|
|
1057
|
+
init {
|
|
1058
|
+
require(value.isNotBlank()) { "Category ID cannot be blank" }
|
|
1059
|
+
}
|
|
1060
|
+
override fun toString(): String = value
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
// Range type: invariant over multiple values
|
|
1064
|
+
data class OrderPeriod(
|
|
1065
|
+
val from: LocalDateTime,
|
|
1066
|
+
val to: LocalDateTime
|
|
1067
|
+
) {
|
|
1068
|
+
init {
|
|
1069
|
+
require(!to.isBefore(from)) { "End date must be on or after start date" }
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
604
1072
|
|
|
605
|
-
|
|
1073
|
+
// Metadata type: associated data in event payload
|
|
1074
|
+
data class ApprovalInfo(
|
|
1075
|
+
val approvedBy: String,
|
|
1076
|
+
val approvalTime: LocalDateTime
|
|
1077
|
+
)
|
|
1078
|
+
```
|
|
606
1079
|
|
|
607
|
-
|
|
1080
|
+
| Criteria | Judgment |
|
|
1081
|
+
|----------|----------|
|
|
1082
|
+
| Reusing IDs as raw String | Consider value object |
|
|
1083
|
+
| Same field combination, such as from/to, appears in multiple places | Extract value object |
|
|
1084
|
+
| Value object contains business logic such as state transitions | REJECT. Aggregate responsibility |
|
|
1085
|
+
| No `init` block to guarantee invariants | REJECT |
|
|
1086
|
+
|
|
1087
|
+
## Master Data, Settings, and CRUD Use
|
|
608
1088
|
|
|
609
|
-
|
|
1089
|
+
Even inside a CQRS+ES system, not everything needs to be event-sourced. Simple master data, reference data, managed settings, and allowlists are often simpler and easier to maintain as normal CRUD.
|
|
610
1090
|
|
|
611
|
-
|
|
1091
|
+
Do not mechanically decide "master data means CRUD". The more the following criteria apply, the more suitable CRUD is. Conversely, if explicit requirements match CQRS+ES adoption criteria, consider CQRS+ES.
|
|
612
1092
|
|
|
613
|
-
|
|
614
|
-
|--------|-----------|---------------|
|
|
615
|
-
| Business requirements | Just "manage X" with no special mentions | Specific business rules or constraints |
|
|
616
|
-
| Logic evolution | Simple reference/update, no foreseeable complexity | State transitions or lifecycle may grow complex |
|
|
617
|
-
| Change history / audit | No need to track "who changed what when" | Change history or audit trail required |
|
|
618
|
-
| Domain events | Changes don't affect other aggregates or processes | Changes trigger downstream processes |
|
|
619
|
-
| Consistency scope | Self-contained, no cross-aggregate consistency needed | Must maintain consistency with other aggregates |
|
|
620
|
-
| Point-in-time queries | No "what was the state at time T" queries | Point-in-time queries required |
|
|
1093
|
+
**Criteria for deciding CRUD is enough:**
|
|
621
1094
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
1095
|
+
| Perspective | CRUD-leaning | CQRS+ES-leaning |
|
|
1096
|
+
|-------------|--------------|-----------------|
|
|
1097
|
+
| Business requirement | Around "manage X" with no special mention | Specific business rules or constraints exist |
|
|
1098
|
+
| Logic evolution | Simple reference/update completes it, unlikely to evolve | State transitions or lifecycle can become complex |
|
|
1099
|
+
| Change history/audit | No need to track who changed what and when | Need change-history reference or audit evidence |
|
|
1100
|
+
| Domain events | This change does not affect other Aggregates or processes | Change triggers downstream processes |
|
|
1101
|
+
| Consistency scope | Self-contained, no need for consistency with other Aggregates | Needs consistency with other Aggregates |
|
|
1102
|
+
| Point-in-time reference | No question asks for "state at a past point" | Point-in-time queries are needed |
|
|
626
1103
|
|
|
627
|
-
**
|
|
628
|
-
-
|
|
629
|
-
-
|
|
630
|
-
-
|
|
1104
|
+
**Typical CRUD targets:**
|
|
1105
|
+
- Prefecture and country-code master data
|
|
1106
|
+
- Category and tag classification master data
|
|
1107
|
+
- Settings and constant tables
|
|
1108
|
+
- Current-value managed settings such as IP allowlists, feature flags, and notification settings
|
|
1109
|
+
|
|
1110
|
+
**Examples where CQRS+ES can be justified:**
|
|
1111
|
+
- Product master data where price-change history must be tracked
|
|
1112
|
+
- Organization master data where changes trigger permission recalculation
|
|
1113
|
+
- Customer/vendor master data with credit-review state transitions
|
|
631
1114
|
|
|
632
1115
|
```kotlin
|
|
633
|
-
// CRUD is
|
|
1116
|
+
// CRUD is enough: simple category master
|
|
634
1117
|
@Entity
|
|
635
1118
|
data class Category(
|
|
636
1119
|
@Id val categoryId: String,
|
|
@@ -638,7 +1121,7 @@ data class Category(
|
|
|
638
1121
|
val displayOrder: Int
|
|
639
1122
|
)
|
|
640
1123
|
|
|
641
|
-
// CQRS+ES is appropriate:
|
|
1124
|
+
// CQRS+ES is appropriate: product requiring price-change history
|
|
642
1125
|
data class Product(
|
|
643
1126
|
val productId: String,
|
|
644
1127
|
val currentPrice: Money
|
|
@@ -650,12 +1133,12 @@ data class Product(
|
|
|
650
1133
|
}
|
|
651
1134
|
```
|
|
652
1135
|
|
|
653
|
-
|
|
1136
|
+
When implementing with CRUD, other Aggregates in a CQRS+ES system should still reference it by ID. It is the same principle that CRUD entities must not directly reference Aggregate internal state.
|
|
654
1137
|
|
|
655
1138
|
## Infrastructure Layer
|
|
656
1139
|
|
|
657
|
-
|
|
658
|
-
- Is event
|
|
659
|
-
- Does messaging infrastructure
|
|
660
|
-
- Is snapshot strategy defined?
|
|
661
|
-
- Is event serialization format appropriate?
|
|
1140
|
+
Checklist:
|
|
1141
|
+
- Is the event-store choice appropriate?
|
|
1142
|
+
- Does the messaging infrastructure satisfy requirements?
|
|
1143
|
+
- Is the snapshot strategy defined?
|
|
1144
|
+
- Is the event serialization format appropriate?
|