---
title: Requirements
description: A document carries its requirements beside the screens that answer them — what the product must do, the criteria that say it does, and links validate() checks.
status: shipped
url: https://getairframe.com/framewerk/core/requirements
---

> Part of the Framewerk docs. Index of every page: /framewerk/llms.txt

# Requirements

**Requirements** say what the product must do, in the same document as the screens that answer them: each one links the screens it is about, and `validate()` reports a link to a screen that is not there.

Requirements are a draft part of Framewerk 1: the shape may change between minor versions until it is marked stable. Screens, components, flows and styles are stable.

## Examples

A screen, and the requirement it answers:

**A requirement linked to its screen**

```json
{
  "formatVersion": "1.0",
  "drafts": ["requirements"],
  "screens": [
    {
      "id": "menu",
      "layout": {
        "_id": "menu-root",
        "type": "column",
        "gap": 12,
        "padding": 16,
        "children": [
          { "_id": "h", "type": "text", "variant": "heading", "content": "Coffee Cart" },
          { "_id": "go", "type": "button", "variant": "primary", "content": "Order", "action": "navigate(order)" }
        ]
      }
    },
    {
      "id": "order",
      "layout": { "_id": "order-root", "type": "text", "content": "One espresso, on its way." }
    }
  ],
  "flows": [{ "from": "menu", "to": "order", "trigger": "Order button" }],
  "flowPackages": [{ "id": "order-flow", "name": "Order a coffee", "screens": ["menu", "order"] }],
  "requirements": [
    {
      "id": "order-a-coffee",
      "title": "A customer orders a coffee from the menu",
      "type": "user-story",
      "scope": "capability",
      "source": "stakeholder",
      "priority": "P0",
      "status": "implemented",
      "linkedScreens": ["menu", "order"],
      "linkedFlows": ["order-flow"],
      "criteria": [
        { "id": "order-button", "text": "The menu has an Order button", "verified": true },
        { "id": "order-confirmed", "text": "Ordering opens a screen that confirms the drink", "verified": true }
      ]
    }
  ]
}
```

A capability and a task under it:

**A capability and its task**

```json
{
  "formatVersion": "1.0",
  "drafts": ["requirements"],
  "screens": [
    {
      "id": "menu",
      "stateVars": { "oatMilk": { "type": "boolean", "default": "false", "label": "Oat milk" } },
      "layout": {
        "_id": "menu-root",
        "type": "column",
        "gap": 12,
        "padding": 16,
        "children": [
          { "_id": "h", "type": "text", "variant": "heading", "content": "Coffee Cart" },
          { "_id": "oat", "type": "button", "content": "Toggle oat milk", "action": "toggleState(oatMilk)" },
          { "_id": "badge", "type": "badge", "content": "Oat milk", "showWhen": "$oatMilk == true" }
        ]
      }
    }
  ],
  "requirements": [
    {
      "id": "choose-a-milk",
      "title": "A customer chooses the milk in their drink",
      "type": "user-story",
      "scope": "capability",
      "source": "research",
      "priority": "P1",
      "status": "accepted",
      "linkedScreens": ["menu"]
    },
    {
      "id": "oat-milk-toggle",
      "title": "Oat milk is one tap on the menu",
      "type": "requirement",
      "scope": "task",
      "parentCapabilityId": "choose-a-milk",
      "source": "research",
      "priority": "P1",
      "status": "verified",
      "linkedScreens": ["menu"],
      "criteria": [
        { "id": "toggle", "text": "A button switches oat milk on and off", "verified": true },
        { "id": "badge", "text": "An Oat milk badge shows while it is on", "verified": true }
      ]
    }
  ]
}
```

## Anatomy

```json
{
  "id": "order-a-coffee",
  "title": "A customer orders a coffee from the menu",
  "type": "user-story",
  "scope": "capability",
  "source": "stakeholder",
  "priority": "P0",
  "status": "implemented",
  "linkedScreens": ["menu", "order"]
}
```

Seven fields are required: `id`, `title`, `type`, `scope`, `source`, `priority` and `status`.

Because requirements are draft, a document that states a `formatVersion` and carries any names them at the top level: `"drafts": ["requirements"]`. `validate()` reports an error when it does not. See [The document](/framewerk/core/document.md).

**Scope** is the level. A `capability` says what the product does and never closes; a `task` is a piece of the work that makes a capability true, and names it in `parentCapabilityId`.

**Status** is how far the work got: `proposed`, `accepted`, `in-progress`, `implemented`, `blocked`, `verified`. Why a requirement stopped being live is a separate field, `resolution`: `fixed`, `wont-fix`, `duplicate`, `cannot-reproduce`, `superseded`. A superseded requirement may name its replacement in `supersededBy`.

**Type** is one of `user-story`, `requirement`, `acceptance-criteria`, `bug`, `feedback`, `qa-note`. **Source** is one of `stakeholder`, `research`, `agent`, `audit`. **Priority** is `P0`, `P1` or `P2`. A bug may carry a `severity`: `critical`, `major`, `minor`, `cosmetic`.

**Criteria** are the acceptance checklist. Each has an `id` unique within its requirement, a `text`, and `verified`.

**Links** point into the same document:

| Field | Names |
| --- | --- |
| `linkedScreens` | ids from `screens` |
| `linkedFlows` | ids from `flowPackages` |
| `personas` | ids from `project.personas` |
| `parentCapabilityId`, `supersededBy` | the `id` of another requirement |

`validate()` reports a missing `scope`, a value outside a list, and a `linkedScreens`, `linkedFlows`, `personas` or `parentCapabilityId` entry that names nothing in the document, as errors. A `supersededBy` that names nothing is a warning.

Optional fields: `feature` (the area of the product), `description`, `tags`, `blockerReason`, `refCode` (a short handle people say aloud), `reporter` and `assignee` (a name to show, never an account id), and the ISO 8601 datetimes `createdAt`, `updatedAt`, `startedAt` and `closedAt`.

A tool may add its own fields to a requirement. `validate()` passes over fields it does not know.

The value lists are exported, so a form or a schema reads them and does not retype them:

```ts
import { REQUIREMENT_STATUSES, validate, type Requirement } from "@framewerk/spec";
```

## What the format does not carry

A requirement in a document is a record. Everything that does work on one stays in the tool that holds the document. Airframe adds, and does not publish:

- the account behind an assignee or reporter, the build a requirement rides, and its milestone
- Features, test plans, the roadmap and boards
- the agents and MCP server that write requirements, status reconciliation, alignment against code, and the test walker

## Stability

Draft. Requirements are a draft part of Framewerk 1: the shape may change between minor versions until it is marked stable. Screens, components, flows and styles are stable.
