Schema reference
Every field the catalog validates, read directly from schemas/*.schema.json. This
page is generated, so it cannot drift from what tools/validate.py actually enforces.
The contract is frozen. Before changing any schema, read the change-class policy in ADR 0019: annotation edits are a normal PR, a new optional property is a minor bump, and anything else needs an ADR, a major bump, and a migration.
entry.universal.schema.json
Section titled “entry.universal.schema.json”Shared base schema for all taxonomy entry types
- Published at:
https://product-on-purpose.github.io/writing-style-catalog/schemas/v1/entry.universal.schema.json - Freeze status: frozen (ADR 0019)
| Field | Type | Required | Notes |
|---|---|---|---|
anti_patterns | array of object (min 2, max 4) | yes | ADR 0009 pedagogical bar (required since 2026-06-22, the F2 tighten-to-required step after the 60-entry backfill). 2 to 4 misuse/why pairs; consumed by Gate 2. Makes explicit what is implicit in when_not_to_use and confusable_with. |
avoid_with | array of string | yes | List of entry IDs that conflict or produce poor results |
axis | enum: voice, tone, style, format | yes | The taxonomy axis this entry belongs to. Four peer axes, applied in this precedence order during composition: voice, tone, style, format. Voice and Tone are the most confusable pair (voice is stable across contexts; tone varies per piece) but they are separate axes with separate schemas. See ADR 0018. |
confusable_with | array of string | yes | Entry IDs that are commonly confused with this one |
deprecated_in_favor_of | string | no | If deprecated, the entry ID that replaces this one |
description | string | yes | Full prose description, 100-500 words |
examples_index | array of string | no | Relative paths to example files |
failure_modes | array of object (min 2, max 3) | yes | ADR 0009 pedagogical bar (required since 2026-06-22, the F2 tighten-to-required step after the 60-entry backfill). 2 to 3 mode/mitigation pairs. The field the C1 restraint check renders against: a sample is judged a caricature if it triggers the entry’s own declared failure modes. |
id | string | yes | Slug identifier, unique within axis |
llm_instruction_phrasing | string | yes | Ready-to-use instruction paragraph for an LLM |
name | string | yes | Human-readable display name |
one_liner | string | yes | 1-2 sentence standalone description |
pairs_well_with | array of string (min 1) | yes | List of entry IDs that compose well with this entry |
review_status | enum: draft, reviewed, stable, reference-quality, deprecated | yes | Lifecycle state of this entry. draft = initial content; reviewed = editorially checked; stable = in active use; reference-quality = exemplary; deprecated = superseded. |
tags | array of string | yes | Searchable tags |
tells | array of string (min 5, max 7) | yes | ADR 0009 pedagogical bar (required since 2026-06-22, the F2 tighten-to-required step after the 60-entry backfill). 5 to 7 spottable phrases, syntactic moves, or structural markers by which a reader identifies this entry’s application in the wild. Largely reformats the per-axis fields (language_patterns, markers, structural_conventions). Substance (non-empty strings) is enforced by Gate 2 in tools/validate.py. |
when_not_to_use | array of string (min 1) | yes | Situations where this entry is inappropriate |
when_to_use | array of string (min 1) | yes | Situations where this entry is appropriate |
example.schema.json
Section titled “example.schema.json”Schema for the YAML frontmatter of example files in the examples/ directory
- Published at:
https://product-on-purpose.github.io/writing-style-catalog/schemas/v1/example.schema.json - Freeze status: frozen (ADR 0019)
| Field | Type | Required | Notes |
|---|---|---|---|
audience | string | no | Intended audience for the example content |
author_type | enum: human, llm, hybrid | yes | Who produced the example content. human = written by a person; llm = fully AI-generated; hybrid = AI-generated and then edited by a human. |
axis | enum: voice, tone, style, format | yes | |
edited_by | string | no | Human editor name (applicable when author_type is hybrid) |
entry_id | string | yes | Slug of the taxonomy entry this example demonstrates |
format_id | string | no | Format entry ID if this example demonstrates a format combination |
generated_at | string | no | ISO 8601 date when the example was generated |
llm_model | string | no | Model identifier (required when author_type is llm or hybrid) |
llm_prompt_file | string | no | Relative path to the prompt file used to generate this example |
review_status | enum: draft, reviewed, stable, reference-quality | yes | |
style_id | string | no | Style entry ID if this example demonstrates a style combination |
tone_id | string | no | Tone entry ID if this example demonstrates a tone combination |
topic_label | string | yes | Human-readable topic name |
topic_slug | string | yes | Slug of the anchor topic (e.g., async-standups) |
voice_id | string | no | Voice entry ID if this example demonstrates a voice combination |
word_count | integer | no | Word count of the example body |
experimental/diff-pair.schema.json
Section titled “experimental/diff-pair.schema.json”Schema for the YAML frontmatter of diff-pair files in examples/diff-pairs/. A diff-pair contrasts two taxonomy entries on the same axis applied to the same topic, with every other axis held constant.
- Published at:
https://product-on-purpose.github.io/writing-style-catalog/schemas/experimental/diff-pair.schema.json - Freeze status: not frozen - may take a breaking change without a major version bump
| Field | Type | Required | Notes |
|---|---|---|---|
author_type | enum: human, llm, hybrid | no | Who produced the diff-pair body. human = written by a person; llm = fully AI-generated; hybrid = AI-generated and then edited by a human. |
axis_varied | enum: voice, tone, style, format | yes | The axis whose value differs between entry_a and entry_b. All other axes are held constant. |
diff_pair_id | string | yes | Slug uniquely identifying this diff pair. Convention: |
entry_a | string | yes | Slug of the first taxonomy entry being contrasted. |
entry_b | string | yes | Slug of the second taxonomy entry being contrasted. |
generated_at | string | no | ISO 8601 date when the diff-pair was generated. |
generator | string | no | Path to the script or process that produced this file, if applicable. |
llm_model | string | no | Model identifier (relevant when author_type is llm or hybrid). |
notes | string | no | Optional curator notes about why this pair was chosen or what it teaches. |
review_status | enum: draft, reviewed, stable, reference-quality | yes | |
topic_label | string | yes | Human-readable topic name. |
topic_slug | string | yes | Slug of the anchor topic the pair is rendered on (e.g., async-standups). |
format.schema.json
Section titled “format.schema.json”Schema for format entries (axis 4 of 4). Format is the structural container or output type: headings, field ordering, section templates.
- Published at:
https://product-on-purpose.github.io/writing-style-catalog/schemas/v1/format.schema.json - Freeze status: frozen (ADR 0019)
- Composes:
entry.universal.schema.json
| Field | Type | Required | Notes |
|---|---|---|---|
axis | no | ||
canonical_template | string | no | The structural template (e.g., Context/Decision/Consequences for ADR) |
digital_or_print | enum: digital, print, both, neither | no | Primary medium for this format |
domain | enum: professional, public, personal, ceremonial, contemplative | yes | Sphere of life the writing happens in (ADR 0010). Required (F2 phase 3, tightened 2026-06-19 after the 60 were backfilled). The enum mirrors tools/taxonomy.py FORMAT_DOMAINS; the family-belongs-to-domain rule is enforced by validate.py. |
family | enum: deliberation, instruction, progress, brief, appraisal, messaging, outreach, response, broadcast, copy, position, accountability, correspondence, essay, tribute, devotion, journal | yes | Structural/functional kind of writing, scoped to its domain (ADR 0010). Flat enum (mirrors tools/taxonomy.py); membership within the domain is checked by validate.py. Required (F2 phase 3). |
subfamily | string | no | Optional third level, required once a family reaches 12 members (ADR 0010). Membership is checked by validate.py where the family enumerates subfamilies. |
typical_length | object | no | Typical word-count range for this format |
typical_tones | array of string | no | Tone IDs that frequently use this format |
typical_voices | array of string | no | Voice IDs that frequently use this format |
style.schema.json
Section titled “style.schema.json”Schema for style entries (axis 3 of 4). Style is the kind or mode of writing (rhetorical, narrative, or genre category): how ideas are sequenced and structured.
- Published at:
https://product-on-purpose.github.io/writing-style-catalog/schemas/v1/style.schema.json - Freeze status: frozen (ADR 0019)
- Composes:
entry.universal.schema.json
| Field | Type | Required | Notes |
|---|---|---|---|
axis | no | ||
classical_mode | string | no | Classical rhetorical mode if applicable (narration, description, exposition, argumentation) |
evidence_types | array of string | no | Types of evidence or support this style typically uses |
frame | string | no | The rhetorical or narrative frame (e.g., argument, narrative, expository) |
reader_contract | string | no | What the reader can expect from a piece written in this style |
structural_conventions | array of string (min 1) | yes | The typical structure rules for this style |
tone.schema.json
Section titled “tone.schema.json”Schema for tone entries (axis 2 of 4). Tone captures situational register, how the writing feels in a given context, and varies from one piece to the next. Voice, its most confusable neighbour, captures persistent identity and is stable across contexts. They are separate axes with separate schemas. See ADR 0018.
- Published at:
https://product-on-purpose.github.io/writing-style-catalog/schemas/v1/tone.schema.json - Freeze status: frozen (ADR 0019)
- Composes:
entry.universal.schema.json
| Field | Type | Required | Notes |
|---|---|---|---|
axis | no | ||
markers | array of string (min 3) | yes | Observable textual markers that signal this tone |
nn_g_profile | enum: funny, neutral, serious | no | Nielsen-Norman Group tone profile classification |
spectrum | string | no | Which emotional or rhetorical spectrum this tone lives on (e.g., warmth, confidence, formality) |
spectrum_position | number | no | Position on the spectrum (0 = one extreme, 1 = the other extreme) |
voice.schema.json
Section titled “voice.schema.json”Schema for voice entries (axis 1 of 4). Voice is the persistent identity of who is speaking, stable across contexts. Contrast tone, which varies per piece.
- Published at:
https://product-on-purpose.github.io/writing-style-catalog/schemas/v1/voice.schema.json - Freeze status: frozen (ADR 0019)
- Composes:
entry.universal.schema.json
| Field | Type | Required | Notes |
|---|---|---|---|
axis | no | ||
default_pov | enum: first-person, second-person, third-person, variable | no | Default grammatical person |
diction | string | no | Vocabulary register: formal, casual, technical, vernacular, etc. |
family | enum: expert, care, principal, witness, dissident | yes | Voice family, grounded by communicative function (ADR 0010 sections 2 and 2a). No domain: a voice travels across spheres. Enum mirrors tools/taxonomy.py VOICE_FAMILIES. Required (F2 phase 3, tightened 2026-06-19). |
language_patterns | array of string (min 3) | yes | Observable language patterns: sentence structures, word choices, rhythms |
sentence_style | string | no | Characteristic sentence construction |
subfamily | string | no | Optional third level, required once a family reaches 12 members (ADR 0010). For example care -> pastoral. Membership is checked by validate.py where the family enumerates subfamilies. |
typical_tones | array of string | no | Tone IDs that frequently pair with this voice |