# audio@0.1

**Status:** normative. **Inherits:** [`../conventions.md`](../conventions.md) — envelope, block
identifiers, inline fields, tags, wikilinks, preservation rules, and the shared diagnostic codes
`MDTO001`–`MDTO099`. It does **not** inherit the shared item model: an audio file has no items
(§2.4).
**Schema:** [`schema.json`](schema.json), which `$ref`s `position`, `source`, and `diagnostic` from
[`../todo/schema.json`](../todo/schema.json) rather than restating them.
**Fixtures:** [`fixtures/`](fixtures/). **Owns diagnostic codes:** `MDTO300`–`MDTO399`.

---

## 1. Purpose

`audio@0.1` describes a **narration manuscript**: a piece of writing meant to be heard, where headings
are chapters, prose is the script, and the small amount of direction a narrator needs — voice, pace,
pronunciations, a held beat, a passage not to read — lives in frontmatter and in sparse inline fields.
It is the first spec with a `produce` adapter: the same file that reads as a document is what
`mdto audio produce` turns into MP3s, chapter by chapter, and it stays a document afterwards.

---

## 2. Grammar

### 2.1 Envelope

```yaml
---
markdownto: audio@0.1
title: Field notes, week 32   # optional, reserved
id: field-notes-32            # optional, reserved
voice: warm-narrator          # optional, spec-defined
pace: relaxed                 # optional, spec-defined
pronunciations:               # optional, spec-defined
  agentsFS: agents F S
---
```

`audio@0.1` defines three additional frontmatter keys:

| Key | Type | Meaning |
|---|---|---|
| `voice` | string | The name of a voice from `mdto audio voices`. A single token matching `[A-Za-z][A-Za-z0-9._-]*` (`MDTO300`). Compared case-insensitively against the provider's catalogue **at production time, not at validation time**. |
| `pace` | `slow` \| `relaxed` \| `natural` \| `brisk` \| `fast` | Delivery speed. Case-insensitive, lowercased in the IR. Default `natural` (`MDTO301`). |
| `pronunciations` | mapping of string → string | Written form → spoken form (§2.5). |

A wrong YAML *type* on any of them is the shared `MDTO007`; the key is discarded and its value-level
checks are skipped rather than firing once per entry. `title` is never narrated — it is document
metadata, and it is what `produce` writes into the MP3 title tag and the receipt.

**What is deliberately not in 0.1, and where it went instead.** The rule is: **frontmatter carries
direction that belongs to the text; flags carry facts about a particular run.** So there is no
`format:` (MP3 is the only output in 0.1, and a key with one legal value is furniture), no `model:`,
no `output:`, and no `provider:` — those are `produce` flags, recorded in the build receipt where a
metered service can be audited against them. Also deferred, each a plausible minor version:
per-block voice direction, a declared `chapters:` list in the manner of kanban's `columns:`,
`language:`, intro/outro and music cues, and speaker names for dialogue.

### 2.2 Chapters

**A chapter is opened by a heading at the document's chapter level. The chapter level is `##` if the
document contains at least one `##` heading, and `#` otherwise.**

That one rule covers both shapes people actually write a manuscript in:

```markdown
# Three letters to a younger engineer      ← no `##` anywhere in the file,
                                             so `#` is the chapter level
# On being slow
# On being wrong
```

```markdown
# Field notes, week 32                     ← the file contains `##`,
                                             so `##` is the chapter level and
## The idea                                  this `#` is the leading title heading
## What I built
```

- The level is decided **once, for the whole document**, by a single pass over its headings. It never
  varies within a file.
- Headings **below** the chapter level (`###` under `##` chapters) are ordinary narrated content. They
  are read where they stand and open nothing. This is what makes a long chapter with sections in it
  a legal manuscript rather than a structural error.
- A heading **above** the chapter level is permitted only as the **first block after the frontmatter**,
  where it is the document's title heading. Anywhere else it is `MDTO310`.
- Only **top-level** headings open chapters. A heading inside a blockquote or a list item is content.
  A `#` inside a code fence is not a heading at all (conventions §4.5).
- ATX (`## Name`) and setext (`Name` over `---`) headings are both recognized; only the level matters.
- A chapter heading must have a title once direction fields are removed (`MDTO311`).
- Chapter titles need not be unique. Chapters are addressed by **number**, never by name, and output
  files are index-prefixed, so two chapters called "Interlude" collide with nothing.

**The implicit leading chapter.** Narratable content before the first chapter heading forms a leading
chapter whose title is `null` — a preface, a standing-alone note, or a whole document with no headings
at all. It exists **only when there is such content**: a document that opens directly with `## Chapter
one` has no implicit chapter and its chapter 1 is that heading. The floor of the format is therefore
a frontmatter block and a paragraph:

```markdown
---
markdownto: audio@0.1
---

I keep meaning to write these down while they are still warm, so here is the first one.
```

**Numbering.** Chapters are numbered 1..N in document order, counting the implicit leading chapter if
it exists. These are the numbers `preview --chapter N` and `produce --chapters 2-5` address, and the
numbers that prefix output filenames.

**Recovery.** A heading that is invalid for its *level* (`MDTO310`) does not open a chapter: it is
narrated in place inside the enclosing chapter, so the prose beneath it cannot silently migrate. A
heading that is invalid for its *content* still opens a chapter: for `MDTO311` the title is the empty
string and the output filename falls back to `chapter-NN`.

### 2.3 What is narrated

| Block | Narrated? |
|---|---|
| Paragraph | **Yes** |
| Heading, at any level | **Yes** — including the heading that opens the chapter |
| List, bullet or ordered, task list or not | **Yes** — each item is its own utterance, in document order |
| Blockquote | **Yes** |
| Fenced or indented code block | No |
| Table | No |
| HTML block | No |
| Thematic break (`***`, `---`) | No — it inserts nothing, not even silence |

Code blocks and tables are excluded **by kind, permanently**: read aloud they are punctuation, and
there is nowhere on a fence line or a pipe table to write a direction field, so a default that could
be overridden would be a default nobody could reach. A block kind this spec does not name is not
narrated either; implementations MUST NOT invent narration for constructs 0.1 does not define.

Within a narrated block, the rule is **what a person would read aloud off the page**:

- Link text is read, the URL is not: `[the conventions](https://…)` → "the conventions".
- A wikilink's name is read, its brackets are not: `[[conventions]]` → "conventions".
- A tag's word is read, its `#` is not: `#manuscript` → "manuscript". (`#1234` is literal text, not a
  tag — conventions §5 — and is read as written.)
- Emphasis markers are not read; the emphasized words are.
- An **inline code span is read** as its literal characters. Only *block* code is excluded. Nothing
  inside a code span is scanned, so `` `[pause:: 2s]` `` is punctuation and a number, and it is
  spoken as such rather than held as silence.
- An image contributes nothing: alt text describes a picture, and turning it into speech would be the
  spec guessing at the author's intent. A paragraph containing only an image is narrated as nothing,
  and that is not a diagnostic.
- Inline fields are **never** narrated, in any Markdown To spec. That is exactly what makes them
  usable as direction here.
- A soft line break inside a block becomes a single space; a paragraph break inside a block (a
  multi-paragraph blockquote) becomes a paragraph break in the speech text.
- List markers, ordinal numbers, and task checkboxes are not narrated. `- [x] Water within reach` is
  read as "Water within reach".

A document with **no narratable content at all** is `MDTO330`: it names itself a manuscript and gives
`produce` nothing to say.

### 2.4 Block direction

Two inline fields are direction. They are the only fields `audio@0.1` knows.

| Field | Meaning |
|---|---|
| `[narration:: skip]` | The block it appears in is not narrated. `skip` is the only value in 0.1 (`MDTO320`). |
| `[pause:: <duration>]` | Hold silence **after** the block it appears in (`MDTO321`, `MDTO322`). |

**Attachment.** A direction field applies to the **top-level block that contains it**, wherever in
that block it is written — position within the text is never meaningful, here or in any other spec.
The one finer grain is the list item: items are addressed individually, because a list is a sequence
of separate utterances rather than one run of prose.

```markdown
> [narration:: skip] Reminder: clear this anecdote with him before it goes out.
```

A field in a blockquote applies to the whole blockquote, including all of its paragraphs.

**On a chapter heading, `[narration:: skip]` silences the title only.** The chapter still exists, is
still numbered, and its blocks are still narrated. This is the mechanism for a decorative title you do
not want announced — and, on a leading `#` title heading, for a book that should not open by saying
its own name.

**Duration grammar.** `^(0|[1-9][0-9]*)(\.[0-9]+)?(ms|s)$` — a number and a unit, `ms` or `s`. The
unit is required: a bare `2` is `MDTO321`, because seconds and milliseconds are three orders of
magnitude apart and guessing is not worth being wrong once. The value must be greater than 0 and at
most **60s** (`MDTO322`); resolution is one millisecond, and finer values round to the nearest.

**A pause of its own.** The conventional way to write a beat is a paragraph consisting only of the
field. It reads on the page exactly like the stage direction it is:

```markdown
Then we put it on a slide.

[pause:: 2s]

Kaufmann pointed out that we had all been saying it differently for a month.
```

A pause on a content block holds after that block, including at the end of a chapter, where it becomes
trailing silence in that chapter's file. **A mid-sentence pause is not in 0.1**: keeping every inline
field position-insensitive is worth more than the feature, and splitting the paragraph reads better
anyway.

**Interactions.** At most one `narration` and one `pause` per block — a repeat is the shared
`MDTO023`, because two values with no defined precedence is ambiguity. A `pause` on a skipped block
has no effect and is `MDTO323`: a skipped block contributes neither speech nor silence, and skip wins.
A block consisting only of direction fields that produces neither speech nor silence is `MDTO324` —
almost always an author who expected `[narration:: skip]` on its own line to apply to the paragraph
below it.

**Other fields.** `audio@0.1` does not use the shared item model, so the shared field vocabulary
(`priority`, `due`, `owner` — conventions §4.3) is **not** recognized here: those keys are unknown
fields in an audio file, carried in `fields`, preserved verbatim, never narrated, never an error.
Block identifiers (`^id`) are recognized so that they are never read aloud, and preserved — but
`audio@0.1` defines no use for one and **no audio verb ever pins one**, because no audio verb writes
to the manuscript at all.

**Recovery.** An invalid `narration` value recovers to *narrated*: the spec never silences an author's
text because of a typo. An invalid or out-of-range `pause` produces no silence, and the block is
otherwise unaffected — and it is not additionally reported as `MDTO324`, since the diagnostic it
already has is the one that matters.

### 2.5 Pronunciations

```yaml
pronunciations:
  agentsFS: agents F S
  mdto: em dee tee oh
  Kaufmann: KOWF-mun
```

- Each key is the written form as it appears in the manuscript; each value is what is said instead.
- A key MUST be a string (`MDTO302`). This bites more often than it looks: YAML reads `2026:` as an
  integer and `true:` as a boolean, so those need quoting.
- A value MUST be a non-empty string (`MDTO303`). To leave a word alone, delete the entry.
- Matching is **case-insensitive**, on **whole-word boundaries** (a match must be bounded by
  characters outside `[A-Za-z0-9]` or by the ends of the text), and multi-word keys are allowed.
- Two keys differing only in case are `MDTO304`: matching is case-insensitive, so neither could win.
- When several keys could match at one position, the **longest key wins**; ties break by source order.
  This is the whole of the determinism rule, and it is why the IR carries pronunciations as an ordered
  array rather than a map.
- Substitution applies to narrated text only, which is the same thing as saying it never applies
  inside code, tables, or skipped blocks — those are not narrated in the first place.
- A key that never occurs in any narrated text is `MDTO305` (warning): harmless, and usually a typo in
  the key.
- A diagnostic about a `pronunciations` entry reports **that entry's own line**, the same way
  conventions §9.1 reports a frontmatter key on its own line.

The result of substitution is the block's `speech` in the IR: the exact string sent to the provider,
the string `estimate` counts, and the string that goes into the chapter's cache key.

### 2.6 Diagnostics owned by this spec

**Direction in frontmatter (300–309)**

| Code | Severity | Meaning |
|---|---|---|
| `MDTO300` | error | `voice` is not a well-formed voice name (a single token, no spaces). |
| `MDTO301` | error | `pace` is not one of `slow`, `relaxed`, `natural`, `brisk`, `fast`. |
| `MDTO302` | error | A `pronunciations` key is not a string. |
| `MDTO303` | error | A `pronunciations` value is not a non-empty string. |
| `MDTO304` | error | Two `pronunciations` keys differ only in case. |
| `MDTO305` | warning | A `pronunciations` key never occurs in any narrated text. |
| `MDTO306` | warning | `voice` is well-formed but not in this implementation's voice catalogue. |

**Chapters (310–319)**

| Code | Severity | Meaning |
|---|---|---|
| `MDTO310` | error | Heading is above the document's chapter level and is not the leading title heading. |
| `MDTO311` | error | Chapter heading has no title. |
| `MDTO312` | warning | Chapter has no narratable content besides its own heading. |

**Block direction (320–329)**

| Code | Severity | Meaning |
|---|---|---|
| `MDTO320` | error | Unknown value for `[narration:: …]`; 0.1 defines only `skip`. |
| `MDTO321` | error | Malformed `[pause:: …]` duration. |
| `MDTO322` | error | `[pause:: …]` duration is out of range; a pause is greater than 0 and at most 60s. |
| `MDTO323` | warning | `[pause:: …]` on a skipped block has no effect. |
| `MDTO324` | warning | Block consists only of direction fields and produces neither speech nor silence. |

**Document (330–339)**

| Code | Severity | Meaning |
|---|---|---|
| `MDTO330` | error | Document has no narratable content. |

`MDTO312` is suppressed when `MDTO330` is reported: if the whole document is empty of narration,
saying it again per chapter is noise, not information.

**`MDTO306` is deliberately unfixtured**, for the same reason as `MDTO005` and `MDTO008` in
conventions §10.3: what it reports depends on the implementation's own voice catalogue rather than on
the file. It follows that a valid fixture naming a plausible voice MAY produce an `MDTO306` warning in
some implementation, which conformance permits — valid fixtures forbid errors, not warnings.

Everything else an audio file can get wrong is a shared code — envelope (`MDTO001`–`MDTO008`),
identifiers (`MDTO010`–`MDTO012`), inline fields (`MDTO020`–`MDTO023`) — see conventions §9.3. Audio
files contain no items, so the item-structure codes `MDTO030`–`MDTO033` never fire in one.

---

## 3. Rationale

*Read this before repairing a file. Most bad repairs come from guessing at intent.*

**Why the manuscript is the script.** Every other route to narrated audio starts by converting a
document into a production format — a JSON script, an SSML tree, a timeline — after which there are
two artifacts and the readable one stops being true. Here there is one file. It reads as a document
because it *is* a document; the direction it carries is the direction a human narrator would need
written in the margin, and no more. **If a construct would only ever be read by the TTS pipeline and
never by a person holding the page, it does not belong in this spec.**

**Why the chapter level is discovered rather than fixed.** Both manuscript shapes in the wild are
right. A set of letters or essays in one file wants `#` per piece; a book with a title on page one
wants `#` for the book and `##` for chapters. Fixing the level at `##` would have made the first shape
invalid and, worse, would have silently reinterpreted a `#`-chaptered file as one chapter with a very
long title — the failure mode where the tool is confidently wrong and says nothing. The discovered
level is deterministic, needs one pass, and both shapes get exactly what they meant. The cost is the
one shape it cannot express: a file whose chapters are `#` *and* which wants a separate `#` title
heading, where the title becomes chapter 1. **If you want a title heading, use `##` for chapters** —
that is the repair, and it is why `MDTO310` says so out loud.

**Why headings deeper than the chapter level are content and not errors.** todo and kanban make `###`
an error because a third grouping level would make "which section is this item in" ambiguous. Nothing
is ambiguous here: a sub-heading has no structural job to compete for, and long-form writing has
sections inside chapters constantly. Refusing them would push authors into splitting chapters they
did not want split, purely to satisfy the validator.

**Why chapter headings are narrated.** An audiobook that never says its chapter titles loses the
listener's place, and — more decisively — silently dropping the largest line on the page would be the
single most surprising thing this spec could do. So every heading is content, the title heading
included, and the author who does not want a title announced says so with `[narration:: skip]`. **Do
not "fix" a manuscript by deleting a heading you think is decorative.**

**Why the chapter is the unit of everything.** Chapters are the unit of narration, of output, of
caching, of preview, and of resumption, and that is not four coincidences. Generation is the expensive
step, so the boundary the author already drew for the reader's benefit is reused as the boundary that
keeps a typo in chapter four from re-buying chapters one through three. **A tool that regenerates a
whole book because one paragraph changed is not a slow implementation of this spec; it is a
non-conforming one.**

**Why direction is this thin.** `voice`, `pace`, three characters of pause, and one exclusion switch.
Every additional knob — per-block voices, emphasis, emotion, SSML — is a knob whose value only makes
sense to a synthesizer, and the moment the file is full of them it stops being a document and becomes
a build script that happens to have prose in it. Per-block voice direction is deferred **explicitly**,
which is what `MDTO320` exists to say: `[narration:: whisper]` is not an unrecognized value, it is a
named extension point that 0.1 chose not to take. **Do not start honouring values `MDTO320` rejects.**

**Why a pause attaches to a block instead of a position.** Every inline field in every Markdown To
spec is position-insensitive — `[due:: …]` means the same thing wherever it sits on the line — and
making one field the exception would break a rule agents have already learned. The standalone
pause-paragraph is also simply better writing: a beat on its own line looks like a beat, and a bracket
in the middle of a sentence looks like a bug.

**Why `[narration:: skip]` and not a comment.** An HTML comment would hide the text from every viewer,
which is precisely the thing conventions §0 rejects. A skipped block is still visible, still diffable,
still part of the document; it just is not read aloud. The renderer marks it so a reader can see the
difference. **A skipped block is not deleted content and must never be treated as deletable.**

**Why code blocks and tables are never read.** Not because they are unimportant — because speech is a
linear medium and they are not linear. Reading a pipe table aloud produces a list of punctuation;
reading a code fence aloud produces gibberish that also happens to be the one place in the file where
nothing is scanned (conventions §4.5). An author who wants the idea in the audio writes the sentence.
**The repair for "my code did not get narrated" is prose, not an escape hatch.**

**Why validation cannot check the voice name.** The catalogue lives with the provider and changes
without the file changing. A validator that reached the network to check a name would break
conventions §8 (no network access during validation) and would make the same file valid on Tuesday and
invalid on Wednesday. So the file is checked for *shape* (`MDTO300`) and the name is resolved at
production time, where the failure is immediate, obvious, and free. `MDTO306` exists so an
implementation that *does* have a catalogue in hand can say something useful without making it fatal.

**Why nothing is derived in the IR.** No character counts, no durations, no cache keys, no costs. The
IR says what the file says; a receipt says what one run did. Keeping them apart means a figure can
never be stale relative to the document it describes, and it means the metered tier's numbers come
from one place that can be audited.

**Why every audio verb is read-only.** `estimate`, `produce`, `preview`, and `voices` never modify the
manuscript — not a pinned identifier, not a normalized field, not a stamped duration. The file is
written by a person; the tool writes artifacts *beside* it. This is why `audio@0.1` needs no `--pin`,
no patch engine, and no conflict protocol on write: the only thing that can change the file is the
author.

**Deliberate non-features of 0.1**, each a plausible minor-version addition: per-block voice
direction, named speakers and dialogue, segments and intro/outro/music cues, mid-sentence pauses,
SSML pass-through, `language:`, a declared `chapters:` list, M4B and chapter-marked output, word-level
captions, and a `podcast` profile over the same manuscript core.

---

## 4. Examples

### 4.1 Valid

The complete set lives in [`fixtures/valid/`](fixtures/valid/). Every one is something a person would
plausibly have written to be read aloud — that is the bar a fixture has to clear here, and it is a
higher bar than in the other specs, because a manuscript that reads like machine output would be
audibly wrong.

**Minimal** ([`fixtures/valid/minimal.md`](fixtures/valid/minimal.md)) — an envelope and three
paragraphs. No headings, so the whole document is one implicit chapter. This is the floor.

**Chapters** ([`fixtures/valid/chapters.md`](fixtures/valid/chapters.md)) — the two-level shape: a `#`
title heading, three `##` chapters, a bullet list, a blockquote, `voice` and `pace` in frontmatter.
Chapter 1 is the implicit chapter containing the title heading; chapters 2–4 are the `##`s.

**Single-level chapters** ([`fixtures/valid/h1-chapters.md`](fixtures/valid/h1-chapters.md)) — three
`#` chapters and no `##` anywhere, so `#` is the chapter level and the document's name lives only in
`title:`.

**Full direction** ([`fixtures/valid/full-direction.md`](fixtures/valid/full-direction.md)) — a
pronunciations map with all three keys used, a standalone `[pause:: 2s]`, a trailing
`[pause:: 1.5s]` on a paragraph, `[narration:: skip]` on a blockquote, and `[narration:: skip]` on a
chapter heading (the chapter is produced; its title is not spoken):

```markdown
## The version I am less proud of

> [narration:: skip] Reminder: clear the Kaufmann anecdote with him before this goes out.

The command is mdto, which nobody can say either. [pause:: 1.5s]
```

**Edge cases** ([`fixtures/valid/edge-cases.md`](fixtures/valid/edge-cases.md)) — everything that
looks like it should narrate and does not, and vice versa: a table and a fenced code block (excluded),
an image (contributes nothing), a `###` sub-heading (narrated, opens nothing), a task list (the words
are read, the boxes are not), a link and a wikilink and a tag, `#1234` as a literal issue reference,
`` `[pause:: 2s]` `` inside a code span staying literal, and a thematic break inserting no silence.

### 4.2 Invalid, with the exact diagnostics they produce

Each is a fixture in [`fixtures/invalid/`](fixtures/invalid/) with a sibling `.diagnostics.json`
giving the expected `{code, severity, line, message}` list.

**`bad-direction.md`** — every way document-level direction goes wrong:

```yaml
voice: Warm Narrator
pace: leisurely
pronunciations:
  agentsFS: agents F S
  2026: twenty twenty-six
  mdto: ""
  MDTO: em dee tee oh
  Ghibli: GHIB-lee
```

→ `MDTO300` error, line 4: *Invalid value for `voice`: a voice name is a single token matching `[A-Za-z][A-Za-z0-9._-]*`, found `Warm Narrator`. Did you mean `warm-narrator`?*
→ `MDTO301` error, line 5: *Invalid value for `pace`: expected one of `slow`, `relaxed`, `natural`, `brisk`, `fast`, found `leisurely`.*
→ `MDTO302` error, line 8: *Pronunciation key `2026` is not a string: YAML read it as an integer. Quote it.*
→ `MDTO303` error, line 9: *Pronunciation for `mdto` has an empty value; omit the entry instead.*
→ `MDTO304` error, line 10: *Pronunciation key `MDTO` differs only in case from `mdto` on line 9.*
→ `MDTO305` warning, line 11: *Pronunciation key `Ghibli` does not occur in any narrated text.*

**`bad-block-direction.md`** — every way block direction goes wrong, in a scene that is otherwise fine:

```markdown
The room went quiet. [pause:: 2]

Then the door opened. [pause:: two seconds]

She still did not look up. [pause:: 0s]

[pause:: 90s]

[narration:: whisper] Say this next part gently.

[narration:: skip] Cut this line for now. [pause:: 3s]

[narration:: skip]
```

→ `MDTO321` error, line 6: *Malformed `pause` duration `2`: the unit is required.*
→ `MDTO321` error, line 8: *Malformed `pause` duration `two seconds`.*
→ `MDTO322` error, line 10: *`pause` duration `0s` is out of range; a pause of zero is a no-op.*
→ `MDTO322` error, line 12: *`pause` duration `90s` is out of range: at most 60s.*
→ `MDTO320` error, line 14: *Unknown value for `narration`: audio@0.1 defines only `skip`.*
→ `MDTO323` warning, line 16: *`pause` on a skipped block has no effect.*
→ `MDTO324` warning, line 18: *Block consists only of direction fields and produces neither speech nor silence.*

Note that line 12 reports only `MDTO322`: a block whose single direction field is itself invalid is
not *also* reported as having no effect.

**`bad-chapters.md`** — the document contains `##`, so its chapter level is `##`; the `#` on line 6 is
therefore the legal leading title heading and the `#` on line 17 is a structural level the spec does
not have:

```markdown
##

Tuesday, if we are being honest, did not happen.

# Part two

## Friday

## Saturday
```

→ `MDTO311` error, line 13: *Chapter heading has no title. The chapter still opens; its output file falls back to `chapter-03`.*
→ `MDTO310` error, line 17: *Heading `# Part two` is above this document's chapter level (`##`) and is not the leading title heading. It is narrated in place and opens nothing.*
→ `MDTO312` warning, line 19: *Chapter `Friday` has no narratable content besides its own heading.*

**`no-narration.md`** — a voice reference sheet that declares itself a manuscript: a table, a code
block, a thematic break, and nothing else.

→ `MDTO330` error, line 2: *Document has no narratable content: every block in it is a kind audio@0.1 never reads aloud.*

**`bad-envelope.md`** — `pace` as a sequence, `pronunciations` as a string.

→ `MDTO007` error, line 5: *Frontmatter key `pace` has the wrong type: expected a string, found a sequence.*
→ `MDTO007` error, line 8: *Frontmatter key `pronunciations` has the wrong type: expected a mapping, found a string.*

**`missing-envelope.md`** — a perfectly good piece of writing that never names a spec.

→ `MDTO001` error, line 1: *Missing frontmatter envelope: the file must begin with a `---` fenced YAML block naming the spec.*

---

## 5. Verb reference

`audio@0.1` owns the vocabulary **`estimate`, `produce`, `preview`, `voices`**. It shares nothing with
`todo`, `kanban`, or `backlog` — not one verb name — which is the clearest demonstration that verb
vocabularies are spec-owned: those three mutate a list, this one spends money on a manuscript. The
universal commands `validate`, `render`, and `spec` come from the universal adapters and are not
listed here — though note that audio's `render` is expected to show chapter navigation, mark skipped
blocks visibly rather than hiding them, and display the same duration estimate `estimate` reports.

**Every verb in this spec is read-only with respect to the manuscript.** No audio verb edits the
`.md` file, pins an identifier, normalizes a field, or stamps a duration. Artifacts are written
*beside* the file, never into it.

### Common flags and precedence

`--file <path>` (default: the single `*.audio.md` in the working directory) · `--voice <name>` ·
`--pace <p>` · `--json` (machine-readable output).

**Effective direction is flag, then frontmatter, then default.** An overriding flag is recorded in the
receipt and is part of the cache key, so `--voice calm-reader` can never be served audio generated
with the frontmatter voice.

### `estimate` — what this would cost, before anything is spent

```
mdto audio estimate [--file f] [--voice v] [--pace p] [--json]
```

Validates, builds the narration plan, and reports — per chapter and in total — the number of
**narratable characters**, the estimated duration, and the estimated cost. It **MUST NOT call the TTS
provider**, MUST NOT write audio, and MUST be deterministic: the same file and flags produce the same
numbers on every machine.

- **Characters** are counted over each narrated block's `speech` — that is, *after* pronunciation
  substitution, because that is the string a provider would be billed for — summed over the chapter,
  with a single separator character counted between blocks.
- **Duration** is `characters ÷ rate + Σ pauses`, where the reference rate is **14 characters per
  second at `natural`**, scaled by pace: `slow` ×0.80, `relaxed` ×0.90, `natural` ×1.00, `brisk`
  ×1.15, `fast` ×1.30. An implementation MAY substitute a better-measured rate but MUST report the
  rate it used (`charactersPerSecond` in `--json`). It is an estimate and must be labelled as one.
- **Cost** is characters × the provider's price basis. If no price basis is known, `estimate` reports
  characters and duration and says the cost is unknown. **It never invents a number.**
- **Cache awareness is the point.** `estimate` reports two figures: the *full* cost of producing every
  chapter, and the *incremental* cost of running `produce` right now against the current cache — which
  chapters are already cached, and which would be regenerated. After a one-word edit in chapter four,
  the incremental figure is the one that answers the question the author is actually asking.

### `produce` — chaptered generation, cached, resumable

```
mdto audio produce [--file f] [--out DIR] [--chapters 2-5|--chapter N] [--voice v] [--pace p]
                   [--model m] [--dry-run] [--yes]
```

**Refuses on any `error`-severity diagnostic, with no override.** todo and kanban offer `--force`
because the worst case is a bad edit to a text file; here the worst case is a bill. A file that does
not validate is not narrated.

Contract:

1. **Estimate first.** `produce` prints the `estimate` output and requires confirmation before any
   provider call. `--yes` is the non-interactive acknowledgement; `--dry-run` prints the plan and the
   estimate and stops.
2. **One chapter, one unit of work.** Chapters are generated in document order, each written and
   recorded as it completes.
3. **The cache key is the chapter's content, and nothing else.** The key is a SHA-256 over a canonical
   string containing, in order: the spec identifier, the effective voice, pace, provider, and model;
   then, for each narrated unit of that chapter in order, its `speech`, and after any unit carrying a
   pause, that pause in milliseconds. **Excluded, deliberately:** the chapter's index, its title slug,
   the output path, the document title, the rest of the frontmatter, source line numbers, and every
   other chapter. The consequences are the contract:
   - Editing chapter four regenerates chapter four. Nothing else is re-bought, ever.
   - Inserting a new chapter two renumbers the output files and regenerates none of the old ones.
   - Reflowing a paragraph without changing a word changes no key, so nothing regenerates.
   - Changing `voice` or `pace` changes every key, so everything regenerates — which is correct, and
     `estimate` will have said so first.
4. **Resumable.** An interrupted run leaves every completed chapter in the cache. Re-running continues
   where it stopped; there is no separate resume command, because a cached chapter and a finished
   chapter are the same thing. The cache is content-addressed, lives outside the source file, and MUST
   be safe to delete: a cold cache costs money, never correctness.
5. **Output.** Into `--out` (default `./audio/`):
   - `NN-<slug>.mp3` per chapter, `NN` being the 1-based chapter number zero-padded to two digits and
     `<slug>` the chapter title lowercased with runs of non-alphanumerics collapsed to `-`, trimmed,
     truncated to 40 characters; `chapter-NN` when the title is empty and `preamble` for an implicit
     leading chapter.
   - `<basename>.mp3` — every chapter joined, in order. This is the file a person listens to; the
     per-chapter files are what caching and resumption are made of.
   - `<basename>.receipt.json` — the build receipt (`schema.json#receipt`): source path and hash,
     voice, pace, provider, model, and per chapter its key, characters, duration, output file, and
     whether it came from cache — plus totals and the actual cost. Cost figures in a receipt are
     measured, never estimated.
6. **Silence** comes only from `[pause:: …]`. `produce` MUST NOT insert padding between blocks or
   chapters that the manuscript did not ask for.
7. `--chapters 2-5` / `--chapter N` restrict generation to a range, writing only those chapter files
   and skipping the joined output unless every chapter is present and current.

### `preview --chapter N` — audition one chapter

```
mdto audio preview --chapter N [--file f] [--voice v] [--pace p] [--model m] [--play]
```

Generates exactly one chapter and reports where it wrote it. `--chapter` is required: preview exists
to answer "does this voice work for this passage" without buying the book.

Because the cache is keyed on content and settings and *not* on which verb asked, **a preview is a
cache entry**: if the settings match, a later `produce` reuses it and does not pay twice. If the
preview used a different voice, pace, or model, its key differs and no false reuse is possible. That
safety is structural, not a check someone has to remember to write.

### `voices` — the catalogue

```
mdto audio voices [--provider p] [--json]
```

Lists the voices available to the current credentials: name, language, a short description, and
whether it is usable. The only audio verb that needs no file, and the only one that may reach the
network — it queries the provider or the hosted service and SHOULD cache the result locally. It is
also the catalogue `MDTO306` is checked against, and the answer to "what do I put in `voice:`".

### What no verb ever does

Modifies the manuscript · pins or normalizes an identifier · rewrites a field or a frontmatter value ·
inserts silence the file did not ask for · narrates a code block or a table · calls a provider before
the estimate has been shown and acknowledged · regenerates a chapter whose key is unchanged ·
regenerates any chapter other than the one that changed · produces audio from a file with an
`error`-severity diagnostic · invents a cost.
