Concepts
Contracts
The rules every write is checked against, the verdict they return, and the catalog of rules a collection turns on.
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
{
"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 answersblocked; - a warning asks for acknowledgement:
publishanswersneeds_acknowledgementwith the warnings, and publishes once they are sent back inacknowledgeWarnings.
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:
{
"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:
{ "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.