---
title: Contracts
description: The rules every write is checked against, the verdict they return, and the catalog of rules a collection turns on.
section: concepts
order: 1
---

# Contracts

A contract is a rule about an entry. Hollware runs the contracts of a collection on every write, and again when it publishes, and returns a **verdict**.

## The verdict

```json
{
  "ok": false,
  "phase": "write",
  "errors": [
    {
      "contract": "schema",
      "severity": "error",
      "path": "/frontmatter/description",
      "message": "Too big: expected string to have <=160 characters",
      "fix": "Shorten the description to 160 characters or fewer."
    }
  ],
  "warnings": []
}
```

Every violation has a `path` (a JSON Pointer into the entry), a `message` and a `fix`: an agent can act on it without reading your code.

## Writes never fail on a contract

A draft that breaks a contract is **saved**, with the verdict. Work in progress is never lost, and the agent fixes it with `update_draft`. What a contract stops is publishing:

- an **error** blocks `publish`, which answers `blocked`;
- a **warning** asks for acknowledgement: `publish` answers `needs_acknowledgement` with the warnings, and publishes once they are sent back in `acknowledgeWarnings`.

`validate` runs the contracts on Markdown without saving anything.

## Built in

These always run: the schema of the frontmatter, the components and their props, content safety (no raw HTML that runs script), and the size limit of an entry.

## The catalog

A collection turns on more from the catalog, each with a severity (an error blocks publishing; a warning has to be acknowledged) and its parameters. Add them in the admin, on the page of the collection, or over MCP with `set_collection`; `get_structure` lists the catalog with the JSON Schema of each rule's parameters.

| Rule | Checks | Parameters |
| --- | --- | --- |
| `translation-pair` | the entry exists in every locale of its translation group, to publish | none |
| `internal-links-resolve` | links to other entries point at published ones, when publishing | none |
| `images-have-alt` | every image has alternative text | none |
| `field-length` | a text field has between `min` and `max` characters, or a list between `min` and `max` items | `field`, `min`, `max`, `unless` |
| `field-pattern` | a text field matches a regular expression, as a whole | `field`, `pattern`, `description` |
| `required-to-publish` | fields a draft may leave empty and a published entry must fill | `fields` |
| `banned-words` | words that may appear neither in a text field nor in the body | `words`, `reason` |
| `reference` | each slug a field names is an entry of another collection, which must be published to publish | `field`, `collection`, `locale` |

A title of at most 60 characters, unless a `seoTitle` is given, is one rule:

```json
{
  "rule": "field-length",
  "severity": "warning",
  "params": { "field": "title", "max": 60, "unless": "seoTitle" },
  "id": "seo-title-length"
}
```

An entry can name others by their slug, such as the author of a post, a series or the event of a talk. The rule `reference` checks the slugs of a field (one, or a list) against a collection, in the entry's locale or the `locale` you give:

```json
{ "rule": "reference", "severity": "error", "params": { "field": "author", "collection": "authors" } }
```

A site reads them with the entry: `get_entry` with `references: true` (or `getEntry(…, { references: true })`, or `?references=true` over HTTP) answers `references`, the published entries each field names, in its order.

`id` names the rule in verdicts; give one when a collection has the same rule twice. Every violation comes with a `fix` written for the agent that reads it: what to do, not what went wrong.
