# Schema Reference

Specs are authored as YAML with three required sections: `requirements`, `design`, and `tasks`.

## Spec Content Format

```yaml title="Example spec"
requirements: |
  Users must be able to create, read, update, and delete
  API resources via REST endpoints. All endpoints require
  authentication. Rate limiting at 120 req/min.

design: |
  FastAPI router with Pydantic request/response models.
  SQLite storage with parameterized queries. Session-based
  auth via existing middleware. OpenAPI schema auto-generated.

tasks:
  - id: setup-router
    summary: Create FastAPI router skeleton with auth middleware

  - id: add-models
    summary: Define Pydantic request/response models with validation

  - id: implement-crud
    summary: Implement CRUD endpoints with parameterized queries
    depends_on:
      - setup-router
      - add-models

  - id: add-tests
    summary: Write endpoint tests covering happy path and error cases
    depends_on:
      - implement-crud
```

## requirements

| Field | Type | Required | Description |
|---|---|---|---|
| `requirements` | string | Yes | Free-form text describing what the feature must accomplish. Must be non-empty. |

The requirements section captures the "what" and "why" — acceptance criteria, constraints, user stories, or business rules. There is no prescribed internal format; use whatever structure works for your team.

## design

| Field | Type | Required | Description |
|---|---|---|---|
| `design` | string | Yes | Free-form text describing the technical approach. Must be non-empty. |

The design section captures the "how" — architecture decisions, API contracts, data models, or integration patterns. Like requirements, the internal format is free-form.

## tasks

| Field | Type | Required | Description |
|---|---|---|---|
| `tasks` | list | Yes | A list of task objects. May be empty but must be present. |

Each task is a mapping with the following fields:

### Task Fields

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | string | Yes | Unique identifier within the spec. Must be non-empty. |
| `summary` | string | Yes | Human-readable description of the task. Must be non-empty. |
| `depends_on` | list of strings | No | Task IDs that this task depends on. Defaults to empty. |

## Validation Rules

When a spec is created or updated, the content is validated:

1. **Required sections** — `requirements`, `design`, and `tasks` must all be present
2. **Non-empty strings** — `requirements` and `design` must be non-empty strings
3. **Task list** — `tasks` must be a list (can be empty)
4. **Task IDs** — each task must have a non-empty string `id`
5. **No duplicate IDs** — task IDs must be unique within the spec
6. **Valid references** — every `depends_on` entry must reference an existing task ID in the same spec
7. **No circular dependencies** — the task dependency graph must be acyclic

Validation errors return a `SpecValidationError` with a descriptive message. Common error messages:

| Error | Cause |
|---|---|
| `'requirements' must be a non-empty string` | Missing or empty requirements section |
| `'design' must be a non-empty string` | Missing or empty design section |
| `'tasks' must be a list` | Tasks section is not a YAML list |
| `Task at index N must have a non-empty string 'id'` | Task missing its `id` field |
| `Duplicate task ID: 'X'` | Two tasks share the same ID |
| `Task 'X' depends on unknown task 'Y'` | `depends_on` references a task that doesn't exist |
| `Circular dependency detected involving task 'X'` | Task dependency graph has a cycle |

## Phase Metadata (read-only)

Phase approval state is stored in the artifact's metadata, not in the authored YAML content. This metadata is visible in API responses but is not user-editable — it is set via the `approve` and `unapprove` commands.

Each phase has:

| Field | Type | Description |
|---|---|---|
| `status` | string | `draft`, `approved`, or `stale` |
| `approved_at` | ISO 8601 string or null | Timestamp of last approval |
| `approved_by` | string or null | User who last approved |
| `approved_at_version` | integer or null | Content version number at time of approval |

When a phase becomes `stale` via invalidation, the `approved_at`, `approved_by`, and `approved_at_version` fields are preserved. This allows deriving a stale reason: "requirements changed since approval at version N."

## Complete Example

A realistic spec for an API feature with task dependencies:

```yaml title="@acme/spec/user-api"
requirements: |
  The user management API must support:
  - Create user with email, display name, and role
  - Read user by ID or list with pagination
  - Update user profile fields (not role)
  - Deactivate user (soft delete, no hard delete)
  - Role changes require admin approval

  Non-functional:
  - All endpoints authenticated via session middleware
  - Rate limited at 120 requests per minute
  - Input validated server-side with Pydantic
  - Parameterized queries only (no SQL concatenation)

design: |
  New FastAPI router at /api/users mounted in app.py.
  Pydantic models: CreateUserBody, UpdateUserBody, UserResponse.
  Storage functions in services/user_storage.py using the
  existing ThreadSafeConnection pattern. Soft delete via
  is_active boolean column. Role change endpoint separate
  from profile update, gated by admin check.

tasks:
  - id: schema
    summary: Add users table to db.py with migration

  - id: storage
    summary: Implement user CRUD in services/user_storage.py
    depends_on:
      - schema

  - id: models
    summary: Define Pydantic request/response models

  - id: router
    summary: Create routers/users.py with all endpoints
    depends_on:
      - storage
      - models

  - id: admin-gate
    summary: Add admin role check for role-change endpoint
    depends_on:
      - router

  - id: tests
    summary: Unit tests for storage + router, integration test for auth
    depends_on:
      - router
      - admin-gate
```
