Let the contract's bound admit the zero its own line calls legal, read the counters' unit off the order, and say when a cut is merely unasked

This commit is contained in:
heaven 2026-09-06 07:30:22 +03:00
parent 33b896b1ac
commit 1cae0ac494

View file

@ -1448,11 +1448,40 @@ components:
description: |
How far THIS RUN has got through the whole of its own work, as ONE monotonic fraction.
⚠ **THE UNIT FOLLOWS HOW THE ORDER WAS PHRASED, and there is exactly one discriminator —
`Run.delivered_chapters`:** a number ⇒ the counters are CHAPTERS · `null` ⇒ the order was
placed in characters and the counters are the engine's own shipping units, the same `Unit`
this contract already carries · a re-pass ⇒ `total` is `1`, a declared shape and not a count
of anything. **Do not render a unit noun without checking it** — "3 of 10 chapters" drawn
⚠ **THE UNIT FOLLOWS HOW THE ORDER WAS PHRASED, and it is read off `Run.ordered_chapters`
and `Run.ordered_units` — the pair that describes the ORDER — in this order, first match
wins:**
1. **`ordered_chapters == 0` ⇒ a RE-PASS.** `total` is `1` and `done` is `0` until it
finishes clean, then `1`: a declared shape, not a count of anything. Do not render it as
"0 of 1 chapters". Held by `pgstore.TestARePassRunsBarIsOneUnitOfWork`, and its
EXCLUSIVITY — that nothing else can put a zero here — by
`runs.TestABookWithNothingLeftIsRefusedRatherThanSoldAsAZeroChapterRun`: a whole-book order
over a fully delivered book quotes zero chapters, and the service refuses it rather than
admitting a run that would wear the re-pass's shape without being one.
2. **`ordered_chapters == null` ⇒ the order was placed in CHARACTERS**, and the counters are
the engine's own shipping units — the same `Unit` this contract already carries. The size
bought stands in `ordered_units`. Held by
`runs.TestACharacterOrdersBarIsCountedInWhatItActuallyBought` and
`runs.TestACharacterOrdersVolumeIsPublishedInTheUnitItWasSoldIn`.
3. **Otherwise (`ordered_chapters >= 1`) ⇒ the counters are CHAPTERS.** Held by
`runs.TestAChapterOrderKeepsTheBarItAlwaysHad`.
⛔ **THE ORDER OF THOSE CHECKS IS LOAD-BEARING, and `Run.delivered_chapters` is NOT the
discriminator** — an earlier edition of this paragraph said it was, and it was wrong in the
one case that matters. A re-pass answers `delivered_chapters: 0`, which is a NUMBER, so the
rule "a number means chapters" sends a client to case 3 and it draws the very "0 of 1
chapters" this paragraph forbids. `delivered_chapters: null` does mark case 2 — it is derived
from the same fact — but it cannot separate case 1 from case 3, so a client that starts there
gets a re-pass wrong. Measured, not reasoned: the re-pass row of
`pgstore.TestARePassRunsBarIsOneUnitOfWork` asserts `delivered_chapters: 0` beside
`ordered_chapters: 0`.
⚠ **Do not key on `stage` either.** `stage: re_pass` is true of case 1 today, but `stage` is
an OPEN vocabulary whose new values move no version — a client owes it degradation, not
decisions. It corroborates; it does not discriminate.
**Do not render a unit noun without running the three checks** — "3 of 10 chapters" drawn
over units is a lie the client tells on our behalf. A client that draws a PERCENTAGE needs
none of this: the fraction is meaningful in every case, which is why no ready-made percentage
is shipped. A run may make more than one pass over the chapters it bought, and it may stop
@ -1818,7 +1847,7 @@ components:
`resume` clears nothing — nothing was withdrawn.
ordered_chapters:
type: [integer, 'null']
minimum: 1
minimum: 0
description: |
⚠ **`null` when the order was NOT placed in chapters** — an order phrased in
characters carries its size in `ordered_units` instead. **Exactly one of the two is
@ -1827,6 +1856,17 @@ components:
⚠ **`0` is legal and means a re-pass:** it buys no chapters while being a perfectly
ordinary run. A client that treats this field as at-least-one will reject valid answers.
⚠ **The bound was `minimum: 1` until this was measured, and it contradicted the line
above it** — a generated validating client would have rejected every re-pass. `null` was
never at risk (a bound constrains numbers, not the null branch); the value it excluded
was the zero. Held by `pgstore.TestARePassRunsBarIsOneUnitOfWork`, which asserts `0` on
BOTH surfaces a run is handed out on — the start receipt and the read path — because they
are two different expressions and a promise made once must hold on both.
The bound is kept rather than dropped: `0` is the floor and not merely today's smallest
value. A negative is refused at admission by the service itself, so the schema says out
loud what the store already guarantees.
⚠ **Changed in 0.12.0.** Until then this field carried, for a character order, the SPAN
of chapters the order reached into — a number that could exceed what was bought (an order
of one unit out of four reported `1`). That was a defect: the field is named for what was
@ -2696,7 +2736,17 @@ components:
- type: string
- type: 'null'
description: |
Where the chapter boundaries came from, passed through from the engine's manifest.
Where the chapter boundaries came from, passed through from the engine's manifest, or
`null` when NO MANIFEST HAS ANSWERED YET.
⚠ **`null` is not a fourth kind of cut, and it is not `none`.** `none` is a book the engine
looked at and found one chapter in; `null` is a book it has not been asked about. Having no
provenance and having a poor one are different facts, and only the second says anything
about the book. Held by
`httpapi.TestABookWhoseCutCannotBeSoldAgainstOffersCharactersAndSaysSo`, which asserts an
explicit `null` rather than an invented word. ⚠ The sibling `Book.structure` carries the
same fact and says the same in its own words; the two cannot disagree.
⚠ **An open vocabulary that GROWS additively** — today `declared`, `delimited`, `detected`, `none` (⚠ `delimited` added by the engine 06.09: the file drew a separator — an EPUB spine, a form feed — but calling it a CHAPTER is the engine's assumption, not the format's word).
A value this build does not know is read as "not recognised" and never promoted into
trust; a client MUST do the same rather than switch on an exhaustive list.