SDS-001: Flow Annotation Linter Service
Type
Software Design Specification - Developer Tooling
Status
Proposed
Implements
1. System Overview
The Flow Annotation Linter is a strict validation gate that ensures every Flow declaration in SEA-DSL includes the required CQRS classification annotations. It enforces the “Flow-only tagging” convention and prevents pipeline failures by catching missing or malformed annotations before IR compilation.
2. Purpose
Problem Statement
Without enforcement, agents may generate SEA-DSL flows that lack CQRS classification, leading to:
- Manifest generation with empty
commands/queries/events sections
- Codegen producing only placeholder routes (e.g.,
/health)
- Silent behavioral omissions that are hard to debug
Solution
A fast, deterministic linter that runs immediately after sea parse and fails with clear, actionable diagnostics pointing to the exact source location.
3. Design Principles
- Fail Fast — Block pipeline before IR compilation
- Novice-Friendly — Provide exact fix suggestions with copy-pasteable code
- Span-Aware — Report file:line:column for IDE integration
- CI-Native — Support GitHub Actions annotation format (
--gha)
- Configurable — Allow strict/warn-only modes via config file
4. Flow Annotation Contract
Required for Every Flow
Every Flow declaration must include:
Flow "PlaceOrder"
@cqrs { "kind": "command" } # ← REQUIRED
from "Customer" to "Order"
Allowed cqrs.kind Values
| Value |
Description |
command |
Write operation (mutates state) |
query |
Read operation (no side effects) |
event |
Domain event publication |
Additional Annotations by Kind
Commands (Required)
Flow "PlaceOrder"
@cqrs { "kind": "command" }
@tx { "transactional": true } # REQUIRED
@idempotency { "enabled": true, "key": "..." } # Recommended
from "Customer" to "Order"
Events (Required)
Flow "OrderPlaced"
@cqrs { "kind": "event" }
@outbox { "mode": "required" } # REQUIRED
from "Order" to "Customer" quantity 1
Queries (Recommended)
Flow "GetOrder"
@cqrs { "kind": "query" }
@read_model { "source": "projection", "table": "..." } # Recommended
from "Customer" to "Order"
@cqrs { "kind": "command" } # ✅ Correct
@cqrs.kind "command" # ❌ Rejected (dotted format)
The linter enforces the nested JSON form to ensure manifest generation can parse annotations consistently.
6. Validation Rules
Hard Errors (Block Pipeline)
| Rule |
Message Template |
Missing @cqrs |
Flow '{name}': missing @cqrs annotation. Add: @cqrs { "kind": "command" } |
Missing cqrs.kind |
Flow '{name}': @cqrs present but 'kind' field missing |
Invalid cqrs.kind |
Flow '{name}': cqrs.kind must be 'command', 'query', or 'event' |
| Dotted key format |
Flow '{name}': use nested @cqrs { "kind": "..." }, not dotted @cqrs.kind |
Command missing @tx |
Flow '{name}' (command): missing @tx annotation. Add: @tx { "transactional": true } |
Event missing @outbox |
Flow '{name}' (event): missing @outbox annotation. Add: @outbox { "mode": "required" } |
Warnings (Allow but Flag)
| Rule |
Message Template |
| Command without idempotency |
Flow '{name}' (command): recommend @idempotency { "enabled": true, ... } |
| Query without read_model |
Flow '{name}' (query): recommend @read_model { "source": "...", ... } |
| Event with “optional” outbox |
Flow '{name}' (event): @outbox "optional" — consider "required" for reliability |
7. Reference Validation
The linter also validates flow references:
| Check |
Description |
| Unique flow names |
No duplicate Flow names per namespace |
from_entity exists |
Must reference a declared Entity |
to_entity exists |
Must reference a declared Entity |
resource_name exists |
Must reference a declared Resource |
8. CLI Interface
Basic Usage
1
| python tools/flow_lint.py specs/orders/orders.ast.json
|
Options
| Flag |
Description |
--config PATH |
Load config from JSON file |
--gha |
Emit GitHub Actions annotation format |
--strict |
Treat warnings as errors |
--warn-only |
Never fail (warnings only) |
1
| python tools/flow_lint.py specs/orders/orders.ast.json --gha
|
Output:
1
| ::error file=specs/orders/orders.sea,line=12,col=3::Flow 'PlaceOrder': missing @cqrs annotation
|
9. Configuration File
1
2
3
4
5
6
7
8
9
| {
"require_tx_on_commands": true,
"require_outbox_on_events": true,
"require_idempotency_on_commands": false,
"require_read_model_on_queries": false,
"prefer_outbox_required": true,
"allow_dotted_keys": false,
"strict_mode": false
}
|
10. Pipeline Integration
Position in Pipeline
1
2
3
4
5
6
7
| sea parse --format json specs/orders/orders.sea > orders.ast.json
↓
python tools/flow_lint.py orders.ast.json ← THIS TOOL
↓
python tools/ast_to_ir.py orders.ast.json orders.ir.json
↓
...
|
Nx Target
1
2
3
4
5
6
7
8
9
| {
"sea:lint": {
"executor": "nx:run-commands",
"dependsOn": ["sea:ast"],
"options": {
"command": "python tools/flow_lint.py specs/orders/orders.ast.json --config tools/flow_lint.config.json"
}
}
}
|
11. Data Model
The linter expects AST JSON conforming to ast-v3.schema.json:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| {
"metadata": { "namespace": "orders", ... },
"declarations": [
{
"line": 12,
"column": 1,
"node": {
"Flow": {
"resource_name": "PlaceOrder",
"from_entity": "Customer",
"to_entity": "Order",
"annotations": {
"cqrs": { "kind": "command" },
"tx": { "transactional": true }
}
}
}
}
]
}
|
Output: Lint Report
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| {
"status": "pass" | "fail",
"errors": [
{
"flow": "PlaceOrder",
"line": 12,
"column": 1,
"severity": "error",
"rule": "missing_cqrs_kind",
"message": "Flow 'PlaceOrder': missing @cqrs annotation",
"fix": "@cqrs { \"kind\": \"command\" }"
}
],
"warnings": [ ... ],
"summary": {
"flows_checked": 5,
"errors": 1,
"warnings": 2
}
}
|
12. Error Recovery
The linter collects all errors before failing to provide comprehensive feedback:
1
2
3
4
5
6
7
| specs/orders/orders.sea:12:1 Flow 'PlaceOrder': missing @cqrs annotation
Fix: Add @cqrs { "kind": "command" }
specs/orders/orders.sea:25:1 Flow 'CancelOrder': @tx annotation missing
Fix: Add @tx { "transactional": true }
Lint failed: 2 errors, 0 warnings
|
13. Success Criteria
| Metric |
Target |
| All flows classified |
100% |
| No dotted annotation keys |
100% |
Commands have @tx |
100% |
Events have @outbox |
100% |
14. Testing
Golden Test Cases
| Test Case |
Expected |
| Flow with valid annotations |
PASS |
Flow missing @cqrs |
ERROR |
| Flow with dotted key |
ERROR |
Command missing @tx |
ERROR |
Event missing @outbox |
ERROR |
Query without @read_model |
WARNING |
15. Summary
The Flow Annotation Linter is a hallucination-prevention gate that ensures semantic completeness before IR compilation. By failing early with clear diagnostics, it protects the pipeline from silent behavioral omissions.