Skip to content

Phase 02: Blueprint Creation

INPUT PROCESSING OUTPUT
  • WRD content
  • Phase 1 output
  • Duc analyzes requirements
  • Designs architecture
  • Creates technical blueprint
  • Technical blueprint
  • Component definitions
  • API contracts
  • Phase feedback

Overview

Duc (Architecture agent) creates a technical blueprint from the validated WRD. This is Phase 2 of the 13-phase TDD workflow.

This phase: 1. Analyzes requirements from Phase 1 (WRD Intake) 2. Designs system architecture 3. Defines components, files, APIs, and data models 4. Undergoes two-layer validation (deterministic + Baron LLM)

Note: Duc uses read-only analysis tools (Read, Glob, Grep) to understand the existing codebase before designing.


Assignment

Issue Type Assignee Reason
Feature Duc Architect designs system changes
Tech Duc Architect understands technical changes
Bug Duc Architect can trace through codebase

Document Format

All blueprints MUST be valid YAML files.

Requirement Description
Format YAML 1.2
Extension .yaml
Naming BP-{NNN}-{domain}-v{N}.yaml
Encoding UTF-8
Validation Baron validates against domain-specific JSON Schema

Examples: - BP-001-backend-v1.yaml - BP-002-frontend-v3.yaml - BP-003-devops-v1.yaml


Blueprint Fundamentals

What is a Blueprint?

A blueprint is a self-contained YAML specification for a single domain. It tells the implementing agent:

  • WHAT artifact to produce
  • WHERE it lives (infra, service, domain, file path)
  • WHY (acceptance criteria)
  • INTERFACES (inputs, outputs, contracts)

A blueprint does NOT specify HOW to implement - that's the implementer's job.

Language Principle: Speak the Implementer's Language

Issue documents use business language. Blueprints use technical language.

The blueprint bridges business requirements to implementation. Duc must write artifact descriptions using the vocabulary of the target technology stack so the implementer knows exactly what to create.

Domain Technology Blueprint Language Examples
Backend Python/FastAPI "Create a FastAPI APIRouter", "Define a Pydantic BaseModel", "Add an async def endpoint"
Backend Node/Express "Create an Express Router", "Define a Zod schema", "Add a middleware function"
Backend Go/Gin "Create a Gin RouterGroup", "Define a struct with JSON tags", "Add a handler function"
Frontend React/TypeScript "Create a React functional component", "Create a custom hook using useState", "Define a TypeScript interface"
Frontend Vue "Create a Vue SFC (Single File Component)", "Add a composable using ref and computed"
DevOps Kubernetes "Update the Deployment manifest", "Add an Ingress rule", "Create a ConfigMap"
DevOps Terraform "Add an aws_iam_policy resource", "Create a module for the service"
Tests pytest "Create a pytest test module", "Add a @pytest.fixture", "Use pytest.mark.parametrize"
Tests Jest "Create a Jest test suite with describe", "Add a beforeEach setup", "Mock using jest.fn()"
Tests Playwright "Create a Playwright test spec", "Add a test.beforeEach hook", "Use page.locator()"

Bad (too vague):

- path: src/domains/refunds/service.py
  description: Refund business logic

Good (technology-specific):

- path: src/domains/refunds/service.py
  description: |
    Create a RefundService class with:
    - async def create_refund(order_id, amount_cents) -> RefundResult
    - async def get_refund(refund_id) -> Refund | None
    - Inject StripeClient and OrderRepository via __init__

One Blueprint per Domain

Each blueprint targets exactly one domain owner:

Domain Owner Agent Role Stack Examples
tests Marie QA Engineer - writes tests FIRST pytest, Jest, Playwright
backend Dede Backend Developer Python/FastAPI, Node/Express, Go/Gin
frontend Dali Frontend Developer React, Vue, Angular, Svelte
infra Gus DevOps / GitOps Kubernetes, Terraform, Helm, GitHub Actions
sre Maigret SRE - journey-level observability Prometheus, Grafana, OpenTelemetry

Blueprint Versioning

Blueprints are versioned in the architect's sub-issue:

ISSUE-123/sub-issues/duc/
├── BP-001-tests-v1.yaml        # Initial draft
├── BP-001-tests-v2.yaml        # After Baron's feedback
├── BP-001-tests-v3.yaml        # Approved ✓
├── BP-002-backend-v1.yaml      # Approved ✓
├── BP-003-frontend-v1.yaml     # Approved ✓
├── BP-004-infra-v1.yaml        # Approved ✓
└── BP-005-sre-v1.yaml          # Approved ✓

Only the approved version gets copied to the domain agent's sub-issue.


Blueprint Schemas

Common Header (All Blueprints)

blueprint_id: BP-{NNN}             # Unique within issue
version: v{N}                      # Increments on revision
issue_id: ISSUE-{ID}               # Parent issue reference
owner: string                      # Domain: backend | frontend | devops | tests | security | sre | finops
status: draft | review | approved | rejected
created_at: datetime
updated_at: datetime

# Technology context - helps implementer understand the stack
stack:
  language: string                 # python | typescript | go | etc.
  framework: string                # fastapi | express | react | vue | etc.
  version: string                  # 3.11 | 18.2 | 1.21 | etc.

Backend Blueprint

For API endpoints, services, business logic, data models.

Schema

blueprint_id: BP-{NNN}
version: v{N}
issue_id: ISSUE-{ID}
owner: backend
status: draft | review | approved | rejected

# STACK
stack:
  language: python | typescript | go | java | etc.
  framework: fastapi | express | gin | spring | etc.
  version: string

# WHERE - Location context
service:
  name: string                     # Service name
  status: existing | new           # Is this service new?
  repo: string                     # Repository URL

domain:
  name: string                     # Domain/module name
  status: existing | new           # Is this domain new?
  path: string                     # Path within repo (e.g., src/domains/payments/)

# WHAT - Artifact definition (use framework-specific language!)
artifacts:
  - path: string                   # Exact file path
    type: create | modify | delete
    description: string            # Technical description using framework vocabulary

# INTERFACES - Contracts
interfaces:
  - type: rest | grpc | graphql | event | internal
    method: string                 # GET, POST, etc. (for REST)
    endpoint: string               # /api/v1/users/{id}
    request:
      headers: object | null
      params: object | null
      body: object | null
    response:
      success: object
      errors: [object]

# WHY - Acceptance criteria
acceptance_criteria:
  - string
  - string

# DEPENDENCIES
dependencies:
  blueprints: [string]             # Other blueprint IDs this depends on
  services: [string]               # External service dependencies
  packages: [string]               # Required packages with versions

Example: Payment Refund Endpoint (Python/FastAPI)

blueprint_id: BP-001
version: v1
issue_id: ISSUE-123
owner: backend
status: draft

stack:
  language: python
  framework: fastapi
  version: "0.109"

service:
  name: payment-service
  status: existing
  repo: github.com/acme/payment-service

domain:
  name: refunds
  status: new
  path: src/domains/refunds/

artifacts:
  - path: src/domains/refunds/__init__.py
    type: create
    description: |
      Python package init. Export public API:
      - from .service import RefundService
      - from .models import RefundRequest, RefundResult, RefundStatus
      - from .router import router

  - path: src/domains/refunds/models.py
    type: create
    description: |
      Define Pydantic models:
      - class RefundRequest(BaseModel): order_id (str), amount_cents (int), reason (str | None)
      - class RefundResult(BaseModel): refund_id (str), order_id (str), amount_cents (int), status (RefundStatus), created_at (datetime)
      - class RefundStatus(str, Enum): PENDING, COMPLETED, FAILED
      Add field validators:
      - amount_cents must be > 0
      - order_id must match pattern ^ord_[a-zA-Z0-9]+$

  - path: src/domains/refunds/service.py
    type: create
    description: |
      Create RefundService class:
      - __init__(self, stripe_client: StripeClient, order_repo: OrderRepository, refund_repo: RefundRepository)
      - async def create_refund(self, request: RefundRequest, idempotency_key: str) -> RefundResult
        - Validate order exists via order_repo.get_by_id()
        - Validate amount <= order.amount_cents
        - Check idempotency via refund_repo.get_by_idempotency_key()
        - Call stripe_client.refunds.create() with Stripe idempotency key
        - Persist refund via refund_repo.create()
        - Publish RefundCompletedEvent via event bus
      - async def get_refund(self, refund_id: str) -> Refund | None

  - path: src/domains/refunds/router.py
    type: create
    description: |
      Create FastAPI APIRouter with prefix="/api/v1/refunds":
      - @router.post("/", response_model=RefundResult, status_code=201)
        async def create_refund(request: RefundRequest, idempotency_key: str = Header(...), service: RefundService = Depends(get_refund_service))
      - Use HTTPException for error responses with detail dict containing 'code' and 'message'
      - Add OpenAPI tags=["refunds"] and summary/description for docs

  - path: src/domains/refunds/repository.py
    type: create
    description: |
      Create RefundRepository class:
      - __init__(self, db: AsyncSession)
      - async def create(self, refund: Refund) -> Refund
      - async def get_by_id(self, refund_id: str) -> Refund | None
      - async def get_by_idempotency_key(self, key: str) -> Refund | None
      Use SQLAlchemy async queries with select() and insert()

  - path: src/domains/refunds/events.py
    type: create
    description: |
      Define event schemas:
      - class RefundCompletedEvent(BaseEvent):
          topic = "payments.refund.completed"
          refund_id: str
          order_id: str  
          amount_cents: int
          completed_at: datetime

  - path: src/api/main.py
    type: modify
    description: |
      Add to existing FastAPI app:
      - from src.domains.refunds import router as refunds_router
      - app.include_router(refunds_router)

interfaces:
  - type: rest
    method: POST
    endpoint: /api/v1/refunds
    request:
      headers:
        Authorization: Bearer {token}
        Idempotency-Key: string (required, UUID format)
      body:
        order_id: string (required, pattern: ^ord_[a-zA-Z0-9]+$)
        amount_cents: integer (required, min: 1, max: order.amount_cents)
        reason: string (optional, max: 500 chars)
    response:
      success:
        status: 201
        body:
          refund_id: string
          order_id: string
          amount_cents: integer
          status: "pending" | "completed" | "failed"
          created_at: datetime (ISO 8601)
      errors:
        - status: 400
          body:
            code: "INVALID_AMOUNT"
            message: "Refund amount exceeds original order amount"
        - status: 404
          body:
            code: "ORDER_NOT_FOUND"
            message: "Order does not exist"
        - status: 409
          body:
            code: "DUPLICATE_REQUEST"
            message: "Refund already processed for this idempotency key"
            refund: { existing refund object }
        - status: 422
          body:
            code: "ORDER_NOT_REFUNDABLE"
            message: "Order status does not allow refunds"

  - type: event
    method: publish
    endpoint: payments.refund.completed
    request:
      body:
        refund_id: string
        order_id: string
        amount_cents: integer
        completed_at: datetime

acceptance_criteria:
  - Refund endpoint validates order exists and belongs to authenticated user
  - Refund amount cannot exceed original order amount
  - Idempotency key prevents duplicate refunds (returns existing refund with 409)
  - Refund integrates with Stripe Refund API
  - Event published on successful refund for downstream consumers
  - All errors return structured JSON with error code and message

dependencies:
  blueprints: []
  services:
    - stripe-api
    - order-service (gRPC for order validation)
  packages:
    - stripe>=7.0.0
    - pydantic>=2.0.0
    - sqlalchemy[asyncio]>=2.0.0

Frontend Blueprint

For UI components, pages, client-side logic.

Schema

blueprint_id: BP-{NNN}
version: v{N}
issue_id: ISSUE-{ID}
owner: frontend
status: draft | review | approved | rejected

# STACK
stack:
  language: typescript | javascript
  framework: react | vue | angular | svelte
  version: string
  styling: tailwind | styled-components | css-modules | etc.

# WHERE
application:
  name: string
  status: existing | new
  repo: string

component:
  name: string
  status: existing | new
  path: string                     # e.g., src/components/RefundDialog/

# WHAT (use React/Vue/etc. vocabulary!)
artifacts:
  - path: string
    type: create | modify | delete
    description: string

# INTERFACES - API contracts this component uses
api_contracts:
  - endpoint: string
    method: string
    request: object
    response: object

# UI SPECIFICATIONS
ui_specs:
  wireframe: string | null         # Link to design
  states:                          # UI states to handle
    - name: string
      description: string
  interactions:                    # User interactions
    - trigger: string
      action: string

# WHY
acceptance_criteria:
  - string

# DEPENDENCIES
dependencies:
  blueprints: [string]
  components: [string]             # Existing components to use
  packages: [string]

Example: Refund Dialog Component (React/TypeScript)

blueprint_id: BP-002
version: v1
issue_id: ISSUE-123
owner: frontend
status: draft

stack:
  language: typescript
  framework: react
  version: "18.2"
  styling: tailwind

application:
  name: merchant-dashboard
  status: existing
  repo: github.com/acme/merchant-dashboard

component:
  name: RefundDialog
  status: new
  path: src/components/RefundDialog/

artifacts:
  - path: src/components/RefundDialog/index.tsx
    type: create
    description: |
      Create React functional component RefundDialog:
      - Props interface: { orderId: string; maxAmount: number; isOpen: boolean; onClose: () => void; onSuccess: (refund: Refund) => void }
      - Use useState for form state and dialog step (form | confirm | loading | success | error)
      - Use Dialog component from @/components/ui/Dialog (Radix-based)
      - Render RefundForm when step === 'form'
      - Render RefundConfirmation when step === 'confirm' | 'success' | 'error'
      - Handle onClose with dirty form warning using window.confirm()

  - path: src/components/RefundDialog/RefundForm.tsx
    type: create
    description: |
      Create React functional component RefundForm:
      - Props interface: { maxAmount: number; onSubmit: (data: RefundFormData) => void; onCancel: () => void }
      - Use react-hook-form with zodResolver for form handling
      - Define Zod schema: z.object({ amount: z.number().min(1).max(maxAmount), reason: z.string().optional() })
      - Render:
        - Input type="number" for amount with {...register('amount')} 
        - Show max refundable amount as helper text
        - Textarea for optional reason
        - Button type="submit" "Continue to Review"
        - Button type="button" onClick={onCancel} "Cancel"
      - Display validation errors from formState.errors

  - path: src/components/RefundDialog/RefundConfirmation.tsx
    type: create
    description: |
      Create React functional component RefundConfirmation:
      - Props interface: { 
          step: 'confirm' | 'loading' | 'success' | 'error';
          amount: number;
          refund?: Refund;
          error?: string;
          onConfirm: () => void;
          onRetry: () => void;
          onClose: () => void;
        }
      - Conditional rendering based on step:
        - 'confirm': Show amount summary, "Confirm Refund" button, "Go Back" button
        - 'loading': Show Spinner component with "Processing refund..." text
        - 'success': Show CheckCircle icon, refund.refund_id, amount, auto-close after 3s using useEffect
        - 'error': Show XCircle icon, error message, "Try Again" button

  - path: src/components/RefundDialog/useRefund.ts
    type: create
    description: |
      Create custom hook useRefund:
      - Signature: (orderId: string) => { mutate, isLoading, error, data }
      - Use useMutation from @tanstack/react-query
      - mutationFn: async (data: { amount_cents: number; reason?: string }) => {
          const response = await fetch('/api/v1/refunds', {
            method: 'POST',
            headers: { 
              'Content-Type': 'application/json',
              'Idempotency-Key': crypto.randomUUID()
            },
            body: JSON.stringify({ order_id: orderId, ...data })
          });
          if (!response.ok) throw await response.json();
          return response.json();
        }
      - On success: invalidate ['orders', orderId] query

  - path: src/components/RefundDialog/types.ts
    type: create
    description: |
      Define TypeScript interfaces:
      - interface RefundFormData { amount: number; reason?: string }
      - interface Refund { refund_id: string; order_id: string; amount_cents: number; status: 'pending' | 'completed' | 'failed'; created_at: string }
      - interface RefundError { code: string; message: string }

  - path: src/components/RefundDialog/RefundDialog.test.tsx
    type: create
    description: |
      Create Jest + React Testing Library tests:
      - describe('RefundDialog', () => { ... })
      - test('renders form when open')
      - test('validates amount does not exceed max')
      - test('shows confirmation step after form submit')
      - test('calls onSuccess with refund data after successful API call')
      - test('shows error message on API failure')
      - test('closes dialog on backdrop click with dirty form warning')
      - Mock fetch using jest.fn()
      - Use renderHook for testing useRefund

  - path: src/pages/OrderDetail/index.tsx
    type: modify
    description: |
      Add to existing OrderDetail component:
      - Import RefundDialog from '@/components/RefundDialog'
      - Add useState<boolean> for isRefundDialogOpen
      - Add Button onClick={() => setIsRefundDialogOpen(true)} "Issue Refund"
        - Only show if order.status === 'completed' && order.refundable_amount > 0
      - Render <RefundDialog 
          orderId={order.id}
          maxAmount={order.refundable_amount}
          isOpen={isRefundDialogOpen}
          onClose={() => setIsRefundDialogOpen(false)}
          onSuccess={(refund) => { toast.success('Refund processed'); refetch(); }}
        />

api_contracts:
  - endpoint: /api/v1/refunds
    method: POST
    request:
      order_id: string
      amount_cents: integer
      reason: string | null
    response:
      refund_id: string
      order_id: string
      amount_cents: integer
      status: "pending" | "completed" | "failed"
      created_at: string

ui_specs:
  wireframe: https://figma.com/file/xxx/refund-dialog
  states:
    - name: idle
      description: Dialog closed, no state
    - name: form
      description: Dialog open showing amount input and reason textarea
    - name: confirm
      description: User clicked "Continue", showing confirmation with final amount
    - name: loading
      description: API request in progress, spinner visible, buttons disabled
    - name: success
      description: Refund completed, showing green checkmark and refund ID
    - name: error
      description: Refund failed, showing error message with retry option
  interactions:
    - trigger: Click "Issue Refund" button on order detail page
      action: Open dialog in 'form' state
    - trigger: Submit form with valid amount
      action: Transition to 'confirm' state
    - trigger: Click "Confirm Refund" in confirmation view
      action: Transition to 'loading', call API, then 'success' or 'error'
    - trigger: Click "Go Back" in confirmation view
      action: Return to 'form' state with preserved values
    - trigger: Click outside dialog or press Escape
      action: If form dirty, show confirm("Discard changes?"). If confirmed or not dirty, close dialog.
    - trigger: Success state shown for 3 seconds
      action: Auto-close dialog and call onSuccess

acceptance_criteria:
  - Dialog opens from order detail page when clicking "Issue Refund"
  - Amount field validates: required, positive, max = order.refundable_amount
  - Confirmation step shows amount before API call
  - Loading state disables all buttons and shows spinner
  - Success state shows refund ID and closes after 3s
  - Error state shows error.message and allows retry
  - Form state preserved when going back from confirmation
  - Escape key and backdrop click close with dirty form warning
  - Fully keyboard accessible (tab order, enter to submit)
  - Screen reader announces dialog title and state changes

dependencies:
  blueprints:
    - BP-001  # Backend refund endpoint must exist
  components:
    - Dialog from @/components/ui/Dialog (Radix)
    - Button from @/components/ui/Button
    - Input from @/components/ui/Input
    - Spinner from @/components/ui/Spinner
  packages:
    - react-hook-form@^7.0.0
    - @hookform/resolvers@^3.0.0
    - zod@^3.0.0
    - @tanstack/react-query@^5.0.0

DevOps Blueprint

For infrastructure, deployment, CI/CD changes.

Schema

blueprint_id: BP-{NNN}
version: v{N}
issue_id: ISSUE-{ID}
owner: devops
status: draft | review | approved | rejected

# STACK
stack:
  orchestration: kubernetes | ecs | docker-compose
  iac: terraform | pulumi | cloudformation
  ci: github-actions | gitlab-ci | jenkins

# WHERE - Infrastructure context
infrastructure:
  provider: aws | gcp | azure
  account: string
  region: string
  cluster: string | null           # EKS, GKE, etc.

# WHAT - Deployment context
deployment:
  namespace: string
  service: string
  type: update | new | delete

# ARTIFACTS (use K8s/Terraform vocabulary!)
artifacts:
  - path: string
    type: create | modify | delete
    description: string

# REQUIREMENTS
requirements:
  compute:
    cpu: string
    memory: string
    replicas: integer
    autoscaling: object | null
  networking:
    ingress: [object]
    egress: [object]
  storage:
    volumes: [object]
  secrets:
    - name: string
      source: string

# WHY
acceptance_criteria:
  - string

# DEPENDENCIES
dependencies:
  blueprints: [string]
  infrastructure: [string]

Example: Payment Service Deployment Update (Kubernetes/Terraform)

blueprint_id: BP-003
version: v1
issue_id: ISSUE-123
owner: devops
status: draft

stack:
  orchestration: kubernetes
  iac: terraform
  ci: github-actions

infrastructure:
  provider: aws
  account: "123456789012"
  region: eu-west-1
  cluster: eks-production

deployment:
  namespace: payments
  service: payment-service
  type: update

artifacts:
  - path: k8s/payment-service/deployment.yaml
    type: modify
    description: |
      Modify existing Deployment resource:
      - Add to spec.template.spec.containers[0].env:
        - name: STRIPE_REFUND_ENABLED
          value: "true"
        - name: REFUND_MAX_AMOUNT_CENTS
          valueFrom:
            configMapKeyRef:
              name: payment-service-config
              key: refund.max_amount_cents
      - Add to spec.template.spec.containers[0].envFrom:
        - secretRef:
            name: stripe-refund-secrets

  - path: k8s/payment-service/configmap.yaml
    type: modify
    description: |
      Add to existing ConfigMap data:
        refund.max_amount_cents: "1000000"  # $10,000 max
        refund.rate_limit_per_minute: "10"
        refund.idempotency_ttl_hours: "24"

  - path: k8s/payment-service/ingress.yaml
    type: modify
    description: |
      Add to existing Ingress spec.rules[0].http.paths:
        - path: /api/v1/refunds
          pathType: Prefix
          backend:
            service:
              name: payment-service
              port:
                number: 8080

  - path: k8s/payment-service/secrets.yaml
    type: create
    description: |
      Create ExternalSecret resource (external-secrets-operator):
        apiVersion: external-secrets.io/v1beta1
        kind: ExternalSecret
        metadata:
          name: stripe-refund-secrets
          namespace: payments
        spec:
          refreshInterval: 1h
          secretStoreRef:
            name: aws-secrets-manager
            kind: ClusterSecretStore
          target:
            name: stripe-refund-secrets
          data:
            - secretKey: STRIPE_REFUND_API_KEY
              remoteRef:
                key: payments/stripe/refund-api-key

  - path: terraform/modules/payment-service/iam.tf
    type: modify
    description: |
      Add to existing aws_iam_policy.payment_service_secrets:
        - In statement[0].resources, add:
          "arn:aws:secretsmanager:eu-west-1:123456789012:secret:payments/stripe/refund-api-key-*"

  - path: terraform/modules/payment-service/variables.tf
    type: modify
    description: |
      Add variable:
        variable "refund_enabled" {
          description = "Enable refund functionality"
          type        = bool
          default     = false
        }

  - path: .github/workflows/deploy-payment-service.yaml
    type: modify
    description: |
      Add to existing GitHub Actions workflow jobs.deploy.steps:
        - name: Verify refund endpoint health
          run: |
            kubectl wait --for=condition=ready pod -l app=payment-service -n payments --timeout=120s
            curl -f http://payment-service.payments.svc.cluster.local:8080/api/v1/refunds/health || exit 1

requirements:
  compute:
    cpu: "500m"
    memory: "1Gi"
    replicas: 3
    autoscaling:
      minReplicas: 3
      maxReplicas: 10
      targetCPUUtilization: 70
  networking:
    ingress:
      - path: /api/v1/refunds
        service: payment-service
        port: 8080
        annotations:
          nginx.ingress.kubernetes.io/rate-limit: "10"
          nginx.ingress.kubernetes.io/rate-limit-window: "1m"
    egress:
      - destination: api.stripe.com
        port: 443
        protocol: HTTPS
  storage:
    volumes: []
  secrets:
    - name: STRIPE_REFUND_API_KEY
      source: aws-secrets-manager:payments/stripe/refund-api-key

acceptance_criteria:
  - Endpoint /api/v1/refunds accessible via ingress
  - ExternalSecret syncs Stripe key from AWS Secrets Manager
  - IAM policy allows payment-service to read refund secrets
  - Zero-downtime deployment via rolling update
  - Health check endpoint responds within 120s of deploy
  - Rollback possible via kubectl rollout undo

dependencies:
  blueprints:
    - BP-001  # Backend code must be merged first
  infrastructure:
    - eks-production cluster (existing)
    - external-secrets-operator (existing)
    - aws-secrets-manager ClusterSecretStore (existing)
    - nginx-ingress-controller (existing)

Tests Blueprint

For test suites, QA automation.

Schema

blueprint_id: BP-{NNN}
version: v{N}
issue_id: ISSUE-{ID}
owner: tests
status: draft | review | approved | rejected

# STACK
stack:
  language: python | typescript | etc.
  test_framework: pytest | jest | playwright | k6
  version: string

# SCOPE
test_scope:
  service: string
  domain: string
  type: unit | integration | e2e | performance | security

# WHAT (use pytest/jest vocabulary!)
artifacts:
  - path: string
    type: create | modify | delete
    description: string

# TEST SCENARIOS
scenarios:
  - name: string
    type: unit | integration | e2e
    given: string
    when: string
    then: string
    priority: critical | high | medium | low

# WHY
acceptance_criteria:
  - string

# DEPENDENCIES
dependencies:
  blueprints: [string]
  fixtures: [string]

Example: Refund API Tests (Python/pytest)

blueprint_id: BP-004
version: v1
issue_id: ISSUE-123
owner: tests
status: draft

stack:
  language: python
  test_framework: pytest
  version: "8.0"

test_scope:
  service: payment-service
  domain: refunds
  type: integration

artifacts:
  - path: tests/integration/refunds/test_refund_api.py
    type: create
    description: |
      Create pytest test module with async tests:
      - Use @pytest.mark.asyncio decorator on all tests
      - Use httpx.AsyncClient for API calls
      - Import fixtures from conftest.py

      Test functions:
      - async def test_create_refund_full_amount(client, completed_order, auth_headers):
          response = await client.post("/api/v1/refunds", json={...}, headers={...})
          assert response.status_code == 201
          assert response.json()["status"] == "completed"

      - async def test_create_refund_partial_amount(...)
      - async def test_refund_exceeds_order_amount_returns_400(...)
      - async def test_refund_nonexistent_order_returns_404(...)
      - async def test_duplicate_idempotency_key_returns_existing_refund(...)
      - async def test_refund_other_users_order_returns_403(...)
      - async def test_refund_pending_order_returns_422(...)

  - path: tests/integration/refunds/conftest.py
    type: create
    description: |
      Create pytest fixtures:

      @pytest.fixture
      async def completed_order(db_session, test_user) -> Order:
          """Create a completed order with known amount for refund testing."""
          order = Order(user_id=test_user.id, amount_cents=10000, status="completed")
          db_session.add(order)
          await db_session.commit()
          return order

      @pytest.fixture
      async def pending_order(db_session, test_user) -> Order:
          """Create a pending order that cannot be refunded."""
          ...

      @pytest.fixture
      def stripe_mock(mocker) -> MagicMock:
          """Mock Stripe refund API calls."""
          mock = mocker.patch("stripe.Refund.create")
          mock.return_value = {"id": "re_test123", "status": "succeeded"}
          return mock

      @pytest.fixture
      def idempotency_key() -> str:
          return str(uuid.uuid4())

  - path: tests/unit/domains/refunds/test_service.py
    type: create
    description: |
      Create pytest unit tests for RefundService:

      - class TestRefundService:
          @pytest.fixture
          def service(self, mock_stripe, mock_order_repo, mock_refund_repo):
              return RefundService(mock_stripe, mock_order_repo, mock_refund_repo)

          async def test_create_refund_validates_amount_not_exceeding_order(self, service):
              ...

          async def test_create_refund_checks_idempotency_key(self, service):
              ...

          async def test_create_refund_publishes_event_on_success(self, service, mock_event_bus):
              ...

  - path: tests/fixtures/orders.py
    type: modify
    description: |
      Add to existing fixtures module:

      def refundable_order_factory(user_id: str, amount_cents: int = 10000) -> Order:
          """Factory for creating refundable orders in tests."""
          return Order(
              id=f"ord_{uuid.uuid4().hex[:12]}",
              user_id=user_id,
              amount_cents=amount_cents,
              status="completed",
              created_at=datetime.utcnow()
          )

scenarios:
  - name: Successful full refund
    type: integration
    given: A completed order exists with amount_cents=10000
    when: POST /api/v1/refunds with amount_cents=10000
    then: Returns 201 with refund_id and status="completed"
    priority: critical

  - name: Successful partial refund
    type: integration
    given: A completed order exists with amount_cents=10000
    when: POST /api/v1/refunds with amount_cents=3000
    then: Returns 201 with refund_id and amount_cents=3000
    priority: critical

  - name: Refund exceeds order amount
    type: integration
    given: A completed order exists with amount_cents=10000
    when: POST /api/v1/refunds with amount_cents=15000
    then: Returns 400 with code="INVALID_AMOUNT"
    priority: high

  - name: Refund for non-existent order
    type: integration
    given: No order with id="ord_nonexistent" exists
    when: POST /api/v1/refunds with order_id="ord_nonexistent"
    then: Returns 404 with code="ORDER_NOT_FOUND"
    priority: high

  - name: Duplicate refund via idempotency key
    type: integration
    given: A refund already exists with idempotency_key="idem_123"
    when: POST /api/v1/refunds with same idempotency_key
    then: Returns 200 (not 201) with existing refund object
    priority: critical

  - name: Refund other user's order
    type: integration
    given: Order belongs to user_id="user_other"
    when: POST /api/v1/refunds authenticated as user_id="user_me"
    then: Returns 403 with code="FORBIDDEN"
    priority: high

  - name: Refund non-refundable order
    type: integration
    given: Order exists with status="pending"
    when: POST /api/v1/refunds
    then: Returns 422 with code="ORDER_NOT_REFUNDABLE"
    priority: medium

acceptance_criteria:
  - All 7 scenarios have passing pytest tests
  - Integration tests use testcontainers for PostgreSQL
  - Stripe API mocked using pytest-mock
  - pytest-cov reports >= 80% coverage for src/domains/refunds/
  - Tests run in CI via pytest -v --cov=src/domains/refunds tests/
  - All tests use @pytest.mark.asyncio for async support

dependencies:
  blueprints:
    - BP-001  # Backend implementation must exist to test
  fixtures:
    - Test database with order table
    - Test user with authentication token
    - Stripe mock responses for success/failure

Security Blueprint

For authentication, authorization, security hardening.

Example: Refund Endpoint Security (Python/FastAPI)

blueprint_id: BP-005
version: v1
issue_id: ISSUE-123
owner: security
status: draft

stack:
  language: python
  framework: fastapi
  auth: jwt

scope:
  service: payment-service
  domain: refunds

artifacts:
  - path: src/domains/refunds/permissions.py
    type: create
    description: |
      Define permission constants and checks:

      class RefundPermissions(str, Enum):
          CREATE = "refund:create"
          READ = "refund:read"
          APPROVE = "refund:approve"

      def can_refund_order(user: User, order: Order) -> bool:
          """Check if user can refund this specific order."""
          if user.has_role("admin"):
              return True
          if user.has_role("merchant") and order.merchant_id == user.merchant_id:
              return True
          return False

  - path: src/domains/refunds/dependencies.py
    type: create
    description: |
      Create FastAPI dependencies for auth:

      async def require_refund_permission(
          current_user: User = Depends(get_current_user),
          order: Order = Depends(get_order_from_request)
      ) -> User:
          if not can_refund_order(current_user, order):
              raise HTTPException(status_code=403, detail={"code": "FORBIDDEN", "message": "Cannot refund this order"})
          return current_user

  - path: src/middleware/rate_limit.py
    type: modify
    description: |
      Add to existing rate limiter configuration:

      RATE_LIMITS = {
          ...existing...,
          "/api/v1/refunds": RateLimitConfig(
              requests_per_minute=10,
              requests_per_hour=100,
              key_func=lambda req: f"refund:{req.state.user.id}"  # per-user limit
          )
      }

  - path: src/domains/refunds/audit.py
    type: create
    description: |
      Create audit logging for refund operations:

      async def log_refund_attempt(
          user_id: str,
          order_id: str,
          amount_cents: int,
          success: bool,
          error_code: str | None = None
      ) -> None:
          """Log all refund attempts for security audit trail."""
          await audit_logger.log(
              event_type="refund.attempt",
              user_id=user_id,
              metadata={
                  "order_id": order_id,
                  "amount_cents": amount_cents,
                  "success": success,
                  "error_code": error_code,
                  "ip_address": get_client_ip(),
                  "user_agent": get_user_agent()
              }
          )

security_requirements:
  authentication:
    type: jwt
    requirements:
      - Valid JWT in Authorization header (Bearer scheme)
      - Token must not be expired (exp claim)
      - Token must contain user_id and roles claims
      - Token signature verified against JWKS endpoint
  authorization:
    type: rbac
    rules:
      - role: admin
        permissions: [refund:create, refund:read, refund:approve]
        constraints: none
      - role: merchant
        permissions: [refund:create, refund:read]
        constraints:
          - order.merchant_id == user.merchant_id
      - role: support
        permissions: [refund:read]
        constraints:
          - read-only access
  data_protection:
    encryption:
      - Refund amounts encrypted at rest (AES-256)
      - All API traffic over TLS 1.3
    pii_fields:
      - customer_email excluded from refund records
      - customer_name excluded from refund records
  rate_limiting:
    enabled: true
    limits:
      per_user: 10 requests/minute, 100 requests/hour
      per_ip: 30 requests/minute
      per_order: 3 refund attempts lifetime

exposure_points:
  - endpoint: POST /api/v1/refunds
    risk_level: critical
    mitigations:
      - JWT authentication required
      - RBAC permission check (can_refund_order)
      - Idempotency key prevents replay attacks
      - Amount validated server-side against order total
      - Audit log for all attempts (success and failure)
      - Rate limit per user and per IP
      - Alert on >5 failed refunds per user per hour

acceptance_criteria:
  - All refund endpoints require valid JWT
  - Users cannot refund orders they don't own (403 Forbidden)
  - Rate limiter returns 429 when exceeded
  - All refund attempts logged to audit trail
  - No PII in error messages or logs
  - OWASP ZAP scan passes with no high/critical findings

dependencies:
  blueprints:
    - BP-001  # Backend implementation

SRE Blueprint

For observability, alerting at user journey level, reliability.

Key Principle: Maigret creates alerts tied to user journey IDs from the issue definition. This enables business-level monitoring ("Is AUTH-001 working?") not just technical metrics.

Schema

blueprint_id: BP-{NNN}
version: v{N}
issue_id: ISSUE-{ID}
owner: sre
status: draft | review | approved | rejected

# STACK
stack:
  monitoring: prometheus | datadog | cloudwatch
  dashboards: grafana | datadog
  alerting: alertmanager | pagerduty | opsgenie
  tracing: opentelemetry | jaeger | zipkin

# SCOPE
service:
  name: string
  criticality: P1 | P2 | P3

# USER JOURNEY OBSERVABILITY - Links to issue's user_journeys
user_journey_monitoring:
  - journey_id: string             # e.g., AUTH-001, REFUND-001
    slo:
      availability: string         # e.g., "99.9%"
      latency_p99: string          # e.g., "< 2s"
    alerts:
      - name: string
        condition: string          # PromQL or similar
        severity: critical | warning | info
        runbook: string

# WHAT (use Prometheus/Grafana vocabulary!)
artifacts:
  - path: string
    type: create | modify | delete
    description: string

# METRICS
monitoring:
  metrics:
    - name: string
      type: Counter | Gauge | Histogram | Summary
      description: string
      labels: [string]
      journey_ids: [string]        # Which journeys this metric supports

# DASHBOARDS
dashboards:
  - name: string
    panels: [string]

# ALERTING
alerting:
  rules:
    - name: string
      journey_id: string | null    # Link to user journey
      condition: string
      severity: critical | warning | info
      runbook: string | null

# SLOs (Service Level Objectives)
slos:
  - journey_id: string             # Journey-level SLO
    availability: string
    latency_p99: string
    error_rate: string

# WHY
acceptance_criteria:
  - string

# DEPENDENCIES
dependencies:
  blueprints: [string]

Example: Refund Feature Observability (Prometheus/Grafana)

blueprint_id: BP-006
version: v1
issue_id: ISSUE-123
owner: sre
status: draft

stack:
  monitoring: prometheus
  dashboards: grafana
  alerting: alertmanager
  tracing: opentelemetry
  oncall: pagerduty

service:
  name: payment-service
  criticality: P1

# USER JOURNEY OBSERVABILITY
# Links directly to journeys defined in the feature request
user_journey_monitoring:
  - journey_id: REFUND-001  # "Request refund" journey from issue
    slo:
      availability: "99.9%"
      latency_p99: "< 5s"
    alerts:
      - name: REFUND001_HighFailureRate
        condition: |
          sum(rate(journey_outcome_total{journey="REFUND-001", outcome="failure"}[5m]))
          / sum(rate(journey_outcome_total{journey="REFUND-001"}[5m])) > 0.01
        severity: critical
        runbook: monitoring/runbooks/refund-001-failures.md
      - name: REFUND001_LatencyHigh
        condition: |
          histogram_quantile(0.99, 
            sum(rate(journey_duration_seconds_bucket{journey="REFUND-001"}[5m])) by (le)
          ) > 5
        severity: warning
        runbook: monitoring/runbooks/refund-001-latency.md

  - journey_id: REFUND-002  # "Check refund status" journey
    slo:
      availability: "99.95%"
      latency_p99: "< 500ms"
    alerts:
      - name: REFUND002_LatencyHigh
        condition: |
          histogram_quantile(0.99, 
            sum(rate(journey_duration_seconds_bucket{journey="REFUND-002"}[5m])) by (le)
          ) > 0.5
        severity: warning
        runbook: monitoring/runbooks/refund-002-latency.md

artifacts:
  - path: monitoring/dashboards/refunds.json
    type: create
    description: |
      Create Grafana dashboard JSON:
      - Title: "Refunds - User Journey Health"
      - Datasource: Prometheus
      - Row 1: Journey Health Overview
        1. Stat: journey_outcome_total{journey=~"REFUND-.*", outcome="success"} / journey_outcome_total{journey=~"REFUND-.*"}
           Title: "REFUND-001 Success Rate"
        2. Stat: Same for REFUND-002
           Title: "REFUND-002 Success Rate"
      - Row 2: Journey Latency
        3. Heatmap: journey_duration_seconds_bucket{journey="REFUND-001"}
           Title: "REFUND-001 Latency Distribution"
        4. Graph: histogram_quantile(0.99, journey_duration_seconds_bucket{journey=~"REFUND-.*"})
           Title: "P99 Latency by Journey"
      - Row 3: Technical Metrics
        5. Graph: rate(refund_requests_total[5m]) by (status)
           Title: "Request Rate"
        6. Graph: refund_stripe_api_duration_seconds
           Title: "Stripe API Latency"
      - Variables:
        - $journey: REFUND-001, REFUND-002

  - path: monitoring/alerts/refunds.yaml
    type: create
    description: |
      Create PrometheusRule custom resource with journey-level alerts:

      apiVersion: monitoring.coreos.com/v1
      kind: PrometheusRule
      metadata:
        name: refund-journey-alerts
        namespace: monitoring
        labels:
          journey_domain: REFUND
      spec:
        groups:
          - name: refund-journeys
            rules:
              # REFUND-001: Request refund journey
              - alert: REFUND001_HighFailureRate
                expr: |
                  sum(rate(journey_outcome_total{journey="REFUND-001", outcome="failure"}[5m]))
                  / sum(rate(journey_outcome_total{journey="REFUND-001"}[5m])) > 0.01
                for: 5m
                labels:
                  severity: critical
                  journey_id: REFUND-001
                  team: payments
                annotations:
                  summary: "User journey REFUND-001 failure rate above 1%"
                  description: "Users are unable to complete refund requests"
                  runbook_url: "https://runbooks.acme.com/refund-001-failures"

              - alert: REFUND001_LatencyP99High
                expr: |
                  histogram_quantile(0.99, 
                    sum(rate(journey_duration_seconds_bucket{journey="REFUND-001"}[5m])) by (le)
                  ) > 5
                for: 10m
                labels:
                  severity: warning
                  journey_id: REFUND-001
                annotations:
                  summary: "REFUND-001 journey P99 latency above 5s"

              # REFUND-002: Check refund status journey  
              - alert: REFUND002_LatencyHigh
                expr: |
                  histogram_quantile(0.99, 
                    sum(rate(journey_duration_seconds_bucket{journey="REFUND-002"}[5m])) by (le)
                  ) > 0.5
                for: 5m
                labels:
                  severity: warning
                  journey_id: REFUND-002
                annotations:
                  summary: "REFUND-002 journey P99 latency above 500ms"

          - name: refund-technical
            rules:
              - alert: StripeAPIErrors
                expr: sum(rate(refund_stripe_api_errors_total[5m])) > 1
                for: 5m
                labels:
                  severity: warning
                annotations:
                  summary: "Stripe API errors detected"

  - path: monitoring/runbooks/refund-001-failures.md
    type: create
    description: |
      Create journey-specific runbook:

      # REFUND-001: Request Refund - Failures Runbook

      ## Journey Definition
      User wants to request a refund for a completed order.

      ## Alert: REFUND001_HighFailureRate

      ### Symptoms
      - Journey failure rate exceeds 1% over 5 minutes
      - Users unable to complete refund requests

      ### Investigation Steps
      1. Check journey dashboard: monitoring/dashboards/refunds (filter journey=REFUND-001)
      2. Identify failure step: `kubectl logs -n payments -l app=payment-service | grep "REFUND-001" | grep "error"`
      3. Check dependent services:
         - Stripe API: https://status.stripe.com
         - Order service: /api/v1/orders/health
      4. Check recent deployments: `kubectl rollout history deployment/payment-service -n payments`

      ### Common Causes
      - Stripe API rate limiting or outage
      - Order service unavailable
      - Invalid Stripe API key

      ### Resolution
      - If Stripe issue: Enable circuit breaker, notify users
      - If order service: Check order-service pods, restart if needed
      - If API key: Rotate in AWS Secrets Manager, restart pods

  - path: src/domains/refunds/observability.py
    type: create
    description: |
      Create journey instrumentation helpers:

      from prometheus_client import Counter, Histogram
      from opentelemetry import trace

      # Journey-level metrics (business outcome)
      JOURNEY_OUTCOME = Counter(
          "journey_outcome_total",
          "User journey outcomes",
          ["journey", "outcome"]  # outcome: success, failure, abandoned
      )

      JOURNEY_DURATION = Histogram(
          "journey_duration_seconds",
          "User journey duration",
          ["journey"],
          buckets=[0.1, 0.5, 1, 2, 5, 10, 30]
      )

      @contextmanager
      def track_journey(journey_id: str):
          """Context manager to track user journey execution."""
          tracer = trace.get_tracer(__name__)
          with tracer.start_as_current_span(f"journey:{journey_id}") as span:
              span.set_attribute("journey.id", journey_id)
              start = time.time()
              try:
                  yield span
                  JOURNEY_OUTCOME.labels(journey=journey_id, outcome="success").inc()
              except Exception as e:
                  JOURNEY_OUTCOME.labels(journey=journey_id, outcome="failure").inc()
                  span.set_attribute("journey.error", str(e))
                  raise
              finally:
                  JOURNEY_DURATION.labels(journey=journey_id).observe(time.time() - start)

monitoring:
  metrics:
    # Journey-level metrics (business outcomes)
    - name: journey_outcome_total
      type: Counter
      description: User journey outcomes (success/failure/abandoned)
      labels: [journey, outcome]
      journey_ids: [REFUND-001, REFUND-002]
      instrumentation: |
        JOURNEY_OUTCOME = Counter(
            "journey_outcome_total",
            "User journey outcomes",
            ["journey", "outcome"]
        )

    - name: journey_duration_seconds
      type: Histogram
      description: End-to-end user journey duration
      labels: [journey]
      buckets: [0.1, 0.5, 1, 2, 5, 10, 30]
      journey_ids: [REFUND-001, REFUND-002]

    # Technical metrics (implementation details)
    - name: refund_requests_total
      type: Counter
      description: Total refund API requests
      labels: [status, error_code]
      journey_ids: [REFUND-001]

    - name: refund_stripe_api_duration_seconds
      type: Histogram
      description: Stripe API call latency
      labels: [endpoint, status_code]
      journey_ids: [REFUND-001]

# Journey-level SLOs
slos:
  - journey_id: REFUND-001
    name: "Request Refund"
    availability: "99.9% of refund requests succeed"
    latency_p99: "< 5s end-to-end"
    error_rate: "< 1% non-user-error failures"

  - journey_id: REFUND-002
    name: "Check Refund Status"
    availability: "99.95% of status checks succeed"
    latency_p99: "< 500ms"
    error_rate: "< 0.1%"

acceptance_criteria:
  - Journey-level dashboard deployed showing REFUND-001 and REFUND-002 health
  - PrometheusRule with journey-labeled alerts created
  - Journey-specific runbooks reviewed by on-call team
  - PagerDuty integration routes journey alerts to payments team
  - SLO tracking enabled per journey in Prometheus
  - OpenTelemetry tracing spans include journey_id attribute

dependencies:
  blueprints:
    - BP-001  # Metrics instrumented in backend code
    - BP-003  # ServiceMonitor configured in deployment

FinOps Blueprint

[Schema and example remain the same as before - FinOps vocabulary is already specific to AWS/cost tools]


Blueprint Creation Process

Step 1: Architect Receives Issue

Baron assigns the issue to Veuve (features) or Duc (tech/bugs):

{
  "id": "c001",
  "timestamp": "2026-01-13T10:00:00Z",
  "agent": "baron",
  "type": "assignment",
  "message": "Issue ISSUE-123 assigned for blueprint creation",
  "refs": ["issue.md"]
}

Step 2: Architect Analyzes & Produces Blueprints

The architect: 1. Reads the issue acceptance criteria and user journeys 2. Identifies affected domains and their technology stacks 3. Creates one blueprint per domain using framework-specific language 4. Posts delivery comment

{
  "id": "c002",
  "timestamp": "2026-01-13T12:00:00Z",
  "agent": "duc",
  "type": "delivery",
  "message": "Created 5 blueprints for refund feature (TDD order)",
  "refs": [
    "BP-001-tests-v1.yaml",
    "BP-002-backend-v1.yaml",
    "BP-003-frontend-v1.yaml",
    "BP-004-infra-v1.yaml",
    "BP-005-sre-v1.yaml"
  ]
}

Step 3: Baron Validates

Baron reviews each blueprint against validation criteria (see below).

Step 4: Iterate or Approve

If rejected, architect creates new version. If approved, Baron copies to domain sub-issues.


Two-Layer Validation

Blueprint Creation uses the same two-layer validation as all phases:

Layer 1: Deterministic Validators (Fast, Code-Based)

Validator Purpose
SchemaValidator Blueprint matches BlueprintCreationOutput schema
RequiredFieldsValidator Required fields present (status, result, feedback)
ValueInSetValidator Status is success, blocked, or needs_clarification

Layer 2: Baron LLM Validation (Semantic Quality)

After deterministic checks pass, Baron validates semantic quality:

Validate Blueprint Creation output:
1. COMPLETENESS: Are all components defined?
2. FEASIBILITY: Is the architecture implementable?
3. TRACEABILITY: Does blueprint address WRD acceptance criteria?
4. QUALITY: Are interfaces well-defined?

Return: pass | flag | escalate
Verdict Action
pass Continue to Phase 3 (Blueprint Review)
flag Log warning, continue
escalate Abort run, human must review

Baron's Validation Criteria

Check Description
Schema compliance All required fields present and valid
Stack declaration Technology stack clearly specified
Technical specificity Artifact descriptions use framework vocabulary, not vague language
Scope alignment Blueprint addresses issue acceptance criteria
Domain correctness Owner matches the content (e.g., infra in devops, not backend)
Interface consistency Contracts match across blueprints (e.g., frontend uses backend's API)
Dependency validity Referenced blueprints exist
Completeness No obvious gaps in coverage

Validation Output

{
  "id": "c003",
  "timestamp": "2026-01-13T14:00:00Z",
  "agent": "baron",
  "type": "review",
  "status": "rejected",
  "message": "BP-001 rejected: Artifact descriptions too vague",
  "refs": ["BP-001-backend-v1.yaml"],
  "details": {
    "blueprint": "BP-001",
    "errors": [
      {
        "field": "artifacts[1].description",
        "issue": "Too vague: 'Refund business logic'",
        "suggestion": "Specify class name, method signatures, dependencies (e.g., 'Create RefundService class with async def create_refund(...)')"
      }
    ]
  }
}

Output

On successful validation, Duc produces structured output:

Phase Result

class BlueprintCreationResult(BaseModel):
    overview: str
    components: list[ComponentDefinition]
    interfaces: list[InterfaceDefinition] = []
    data_models: list[DataModelDefinition] = []
    files_to_create: list[FileDefinition] = []
    files_to_modify: list[FileModification] = []

Agent Feedback (For RL)

Every phase output includes feedback scores:

class AgentFeedback(BaseModel):
    scores: FeedbackScores           # 4 dimensions (0.0-1.0)
    traceability: list[TraceabilityItem]
    gaps: list[Gap]
    suggestions: list[Suggestion]

What Happens Next

  1. Phase output JSON saved to data/wrds/{wrd_id}/runs/{run_id}/phases/blueprint_creation.json
  2. Proceeds to Phase 3 (Blueprint Review) where Baron reviews the blueprint
  3. After approval, domain agents receive their specific instructions

Phase 03 (Blueprint Review) documentation is coming soon.


TDD Workflow Context

After Blueprint Review (Phase 3), the workflow follows TDD order:

Phase Agent Description
4-5 Marie Test Planning + Implementation (tests written FIRST)
6-7 Dede Backend Planning + Implementation
8-9 Dali Frontend Planning + Implementation
10-11 Maigret SRE Planning + Implementation
12-13 Gustave DevOps Planning + Implementation

Version History

Version Date Changes
0.1 2026-01-13 Initial blueprint schemas with examples
0.2 2026-01-13 Added "Speak the Implementer's Language" principle with technology-specific examples
0.3 2026-01-13 Made YAML format mandatory for all blueprints
0.4 2026-01-13 Added Maigret (SRE) with user journey-level observability
0.5 2026-01-13 Corrected agent roles, TDD order (Marie first), removed FinOps
1.0 2026-01-17 Added two-layer validation (deterministic + Baron LLM), feedback structure for RL, 13-phase TDD context