---
title: "Record types"
description: "Record types in Fir: any file may live in a plan's home, typed or not; twenty-two built-in types and packs; your own types in YAML with JSON Schema fields and CEL rules, versioned with lossless conversion; made in the editor, by pasting YAML or by asking an agent."
url: https://frazer.ai/docs/source/record-types
index: https://frazer.ai/docs/source/plans/llms.txt
tokens: 8841
---

> For agents: this page is indexed at https://frazer.ai/docs/source/plans/llms.txt, and every page at https://frazer.ai/llms.txt. Copy commands from this file exactly.

# Record types

A **type** gives a record its structure: fields, sections, states, the links it must carry, the rules it is checked against, who approves it and how much of it an agent session receives. Fir ships twenty-two built-in types and lets every team and every agent add its own, with no ceiling on what a type can say and a bound only on what its rules may cost.

A type is structure you opt into, never a gate on pushing. You can push any file you like, as in any repository; the merge rules decide what blocks, and only for records a pull request changes.

## Any file, typed or not

Any file may live in a plan repository or in a code repository’s `docs/plans/`. Fir finds a file’s type in this order:

1.  The file’s front matter `type:`. A non-Markdown file can name its type in a sidecar `<file>.fir.yaml`, or a type can claim it by pattern (an SLO type claims `slos/*.yaml`).
2.  Otherwise, the most specific `path:` pattern among the types in force for the record’s scope.
3.  Otherwise, a Markdown file is a **doc** and any other file is an **attachment**.

A `doc` is a record like any other: a number at its first merge, versions, links, search, Why, freshness and the rules every record follows (anchors, links, secrets). An attachment (a CSV, an image, a PDF, a notebook, a diagram, a YAML file no type claims) is versioned, searchable, linkable and shown in Why. Nothing is invisible to agents because it has no type.

**A file that breaks its type is kept exactly as written.** Fir parses what it can and reports findings with anchors:

| Problem | Finding | What still works |
| --- | --- | --- |
| A field of the wrong type, a value outside its list, a missing required field | `fir/schema` (error) at the field | Every other field, links, search, Why, the record’s state |
| A section missing or out of order | `fir/required-sections` | The sections present |
| An unknown front matter key | `fir/unknown-field` (warning by default) | The key is kept and shown, never dropped |
| A field Fir owns, written by an author | `fir/status-written` (error) | Fir shows its own value |
| `type:` names a type not in force | `fir/type-unknown` (error); the record reads as a `doc` | Everything a `doc` has |

New `error` findings block the merge of a pull request that changes the record; findings that were already there are the baseline and never block. A push is never refused for a finding.

**Adopt** moves loose files into a type. When a type is added, Fir lists the docs that look like it (“12 docs look like Production incidents”) with what each lacks. Adopting them is a plan of tier task: per file, the front matter to add, the headings to rename and the values to convert, previewed as a diff and merged as one pull request or a stack of at most 50 files each. Numbers, links and history do not change; the files only gain structure. An agent can run it end to end (“type these 40 postmortems”) under the same plan and approval.

## Built-in types

| Group | Type | What it answers | A session receives it |
| --- | --- | --- | --- |
| Direction | goal | The outcome the business wants; key results as metric queries | When its plan contributes to the goal |
| Direction | project plan | What the project is for now: goals, now, next, standing decisions | At session start |
| Direction | brief | The problem, who it is for, what success is, the non-goals | When a plan derives from it |
| Direction | RFC `RFC-0012` | A proposal across teams, its alternatives and the decision | On demand |
| Direction | research | Evidence: an inquiry, a benchmark, a teardown, interviews | On demand |
| Work | plan | Why we change it, what done means, the items, decisions and As built | Always, for the session’s own plan |
| Work | milestone | When a set of plans lands, and whether it is on track | When its plan is in scope |
| Work | risk `RISK-007` | What could go wrong, how likely, how bad, and the mitigations | When it threatens the plan or its paths |
| Truth | spec `CAP-4` | Requirements with IDs, each with a proof, and the paths they cover | When the change touches covered paths |
| Truth | decision `ADR-0031` | The options, the recommendations, the answer and its effects in force | When it governs the touched paths |
| Truth | contract | An interface others depend on, its consumers and compatibility policy | When its schema file changes |
| Truth | architecture | Components, their boundaries and the paths each owns | On demand |
| Truth | glossary | Domain words and what not to confuse them with | The terms that appear in the task |
| Truth | threat model `T-3` | Assets, threats and mitigations with their proofs | For security paths |
| Operations | runbook `RB-0007` | How to respond to an alert or operate a service | For the service, and in every incident |
| Operations | SLO | The reliability users get and the error budget left (an OpenSLO file) | For the service’s paths |
| Quality | test plan | What is tested, how, and the pass bar; datasets and graders for evals | When the plan’s criteria name it |
| Quality | review | A structured review against a versioned checklist, with conditions | Its open conditions, always |
| Knowledge | doc | Guides and references; every untyped Markdown file | On demand |
| Knowledge | standard `GO-12` | Rules a team holds itself to, each with a stable slug, a strength (`must`, `should`, `may`) and an enforcement state: `approved` rules advise, `enforced` musts block under the merge rules | Through your organization’s handbooks |
| Knowledge | instructions | What agents should do here: agents files, skills, subagents, rules | Through your organization’s handbooks |
| Knowledge | lesson `L-11` | What a team learned, the evidence and the paths it applies to | When the change touches those paths |

Kinds and tiers cover more without more types:

-   **plan** has the tiers fix, task, program, inquiry, incident and **migration**. A fix adds Current behavior, Expected behavior and Unchanged behavior; a migration adds Remaining, a code query whose count Fir computes, so its progress comes from the code.
-   **decision** has the kind **deprecation**; **contract** the kinds `api`, `event`, `data`, `agent` and `shared-model`; **runbook** the kind **recovery** (RTO, RPO, and drills as its proofs).
-   **review** has templates for production readiness, design, launch, cost, security, privacy, accessibility and vendor reviews.
-   A postmortem is a plan of tier incident. A timeline of milestones and programs is a view, release notes come from plans’ outcomes, issues stay issues, and meeting notes are a type a team adds when it wants them.

Each type has one of three lifecycles, enforced by Fir:

-   **Flow-forward** (plan, RFC, research, postmortem, review): each change gets its own record. A done plan accepts only mechanical versions; new work is a new plan that links the old one.
-   **Living** (spec, contract, architecture, glossary, runbook, threat model, standard, project plan, goal, risk, SLO): the current version is the contract, and material edits need the type’s approver.
-   **Superseded** (decision, lesson): accepted text never changes; a new record replaces it.

A type you make inherits its base’s lifecycle and may tighten it, never loosen it.

## Packs

A **pack** is a versioned, signed package of types, templates, rules, review templates, views and automations. A pack is off until a project or an organization turns it on, and its types stay inactive (nothing compiled, nothing in an agent’s catalog) until a project turns one on or a record names one.

The **Regulated pack** adds **control** (a security or compliance control, the frameworks it satisfies and its evidence), **privacy review** (the data protection impact assessment of GDPR Article 35), **hazard** (hazards, risk controls and their verification, as ISO 14971 and IEC 62304 ask) and **model card** (an AI system’s documentation, in the EU AI Act’s Annex IV headings).

Each industry pack’s types extend a core type, so they count as that type everywhere, and each computes something people otherwise track by hand:

| Pack | Types | What Fir computes |
| --- | --- | --- |
| **Safety** | `safety/fmea` (a risk), `safety/assurance-case` (a spec), `hazard` | Each failure mode’s action priority from its severity, occurrence and detection; an assurance case’s support, so a broken test shows as an unsupported claim |
| **Medical devices** | `med/design-input`, `med/design-review`, `med/verification`, `med/validation`, `med/capa` | A design review’s independence from authorship; trace from user needs to validation; a corrective action’s effectiveness check; the design file as a baseline export |
| **Financial services** | `fin/dora-incident`, `fin/ict-arrangement`, `fin/model-validation` | A major incident’s report deadlines under DORA; the DORA obligations catalog (article, cadence, evidence kinds) and a `fin/tlpt` scheme for threat-led penetration tests; the register of information exported in the official templates; a model’s validation status before use. A compliance owner approves the obligations a program carries; the pack is a starting point, not legal advice |
| **ML research** | `ml/experiment`, `ml/dataset-card`, model card | An experiment’s reproducibility (pinned commit, dataset digest, seed, runs linked); a dataset card’s license and personal-data fields before training |
| **Data platform** | `data/contract` (wraps an ODCS v3 file), `data/product` | A contract change’s compatibility before merge; a product’s consumers from links |
| **Games** | `games/design-doc`, `games/playtest`, a certification checklist the studio supplies | A build’s certification readiness, every question answered with evidence |
| **Hardware** | `hw/change`, `safety/fmea`, `hw/test-report` | A change’s effectivity (which serial numbers or lots), its affected items and the test reports that close it |

```yaml
# pack.yaml (the Financial services pack)
format: record-pack/v1
name: fin
display: Financial services
version: 1.2.0
publisher: frazer
types: [types/dora-incident.yaml, types/ict-arrangement.yaml, types/model-validation.yaml]
templates: [templates/dora-incident.md]
review_templates: [reviews/model-validation.yaml]
views: [views/register-of-information.yaml]
automations: [automations/dora-deadlines.yaml]
signature: {bundle: pack.sigstore.json}
```

-   **Where packs come from:** Frazer’s built-in set, any repository (`fir pack add acme/packs-fintech@1.2.0`), the Hub, or a URL. A company keeps its house types in a pack of its own and installs it in every project.
-   **Install** per organization or project with a lock entry (version and content digest) in `.fir/records.yaml`: `packs: [{name: fin, version: 1.2.0}]`, or `packs: [regulated]` for a built-in pack.
-   **Names** carry the pack’s prefix (`fin/dora-incident`); a collision with an installed type is refused at install.
-   **Trust:** packs are signed; a pack’s automations ask for their grants at install, and a pack cannot raise its own permissions.
-   **Upgrades** are a plan: the manifest diff, each type’s change class, a backtest over your records and the conversions, merged by the pack’s owners; reverting the merge rolls back.
-   **Uninstall** never deletes a record: records read through the pack type’s last definition, then as their base type with every field kept.

## The type language

A type is a file, `.fir/types/<name>.yaml`, reviewed like code. Three open languages, each doing one job: **YAML** for the definition, **JSON Schema 2020-12** for front matter fields, and **CEL** for rules and computed values. The definition format is itself versioned (`format: record-type/v1`) and published as JSON Schema, so an editor or an agent gets completion and validation while writing one.

```yaml
# frazer/records types/risk.yaml (built in)
format: record-type/v1
name: risk
display: Risk
group: work
version: 2
served: [1, 2]
storage: 2
changes:
  - to: 2
    why: "review_by becomes a date Fir checks, not free text"
    steps:
      - {map: {field: review_by, values: {"next quarter": "~"}}}
path: risks/*.md
key: {prefix: RISK, digits: 3}                 # RISK-007, numbered by the server
fields:                                        # JSON Schema 2020-12
  type: object
  required: [likelihood, impact, owner]
  properties:
    likelihood: {enum: [low, medium, high]}
    impact: {enum: [low, medium, high]}
    owner: {type: string, format: principal}
    review_by: {type: string, format: date}
sections:
  - {name: Risk, required: true}
  - {name: Mitigations, required: true, rows: {id: "M\\d+", links: [mitigated_by]}, max_rows: 200}
states:
  initial: open
  transitions:
    - {from: open, to: mitigated, by: fir, when: "every Mitigations row links a done plan"}
    - {from: [open, mitigated], to: accepted, by: approver}
change_rules:
  material: [fields.impact, fields.likelihood, sections.Mitigations.remove]
  tightening: [sections.Mitigations.add]
approval: {route: risk-review}
links:
  allow: [mitigated_by, in_milestone, governs, references]
  require:
    - {kind: mitigated_by, to: plan, min: 1, when: "self.fields.impact == 'high'"}
freshness: {fresh_for: 30d, triggers: [linked_plan_done, review_by]}
context: {load: linked, budget_tokens: 150}
lint:
  extends: [fir:recommended]
  rules:
    - id: risk/high-needs-mitigation
      kind: cel
      given: self
      check: "self.fields.impact != 'high' || size(self.sections.Mitigations.rows) > 0"
      message: "A high-impact risk needs at least one mitigation"
      severity: error
stages:
  open: ["owner set", "review_by within 30 days"]
```

| Part | What it holds |
| --- | --- |
| `fields` | JSON Schema 2020-12 with Fir’s formats (`principal`, `repository`, `record-ref`, `path-glob`, `duration`, `metric-query`, `service`), checked against Fir’s indexes, so `format: principal` fails on a person who left the organization |
| `sections` | Names, order, required or optional (`required_in` states, `required_when` in CEL), row ID patterns, the link kinds a row may carry, size bounds |
| `states` | Transitions and who may make each: `owner`, `approver`, a role, or `fir` for the ones Fir writes from events |
| `change_rules`, `approval` | Which edits are mechanical, tightening or material; the approval route |
| `links` | Allowed kinds, required links, and link kinds of the type’s own |
| `freshness`, `context` | When a record turns stale; how agents load it and its concise form |
| `template`, `examples` | A Markdown template; valid and invalid examples, run as the type’s tests |
| `extends` | The type this one inherits from; every type has a base, so every view, rule and tool that knows the base still works |
| `lint`, `stages` | Record-check rules and checklist items per state ([The planning flow](https://frazer.ai/docs/source/planning-flow#record-checks)) |
| `checks`, `questions`, `score` | Plan checks, cold-review questions and score weights for types that extend `plan` |
| `computed` | Fields Fir computes in CEL from the record, its links and the clock |
| `automations`, `views`, `notify` | An event and actions; default views; who is told of which transition |
| `levels` | The levels of a plan tree ([Programs](https://frazer.ai/docs/source/programs)) |
| `effect_kinds` | Kinds of decision effects of your own ([Decisions](https://frazer.ai/docs/source/decisions#effect-kinds-of-your-own)) |
| `stack_shapes` | Layer templates a plan of this type starts from ([Stacks and conflicts](https://frazer.ai/docs/source/stacks)) |
| `formats`, `unknown_fields`, `home` | Non-Markdown files the type claims; what an unknown key does; a default home for new records |
| `version`, `served`, `storage`, `deprecated`, `changes` | Versions, below |

### Rules in CEL

A rule sits next to what it checks (`x-rules:` inside a field’s schema, `rules:` inside a section) or in the type’s `lint:` block; either way it is one engine and one list. A rule may read `old_self`, the record at the base version, so “severity is fixed once mitigated” is a transition rule. `message_expression` builds the message (“At most 72 hours; this window is 96 hours”), `reason` gives a code, `field_path` anchors the finding, and `variables` name sub-expressions a type’s rules share.

The `records` environment gives a rule the parsed record (`fields`, `sections`, rows, `state`, `links` and `status`), linked records, lookups in the repository, metric queries and a working-days calendar. The clock enters only through an event’s time or a computed field, so a rule’s result depends on stored data alone. A new or changed rule never blocks an unchanged part of an old record: what it finds there becomes the baseline.

### Computed fields and status

**Status** is what Fir owns: states that Fir moves, computed fields, proof states, counters, freshness, findings and a decision’s force. It lives in the parsed record, never in the file, so it never conflicts in a merge and no agent can type it. A **computed field** is a CEL expression that Fir recomputes when an input changes:

```yaml
computed:
  remaining_uses: "repo.code_count(self.fields.remaining_query)"   # a migration plan's progress
```

A **baseline** is status too: each approval writes one per node it dates (`baselines`, `first_approval`), with a `replan` entry when a date moves (its reason, the decision cited, the milestones moved with their days) and `correction` as its own version kind. A milestone may be `external`, naming who it is promised to.

Computed fields drive rules, checklist items, views, notifications and agents, not only display.

### Links a type allows and requires

```yaml
links:
  allow: [follows, hotfix_for, learned_from, planned_in, references]
  require:
    - {kind: follows, to: runbook, min: 1, at: [mitigated, follow_ups, closed]}
    - {kind: planned_in, min: 1, when: "size(self.sections['Follow-ups'].rows) > 0"}
  kinds:
    - {name: hotfix_for, inverse: hotfixes, from: pull_request, to: prod-incident, cardinality: many, traced: true}
```

A missing required link is a finding at the section or field that needs it, with a suggested link (“#469 covers these paths”). `oslc:` maps a kind of your own onto OSLC’s vocabulary for exchange.

### Cost, and no ceiling

Each rule’s worst-case cost is estimated when the type compiles, from the sizes the type declares (or the defaults: strings of 4,096 characters, lists of 256 items, 10,000 rows, 10,000 links, each one raisable). A rule may cost at most **1,000,000 units**, estimated, and a record at most **10,000,000** at run time, metered as rules run. A rule over budget is refused when the type compiles, with the number and the fix (“declare smaller `max_rows` on Timeline, or run it as a check in Frazer CI”); a rule that exhausts its budget at run time yields `fir/rule-budget` with its own severity, so an `error` rule fails closed. Patterns are priced by what they can match, not by their length.

There is no limit on the number of types, fields, sections, states, link kinds or rules. Each level below lifts the previous one’s limit, and a team climbs only as far as it needs:

| Level | What it can express | Runs where |
| --- | --- | --- |
| 1\. YAML | Fields, sections, states, change rules, link kinds and required links, routes, freshness, context, views, notifications, levels, effect kinds, stack shapes | Fir, declaratively |
| 2\. CEL | Conditions and values over the record, its links and its repository: rules, computed fields, transitions, automation guards | Fir, in process, within the cost budget |
| 3\. A check in Frazer CI | Anything code can say; it reads `fir record view --json` and writes a check result | Frazer CI, sandboxed |
| 4\. A Frazer App | Its own views, actions and screens in the console, and conversion code between type versions | Frazer Apps |

A type may not change a state Fir owns (`done`, `merged`, proof states, `in_force`), let an agent approve, or relax a rule your organization locked. An automation that triggers itself on one record more than 10 times an hour is paused.

## Versions: nothing set in rock

A type file is always written at its **current version**, with a `changes:` log: each entry turns version N−1 into N, says **why**, and lists declarative **steps**. `served` lists the versions readers and writers may use; `storage` is the version new and edited records are written at. A record keeps the version it was written at: reading converts it, writing rewrites it at the storage version, and a version leaves `served` only when no record holds it.

| Step | Forward | Backward |
| --- | --- | --- |
| `rename {from, to}` | Renames a field or section | Renames back |
| `move {from, to}` | Moves a value | Moves back |
| `map {field, values}` | Maps values (`S2` → `SEV2`) | Maps back; an unmapped value is archived |
| `wrap {from, to}` | A scalar becomes a list | The first item; the rest is archived |
| `add {field, default}` | Nothing written: the default applies on read | Removes the field; another value is archived |
| `split`, `join` | One field into several, or several into one | The inverse |
| `app {name}` | A Frazer App’s conversion, for logic no step can say | The App’s inverse, or the change is one-way |

What an older version cannot hold goes to the record’s **archive** and comes back when it is read at a newer version, so conversion loses nothing in either direction.

Fir classifies every change to a type before it merges:

| Class | Examples | What happens |
| --- | --- | --- |
| **Compatible** | An optional field, a section, a link kind, a list value, a view, a rule at `warning` | Merges with the type’s owners’ review; no record changes |
| **Tightening** | A required field with a default, a rule at `error`, a stricter pattern | A backtest is attached; old findings become the baseline |
| **Breaking** | A field renamed, retyped or removed; a state or list value removed | A new version with its `changes:` steps; the old version stays served |

Moving every record to the storage version is a plan an agent executes in batches of at most 50 records; the change kind is `mechanical`, so approvals carry. `deprecated:` adds a warning to every read and write at that version. A session that writes a record whose type changed since it last saw it gets one line per change in its context (“prod-incident v2 → v3: customer impact required for SEV1 and SEV2, because of ADR-0041”), and `get_record_type` with `since` returns the changes with their reasons.

## Layers and scope

Types come in layers: built-in types, installed packs, your organization’s handbook types, the project’s types (its plan repository’s `.fir/types/`), then a repository’s `.fir/types/`. A lower layer may tighten (add required sections, fields, rules) and may relax only what the layer above marked `default`. Layers combine so that their order does not matter: each adds constraints, and a contradiction is a finding at the layer that introduced it. `fir type resolve <name>` shows which layer supplied each field, section and rule.

An organization can ship types in a Frazer Context handbook with `locked`, `default` or `suggested`: lock `decision` across every repository, leave `meeting-notes` suggested. Types follow a record’s **scope, never its home**: the project’s types apply to every record of the project, whether it is stored in a code repository’s tree or in the plan repository ([Plan repositories](https://frazer.ai/docs/source/plans#two-homes-one-index)).

## Make a type: three doors

All three end in the same pull request: `.fir/types/<name>.yaml` with its template and examples. Fir validates it against the published schema, compiles it, classifies the change and **backtests** it over every file under its path and every record of its base type, so the pull request shows how many pass, which fail and why, and which docs could be adopted. The type’s approvers are the owners of `.fir/types/`. From the merge on, files under its path are records of that type, and agents see it at once through `list_record_types` and `get_record_type`, so an agent writes a valid record of a type it has never seen.

### The editor

Settings › Plans › Types opens a **Form** that writes the YAML and a **YAML** tab that edits it, side by side. Every change compiles at once and re-runs the backtest live. Tabs: Definition, Versions, Lint, Stages, Automations, Links, Agent view, Packs.

Acme’s platform team writes its production incident type there; it extends the built-in postmortem, so every incident insight and rule still counts it (demo data):

```yaml
# acme/plans .fir/types/prod-incident.yaml
format: record-type/v1
name: prod-incident
display: Production incident
extends: postmortem
key: {prefix: INC, digits: 4}            # INC-0043, numbered by the server
fields:
  required: [severity, services, commander, started_at]
  properties:
    severity: {enum: [SEV1, SEV2, SEV3]}
    services: {type: array, items: {type: string, format: service}}
    commander: {type: string, format: principal}
sections:
  - {name: Impact, required: true}
  - {name: Timeline, required: true, rows: {id: "T\\d+"}}
  - {name: Customer communication, required_when: "self.fields.severity in ['SEV1', 'SEV2']"}
  - {name: Follow-ups, required_in: [follow_ups, closed], rows: {id: "F\\d+", links: [planned_in]}}
states:
  initial: responding
  transitions:
    - {from: responding, to: mitigated, by: commander}
    - {from: mitigated, to: follow_ups, by: commander}
    - {from: follow_ups, to: closed, when: "every follow-up links a plan or an issue"}
automations:
  - event: monitoring.incident.opened
    if: "event.severity in ['SEV1', 'SEV2'] && event.project == 'acme-production'"
    do:
      - create_record: {template: .fir/templates/prod-incident.md}
      - notify: [commander, "owners(event.services)"]
stages:
  closed: ["lessons proposed, or 'none, because …'", "runbooks for the services reviewed after the incident"]
```

When a SEV2 incident opens for `checkout-api`, the automation creates `INC-0043` from the template and notifies the commander and the service’s owners; every session opened for the incident gets the record, runbook `RB-0007` and the service’s SLO in its context; and the record cannot close until `RB-0007` has been reviewed.

### Paste YAML

Settings › Plans › Types › New type › **Paste YAML**, or upload a file, or `fir type add --from <file>`:

1.  **Validate** against the published schema, with line numbers.
2.  **Explain** each line in plain words (“line 7, `extends: decision`: records of this type count as decisions: In force, conflicts, history”). A model’s summary, if you ask for one, is labeled as such.
3.  **Compile** and show each rule’s cost, the change class against any type of that name, and what each model provider’s structured output accepts.
4.  **Backtest** on existing records and docs, and list what Adopt would do.
5.  **Preview** the form, the template and a rendered example record.
6.  **Open a pull request** with the file, the template, the examples and the backtest.

Acme freezes changes to the storefront during peak season (standing decision `SD-3`, “Peak freeze Nov 20 to Dec 1”). tomasz pastes a freeze-exception type (demo data); Fir explains its lines, compiles its rules, backtests last year’s six exception docs (four pass; two lack a rollback runbook and keep that as a baseline), offers to adopt them and opens `acme/plans#90`:

```yaml
# acme/plans .fir/types/freeze-exception.yaml (excerpt)
format: record-type/v1
name: freeze-exception
extends: decision                        # counts as a decision: In force, conflicts, history, precedents
key: {prefix: FX, digits: 4}             # FX-0001, numbered by the server
path: freeze/**/*.md
fields:
  type: object
  required: [window_start, window_end, services, requester, change]
  properties:
    window_start: {type: string, format: date-time}
    window_end:
      type: string
      format: date-time
      x-rules:
        - check: "self.fields.window_end - self.fields.window_start <= duration('72h')"
          message_expression: "'At most 72 hours; this window is ' + string((self.fields.window_end - self.fields.window_start) / duration('1h')) + ' hours'"
    services: {type: array, items: {type: string, format: service}, minItems: 1, maxItems: 8}
sections:
  - {name: Why, required: true}
  - {name: Change, required: true}
  - {name: Rollback, required: true}
links:
  kinds:
    - {name: excepts, inverse: excepted_by, from: freeze-exception, to: decision, cardinality: one}
  require:
    - {kind: excepts, to: decision, min: 1}
    - {kind: follows, to: runbook, min: 1}
computed:
  budget_left_pct: "metric('slo:checkout-capture:budget_left_pct').min_over('1h')"
stages:
  accepted:
    - id: FX-1
      title: The SLO budget is above 25 % when the exception is approved
      check: "self.status.budget_left_pct > 25"
approval: {route: freeze-exceptions}
```

The first record arrives the next day: `FX-0001` “Hotfix window for checkout-api during the freeze”, requested by lena, linked to `SD-3` and runbook `RB-0007`.

### Ask an agent

“Make me a type for design reviews.” The request starts a session whose plan is a **type proposal**, a plan of tier task scoped to `.fir/types/**`:

1.  The agent looks for what exists first: a built-in type, a kind, an installed pack, a pack on the Hub. A new type from nothing is the last resort, because a base gives views, rules and agents for free.
2.  It drafts the type file, the template, valid and invalid examples, rules with their examples, required links and checklist items, then runs `test_record_type`: schema, compile, costs, backtest and the type’s own tests.
3.  It opens the plan with the evidence attached. The checks run, and a cold review by a model of another family with zero context asks what a reviewer would (“who counts as independent?”). The agent answers what the evidence can; the rest are decision rows for the person.
4.  A person approves the plan like any plan, the pull request merges and the type is in force. Agents never approve.

A team that comes up with a better idea a year from now, or works in an industry no pack covers, makes its type the same way.

### Import from other tools

| From | What maps |
| --- | --- |
| Issue forms and issue types | Inputs become fields (a dropdown a list of values, checkboxes booleans); the form’s body becomes the template |
| Jira work types and workflows, and other trackers’ process exports | Fields, required fields, states and transitions; validators that CEL cannot say become checks, listed in a report |
| Sphinx-Needs and StrictDoc grammars, Backstage kinds | Types, fields, link kinds with both names, ID prefixes |
| Notion and Obsidian properties | Fields; a base’s filters and formulas become a view and computed fields |
| JSON Schema | `fields` directly, with one section, Body |

## Everything in front of the agent

`get_record_type` returns a type in three forms:

| Form | What it holds | When the agent gets it |
| --- | --- | --- |
| **Card** (about 300 tokens) | Name, base, purpose, required fields and their values, sections by state, the links it must carry, the error rules’ messages, how to get more | In the context of every session that writes records of the type, re-sent after compaction |
| **Full** (under 5,000 tokens) | The definition at the version in force, every rule with message, severity, cost and examples, the states, the template, a valid example, invalid snippets, and the changes since the version the caller last saw | On demand: `get_record_type`, or `fir record types --show` |
| **Generation schema** | The fields lowered into a model provider’s documented JSON Schema subset for structured output, plus the constraints checked right after generation | When a harness generates front matter with structured outputs |

The checks then run as the agent writes: the Frazer harness checks every record it saves and hands errors back in the same turn. Fir is designed to compile a 50-field, 40-rule type within 5 ms, to check a keystroke within 1 ms even on a 2,000-row record, to check a push of 20 records within 50 ms, to backtest 10,000 records within 5 seconds and to find a file’s type among 100,000 types within a microsecond.

## Commands

```sh
fir type list
fir type show prod-incident
fir type explain freeze-exception.yaml
fir type test prod-incident
fir type add --from freeze-exception.yaml
fir type adopt prod-incident
fir type resolve plan
fir pack list
fir pack add acme/packs-fintech@1.2.0
fir pack upgrade fin
fir record types --show risk
fir record rules --type prod-incident --test
```

## MCP tools

| Tool | Does | Toolset |
| --- | --- | --- |
| `list_record_types` | Types in force for a scope, as cards; filters by group, pack and base | `records` |
| `get_record_type` | `form: card`, `full` or `generation`; `version`, `since`, `provider` | `records` |
| `explain_record_type` | A definition explained line by line | `records` |
| `test_record_type` | Validate, compile, cost, classify the change and backtest; the report a pull request shows | `records` |
| `draft_record_type` | Start a type proposal plan from a request | `records` |
| `adopt_records` | Find docs that look like a type and draft the adoption plan | `records` |
| `convert_record` | Read or rewrite a record at another served version | `records` |
| `resolve_record_type` | Which layer supplies each part of a type | `records` |
| `list_packs`, `install_pack`, `upgrade_pack` | Packs in scope; installs and upgrades open plans, never apply directly | `records` |

The API is `RecordTypeService` and `PackService` in `frazer.records.v1`. Events `records.type.changed` (with the change class and the why), `records.pack.installed` and `records.pack.upgraded` let agents and Apps react to what changed.

> **Open, no lock-in:** The definition is YAML with a published schema, fields are JSON Schema, rules are CEL and packs are folders of files. An export gives you every type, every pack and every record as Markdown.

## Related

-   [Plan repositories](https://frazer.ai/docs/source/plans): both homes, links and Why
-   [The planning flow](https://frazer.ai/docs/source/planning-flow): the record checks, plan checks and the cold review that run what a type declares
-   [Programs](https://frazer.ai/docs/source/programs): levels and the program tree
-   [Decisions](https://frazer.ai/docs/source/decisions): the `decision` type and effect kinds

## Pages in Fir

- [Overview](https://frazer.ai/docs/source.md)
- [Plan repositories](https://frazer.ai/docs/source/plans.md)
- [Record types](https://frazer.ai/docs/source/record-types.md) (this page)
- [Programs](https://frazer.ai/docs/source/programs.md)
- [Person tasks](https://frazer.ai/docs/source/person-tasks.md)
- [Decisions](https://frazer.ai/docs/source/decisions.md)
- [Decide by conversation](https://frazer.ai/docs/source/decisions/conversation.md)
- [The planning flow](https://frazer.ai/docs/source/planning-flow.md)
- [Loops and learned checks](https://frazer.ai/docs/source/planning-flow/loops.md)
- [Stacks and conflicts](https://frazer.ai/docs/source/stacks.md)
- [Auto-merge and the merge queue](https://frazer.ai/docs/source/merge-queue.md)
- [Sessions](https://frazer.ai/docs/sessions.md)
- [Issues](https://frazer.ai/docs/source/issues.md)
