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:

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

  1. Fail Fast — Block pipeline before IR compilation
  2. Novice-Friendly — Provide exact fix suggestions with copy-pasteable code
  3. Span-Aware — Report file:line:column for IDE integration
  4. CI-Native — Support GitHub Actions annotation format (--gha)
  5. 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
Flow "GetOrder"
  @cqrs { "kind": "query" }
  @read_model { "source": "projection", "table": "..." }  # Recommended
  from "Customer" to "Order"

5. Annotation Format

Required Format: Nested JSON

@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)

GitHub Actions Format

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

tools/flow_lint.config.json

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

Input: AST JSON

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.