books/gu-zhenren/door-to-file-b/checks/api-expectations.md

49 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# §4.8 — ожидаемый исход КАЖДОГО дёрганья, ЗАПИСАННЫЙ ДО НЕГО
Все формы идут на $0-РУКЕ (заглушка на 11434, рендер пайплайна на провайдера `kind: local`).
Довод — §4.8 промта: дёрганье на измеряемом прогоне САМО производит число, которое потом
предъявляется как свойство движка (резюм и второй старт — ровно то, что меряет числитель-2 §4.3).
| # | Форма | Ожидание, написанное ДО |
|---|---|---|
| 1 | `GET /v0/capabilities` без сессии | **401** `unauthenticated`, и `export_formats` при этом НЕИЗВЕСТЕН, а не пуст |
| 2 | Заказ в ЗНАКАХ (`characters`) вместо глав | 202; `ordered_units` непусто ЛИБО `ordered_chapters` рассчитано из знаков — это ДРУГАЯ ветка контракта и другой счётчик |
| 3 | Повтор старта с тем же `Idempotency-Key` | тот же `id` прогона, БЕЗ второго прогона и без второго холда; счёт строк `runs` не растёт |
| 4 | Повтор старта БЕЗ ключа при идущем прогоне | отказ (409-класс): книга уже в работе |
| 5 | Старт на НЕСУЩЕСТВУЮЩЕЙ книге | 404, машинный `code`, `request_id` непустой |
| 6 | Курсор-мусор на страничном чтении | 400-класс с машинным `code`, НЕ 500 и НЕ тихий первый лист |
| 7 | Заказ выгрузки в НЕСМОНТИРОВАННОМ формате (`pdf`) | 400/422-класс: формат не из `export_formats` |
| 8 | Условное чтение: повтор `GET` с `If-None-Match` от прошлого ответа | **304**, тело пустое |
| 9 | Правка банка на НЕСУЩЕСТВУЮЩЕМ `src` | 200 с пустым `accepted` ЛИБО 4xx — но НИКОГДА не «применено» |
| 10 | Нехватка баланса: вторая учётка без гранта | отказ ДО спавна, `blocked` непусто в `run-options`, старт отказан |
| 11 | `POST /v0/runs/{id}/stop` на идущем прогоне | 202; НАМЕРЕНИЕ стопа в Postgres ДО сигнала (иначе стоп и авария — один и тот же выход) |
| 12 | Резюм ДВАЖДЫ подряд (идемпотентность резюма) | второй — отказ либо тот же attempt, НЕ третья попытка |
⚠ Что делает эту таблицу проверкой, а не экскурсией: любой ответ двери задним числом выглядит
правильным. Строки ниже дописываются ФАКТОМ после исполнения, и расхождение НАЗЫВАЕТСЯ.
## ИСХОД КАЖДОЙ ФОРМЫ (дописано ПОСЛЕ исполнения; ожидания выше не правлены)
| # | Форма | ФАКТ | Сошлось |
|---|---|---|---|
| 1 | `capabilities` без сессии | **401** `unauthenticated`; прибор напечатал «export_formats НЕИЗВЕСТЕН, не пуст» | ✅ |
| 1б | `capabilities` с сессией | 200, `export_formats: ["txt","epub"]`, `contract_version 0.15.0`, `bank_corrections_enabled: true` | ✅ обязательный пин ДО фриза |
| 2 | заказ в ЗНАКАХ (`characters: 300`) | **202**, `ordered_chapters: null`, **`ordered_units: 2`** — другая ветка, другой счётчик | ✅ |
| 3 | идемпотентность старта (один `Idempotency-Key` дважды) | тот же `id` прогона обоими ответами; строк `runs` у книги **+1, не +2**; `idempotency_keys` 1 строка | ✅ |
| 4 | второй старт при ИДУЩЕМ прогоне | **409 `run_in_flight`** | ✅ ⚠ с ОГОВОРКОЙ, см. ниже |
| 5 | старт на несуществующей книге | **404 `not_found`**, `request_id` непустой | ✅ |
| 6 | курсор-мусор | **расщеплено тремя пробами**: `cursor=not-a-real-cursor`**400 `cursor_invalid`**; `cursor=%zz`**200 и первая страница**; без курсора → тот же 200 | ⛔ частично: механизм жив, но битый процент-эскейп молча выбрасывается `r.URL.Query()` |
| 7 | выгрузка в несмонтированном формате | **400 `invalid_request`** + `{"pointer":"/format","code":"unknown"}` | ⚠ ожидание было 422 — **неверно было МОЁ ожидание**, дверь права и отвечает машинно |
| 8 | условное чтение `If-None-Match` | **304**, тело пустое | ✅ |
| 9 | правка банка на несуществующем `src` | **200**, `accepted[0].state: applied` в ПРЕВЬЮ | ⚠ дверь принимает правку термина, которого банк не предлагал; по коду это ДОБАВЛЕНИЕ, а не ошибка — но в ответе неотличимо от подтверждения предложенного |
| 10 | нехватка баланса ($0.05 при бонде $2.00) | **200** `verdict: covers_all`, `affordable_chapters: 4`, **`blocked: null`**, но `term_consistency_funded` перевернулся в `false` и холд упал 2 004 553 → **4 553**; старт **ПРИНЯТ, 202**; цена в обоих случаях ОДНА (`expected_micro_usd` 2324) | ⛔ находка, см. отчёт |
| 11 | стоп идущего прогона | **202**, `stop_requested: true`; через 15 с `runs.status=stopped`, `stop_requested_at` записан, **`exit_code = 5`** — стоп отличим от аварии | ✅ |
| 12 | резюм ТЕРМИНАЛЬНОГО прогона (дважды) | **409 `run_not_resumable`** оба раза; попыток по-прежнему 2 | ✅ |
| 12б | резюм ОСТАНОВЛЕННОГО, дважды подряд | первый **202**, второй **202** с тем же состоянием; `run_attempts` = **2, третья попытка НЕ создана** | ⚠ ожидание 409 было МОИМ приором; дверь идемпотентна ПО ЭФФЕКТУ, и это законно |
**ОГОВОРКА К СТРОКАМ 4 и 11, и она про МОЙ ПРИБОР, а не про дверь.** Первая попытка этих двух проб
дала «⛔ не как ожидалось»: второй старт вернул 202, стоп вернул 409. Разбор показал, что я спросила
ПОСЛЕ того, как прогон уже кончился — $0-заглушка проходит четырёхюнитную книгу за ~15 секунд, а между
моими вызовами было больше. Пере-снято залпом без пауз (три вызова за 0.27 с) — обе двери ответили
ровно как обещано. **Аномалия была о приборе, а не о предмете**, и если бы я её не пере-сняла, в отчёт
уехали бы две несуществующие находки.