336 lines
34 KiB
Markdown
336 lines
34 KiB
Markdown
# ⚠ ИСПОЛНЕНО. Канон компаньона живёт не здесь
|
||
|
||
> **Не читать как действующий документ и не править.** Этот файл — черновик спутника, написанный
|
||
> фронт-сессией S3 04.08 и с тех пор **ратифицированный и перенесённый**. Канон компаньона —
|
||
> [`../../docs/architecture/14-api-contract/README.md`](../../docs/architecture/14-api-contract/README.md),
|
||
> рядом с канонической спекой; там же живёт ревью-шапка ратификации D39.99 и К-таблица.
|
||
>
|
||
> Баннер поставлен фронт-сессией S3 08.08 по указанию оркестратора №15: два экземпляра одного
|
||
> состояния расходятся при первой же правке, и расходится всегда тот, который забыли. **Один факт —
|
||
> один носитель.** Ниже — историческое тело; ссылаться на него можно, поддерживать — нет.
|
||
>
|
||
> Куда смотреть вместо него:
|
||
>
|
||
> | Что нужно | Где это теперь |
|
||
> |---|---|
|
||
> | норматив контракта (ФОРМА) | `docs/architecture/14-api-contract/openapi.yaml`; зонное байт-зеркало — `api-contract/openapi.yaml` |
|
||
> | провенанс, обоснования, К-вопросы | `docs/architecture/14-api-contract/README.md` |
|
||
> | правки, которые ждут переноса в канон | канон `docs/architecture/14-api-contract/` (патч ПРИМЕНЁН и удалён, D39.115) — живёт до лендинга S3 и удаляется |
|
||
|
||
## Историческое тело (04.08, не поддерживается)
|
||
|
||
> **Нормативная поверхность контракта — [`api-contract/openapi.yaml`](api-contract/openapi.yaml)**
|
||
> (OpenAPI 3.1). Этот файл её НЕ дублирует: он несёт то, чего YAML не выражает — откуда взято
|
||
> каждое решение, чем оно обосновано, что осталось открытым. При расхождении по ФОРМЕ
|
||
> побеждает YAML; при вопросе «почему так» — этот файл.
|
||
>
|
||
> **Статус: черновик фронт-сессии S3 на ратификацию.** Дом ратифицированной копии —
|
||
> `docs/architecture/14-api-contract/`, зона оркестратора; перенос делает он, генерация типов
|
||
> после ратификации идёт из перенесённой копии — контракт первичен, код вторичен.
|
||
>
|
||
> **Язык.** Спека английская: из неё генерятся типы, а исходники фронта по конвенции
|
||
> английские (слово владельца 04.08). Ссылки на К-вопросы внутри YAML набраны латинской
|
||
> `K-N` — это те же вопросы §4. Спутник и остальные доки зоны — русские.
|
||
>
|
||
> **Зона строки 95 — «оркестратор/бэкенд/фронт».** Фронт авторитетен в одной трети: форма
|
||
> read-модели и продуктовые словари. Транспорт платформы (пути, аутентификация, коды) и
|
||
> работы движка (99–103) здесь ПРЕДЛОЖЕНЫ и без подтверждения своих зон не действуют.
|
||
|
||
## 0. Пометки провенанса
|
||
|
||
| Пометка | Что значит |
|
||
|---|---|
|
||
| **✓ выведено** | следует из кода движка или ратифицированного решения; грунт `file:line` рядом |
|
||
| **◆ предложено** | решение фронта, разумное по его сведениям; подтверждает названная зона |
|
||
| **○ открыто** | развилка, на которую у фронта ответа нет; перечень — §4 |
|
||
|
||
⚠ Пометка ставится **на утверждение, а не на раздел**: у одного пункта половина бывает
|
||
выведенной, а половина предложенной. Первая редакция черновика этим и грешила — восемь мест
|
||
несли ✓ там, где верно было ◆; ниже разведено.
|
||
|
||
---
|
||
|
||
## 1. Почему YAML, а не проза
|
||
|
||
Контракт — машинный артефакт: из него генерируются типы, по нему линтуется форма, им
|
||
типизируются моки. Прозаический контракт расходится с кодом ровно тем способом, ради
|
||
предотвращения которого заведена строка 95.
|
||
|
||
Инструменты, пины и отклонение по пиру TS не дублирую: они в `STACK_DECISIONS.md` §3 и в
|
||
бэклоге зоны — Ф-23 (`overrides` вместо `--legacy-peer-deps`), Ф-24 (AsyncAPI отложен с
|
||
причиной). Четыре формы нарушения гейта, каждая проверена живьём, — `FRONTEND_PLAN.md` §5.4.2.
|
||
|
||
---
|
||
|
||
## 2. Решения и их происхождение
|
||
|
||
### 2.1. Язык — код, никогда не имя — ✓ выведено
|
||
|
||
Движок держит коды (`backend/internal/config/book.go:26-27`), ключ пары — `zh-ru`
|
||
(`configs/langpacks/zh-ru/`). Имя языка в данных — пар-специфика в общем слое, запрещённая
|
||
§2 канона. Вторая цена, дороже: `lang` элемента берётся из данных книги, и пара ja→ru с именем
|
||
вместо кода отрисует кандзи китайскими начертаниями молча.
|
||
|
||
### 2.2. Идентификаторы непрозрачны — ✓ выведено
|
||
|
||
`glossary.id` — свежий автоинкремент на каждой пересборке банка и намеренно не хешируется
|
||
(`store/migrate.go:173-174`); номер главы плотный, «Chapters that yield no text … do NOT
|
||
consume a chapter number» (`chunk/chunker.go:99-105`), поэтому правка исходника сдвигает
|
||
номера последующих глав. Стабильность обеспечивает платформа при персисте манифеста (100).
|
||
|
||
### 2.3. Заголовок главы отдельным полем — ◆ предложено
|
||
|
||
**Движок сегодня делает ОБРАТНОЕ**, и это надо назвать прямо: титул рендерится
|
||
детерминистически из шаблона пары (`configs/langpacks/zh-ru/heading.txt`: `template Глава {n}`),
|
||
исходный маркер вырезается из текста для модели (`chunk/chunker.go:110-114`), а на экспорте
|
||
титул **вклеивается внутрь текста первого юнита** (`pipeline/export.go:215`), причём колонка
|
||
исходника остаётся без него.
|
||
|
||
Выведена здесь только МЕХАНИКА. Само поле `heading` — предложение фронта, и у него есть цена
|
||
на другой стороне: движку придётся отдавать титул отдельно. Альтернатива (оставить вклейку,
|
||
фронт отрезает строку) хуже: отрезание титула из текста — это парсинг прозы, и он сломается
|
||
на первой главе без заголовка. Развилка — К-2.
|
||
|
||
**Расхождение фикстуры, найденное разбором:** дерево витрины показывает «Раздел 2. …», колонка
|
||
оригинала — неснятый «第二节:». Для zh→ru движок не порождает ни одной из форм. Не чинится
|
||
до ответа на К-2.
|
||
|
||
### 2.4. Состояние — у прогона; у главы выполнение — ✓ выведено
|
||
|
||
Подпись банка это один стоп на всю книгу (`pipeline/mining.go:201`), поэтому «глава ждёт
|
||
подписи, пока соседняя финализируется» — невозможная картина. У главы движок держит
|
||
`ChapterPassport` (`pipeline/status.go:37-55`).
|
||
|
||
### 2.5. Прогресс пофазно и в юнитах — ✓ выведено
|
||
|
||
«A unit is DONE when every member draft AND the unit's edit resolved ok»
|
||
(`pipeline/status.go:328-331`), редактура не стартует до стопа банка ⇒ сквозной счётчик стоит
|
||
на нуле всю черновую волну. Зависимость — строка 99.
|
||
|
||
### 2.6. Словарь статусов — ✓ лестница, ◆ ненормальные исходы
|
||
|
||
Лестница дословно из строки 95: «загрузка → разбор → перевод → подпись банка → финал →
|
||
готово». **`not_started` — дыра, найденная самопроверкой черновика:** лестница описывает
|
||
идущий прогон, а библиотека обязана показывать разобранную книгу, которую не запускали.
|
||
`stopped`, `rejected`, `not_started` контракт ВЫВОДИТ из поведения процесса, а не получает
|
||
полем: механика стопа у движка есть (`cmd/tmctl/main.go:63`), но «кто нажал» знает платформа.
|
||
|
||
**Стоп по потолку — не `failed`** ✓ выведено: «Ceiling is a hard, book-wide stop (not a
|
||
per-chunk flag): the job stays 'pending' and resume continues once the ceiling is raised»
|
||
(`pipeline/stagerun.go:488-489`). Мапить его в `failed` запрещено — это соврало бы про
|
||
резюмируемость. Каким статусом и словом он показывается — К-8, вопрос владельцу.
|
||
|
||
### 2.7. Состояние пары выводится из ПАРЫ — ✓ выведено (исправление первой редакции)
|
||
|
||
Первая редакция утверждала «`withheld` = текст не выдан» как факт о движке. **Это было
|
||
ложно:** флагнутый юнит легально приходит С ТЕКСТОМ в двух случаях —
|
||
|
||
- косметическая зачистка санитайзера: «the chunk is NOT lost — the cleaned text is committed
|
||
as the export» (`pipeline/disposition.go:96-104`);
|
||
- c-lite member-drop: редактор отгружает отредактированный чистый остаток, а юнит флагнут
|
||
из-за выпавшего члена (`pipeline/export.go:203-207`).
|
||
|
||
Поэтому состояние выводится из ПАРЫ (вердикт, наличие финального текста): флаг+текст →
|
||
`translated` с замечанием; флаг+пусто → `withheld`.
|
||
|
||
**Причина флага при этом ПРОИЗВОЛЬНА, и «единственный легальный случай» — снято** (ревью
|
||
оркестратора, round-2, пункт 4; утверждение противоречило выводу строкой выше). При c-lite drop
|
||
юнит несёт `FlagReason` ПЕРВОГО выпавшего члена, каким бы он ни был (`export.go:203-207`:
|
||
`ce.FlagReason = drops[0].Reason`, и тут же `ce.FinalText … still ships`). Значит «текст +
|
||
замечание» — это класс, а не один случай, и карта вердиктов обязана иметь фразу для каждой
|
||
причины, а не для двух.
|
||
|
||
**Но `glossary_miss` в этот класс НЕ входит, и это проверено отдельно** (иначе правка выше
|
||
воскресила бы невоспроизводимый пример). `memberDrops` берёт причину из ЧЕРНОВОЙ строки члена
|
||
(`status.go:242-257`: `draftStages[cs.Stage] && flagged`), а `glossary_miss` ставится
|
||
пост-чеком только там, где отгружается финал: в черновой волне — лишь когда она сама финальная
|
||
(`waverun.go:373-381`, draft-only), в c-lite — на строке РЕДАКТУРЫ (`waverun.go:494`). В
|
||
пайплайне, где текст отгружает редактура (то есть где c-lite drop вообще возможен), черновая
|
||
строка `glossary_miss` нести не может. При включённом гейте текст удерживается целиком
|
||
(`export.go:295-300`), при выключенном мисса нет вовсе. **Итог: пара «текст + промах словаря»
|
||
невозможна ни одним каналом — фикстура витрины, показывавшая её, переведена на c-lite drop.**
|
||
|
||
**Свежесть** ✓ выведено: `target` обновляется на границах стадий и на стопах, а не
|
||
непрерывно — посреди прогона канала чтения не существует (эксклюзивный лок движка;
|
||
санкционированное чтение — завершённый либо остановленный прогон, `research/23` §0, §4).
|
||
|
||
### 2.8. Банк: словари ✓, имена ◆, `kind` ◆ с дырой
|
||
|
||
Значения выведены из схемы и гейтов Go; **имена полей контракта — предложение фронта.**
|
||
|
||
| Поле | Словарь | Грунт |
|
||
|---|---|---|
|
||
| `status` | `auto · draft · approved` | `store/migrate.go:191`; только `approved` — канон |
|
||
| `kind` ◆ | `name · place · title · term · nickname` **плюс отсутствие значения** | `terminology/classify.go:15` + `pipeline/banknote.go:74`; пустое — `membank/memseed.go:323-326` |
|
||
| `origin` | `seed · ruby · mined` | пути записи, см. ниже |
|
||
| `sense` | свободный текст | `store/migrate.go:182` |
|
||
| `since_chapter`/`until_chapter` | целые, `0` = без границы | `store/migrate.go:189-190` |
|
||
|
||
**Фантом `auto` в провенансе убран.** Первая редакция взяла словарь из комментария схемы
|
||
(`migrate.go:193`: `seed|ruby|auto`) — комментарий устарел. По путям записи `"auto"` пишет
|
||
**статус**, не провенанс (`membank/memseed.go:328`: `Status:"auto", Source:"ruby"`), а майнинг
|
||
ставит `mined` (`pipeline/mining.go:424`). Правка комментария в движке — за оркестратором.
|
||
|
||
**`kind` пере-размечен ✓→◆, и вот почему это не косметика** (ревью round-2, пункт 3). Словарь
|
||
из пяти значений выведен верно, но ЗАКРЫТЫМ и обязательным он делает нелегальной легальную
|
||
строку: ruby-кандидат получает `Type: ""`, если его класс не `name` — то есть gloss и
|
||
ambiguous живут без типа по построению (`membank/memseed.go:323-326`: `typ := ""`, и только
|
||
`class == rubyClassName` даёт `"name"`). Материализатору read-модели такую строку было
|
||
физически нечем заполнить. **Правило пустого:** `kind` присутствует всегда и допускает `null`;
|
||
`null` значит «движок не решил», строка при этом остаётся подписываемой, и клиенту запрещено
|
||
и выбрасывать её, и додумывать тип за движок. Проекция `""` → `null` — работа платформы.
|
||
|
||
**Ложный друг устранён.** У движка колонка `source` — это ПРОВЕНАНС. Первая редакция назвала
|
||
провенанс `origin`, а имя `source` отдала ДРУГОЙ колонке (тексту термина) — то есть завела
|
||
между схемами ложного друга. Теперь: провенанс `origin`, формы термина `src`/`dst`, как их
|
||
зовёт сам движок; имя `source` в схеме банка не используется вовсе.
|
||
|
||
### 2.9. Подпись — набор решений — ✓ выведено
|
||
|
||
Конвейер заменяет банк целиком (`store/migrate.go:169-170`), поэтому `PATCH /term/{id}` молча
|
||
не работает. Механика дословно: «for EACH term either promote it into the mined-delta file OR
|
||
decline it in the mined-rejects file, then resume — the stop clears once every proposed term
|
||
is promoted or rejected» (`pipeline/mining.go:201`). Отсюда: решение `promote|decline` ·
|
||
счётчик «решено N из M» · запрет «продолжить» при неполном наборе · частичное сохранение ◆.
|
||
|
||
`POST /runs/{id}/resume` — **нормативная операция, а не резерв** (первая редакция помечала её
|
||
«○ резерв строки 94», хотя тут же делала её носителем снятия стопа банка). Резерв строки 94 —
|
||
это `stop`, продуктовая кнопка.
|
||
|
||
### 2.10. Ревизия — ✓ у ре-синка, ◆ у чтений
|
||
|
||
**✓ выведено:** правило ре-синка — идемпотентный апсерт по `(run_id, seq)`, канал согласования
|
||
— `status --json` (`research/23` §2, §8; D39.85).
|
||
|
||
**◆ предложено фронтом:** что ревизию несут и ЧТЕНИЯ, и что счётчик у потока и у чтений ОДИН.
|
||
Обоснование — гонка, которую иначе нечем разрешить: фронт живёт на снимке и потоке разом,
|
||
а рефетч по возврату фокуса окна у ратифицированного `@tanstack/react-query` включён по
|
||
умолчанию, то есть гонка на каждое переключение вкладки. Но это просьба, не вывод; выбор —
|
||
К-4.
|
||
|
||
**Скоуп ревизии** (дыра первой редакции: она отдавала `revision` на межкнижной библиотеке при
|
||
пер-прогонном определении): в спеке ревизия объявлена НА РЕСУРС — у библиотеки своя, у
|
||
прогона своя. Единая сквозная или пер-ресурсная — часть К-4.
|
||
|
||
### 2.11. Разрыв потока — ◆ предложено
|
||
|
||
При переподключении клиент шлёт `Last-Event-ID`. Если сервер докачать не может, он обязан
|
||
ответить событием `resync_required`, а не молча начать с текущего момента: **реплей истории
|
||
запрещён**, иначе разовое событие вроде `note` теряется молча и замечание не появится
|
||
до перезагрузки. Клиент по этому событию перечитывает снимки.
|
||
|
||
### 2.12. Разрешающий список — ✓ инвариант, ◆ форма
|
||
|
||
Проекция «read-модель → фронт» строится как allowlist. Что лежит в операторских структурах
|
||
(`pipeline/status.go:58-130`, `:37-55`) и не может доехать: пять денежных полей плюс
|
||
`cost_usd` главы (§4.8 — денег в MVP-интерфейсе нет вовсе) · `routing` вида «stage=model»,
|
||
`content_labels`, `content_routing_problems` (ПТ-33) · снапшот, дрифт, ре-билл ·
|
||
`escalations`, `postcheck_misses`, `style_flags`, `glossary_miss_flagged`, `stages_skipped`,
|
||
`repair_applied`, `worst_flag_reason` · `flag_reason` и `detail` — последний несёт сырой текст
|
||
движка вида «CJK leak in the ru output: 第一节»; строку собирает `checks/sanitizer.go:658`,
|
||
а `pipeline/export.go:34` — лишь объявление поля, куда она доезжает.
|
||
|
||
### 2.13. ПТ-34 — перевод не индексируется — ✓ инвариант
|
||
|
||
Реестр требований назначает носителем ПТ-34 в том числе контракт, а первая редакция пункта
|
||
не имела вовсе. В спеке: приложение живёт под `X-Robots-Tag: noindex`, ответы с текстом
|
||
перевода несут `Cache-Control: no-store`, ссылка на выгрузку выдаётся только владельцу.
|
||
|
||
---
|
||
|
||
## 3. Зависимости: без чего контракт не заработает
|
||
|
||
| Что | Строка | Без чего именно |
|
||
|---|---|---|
|
||
| Пофазный прогресс `draft ∥ edit` | 99 | прогресс (§2.5) |
|
||
| Персист манифеста + `chunker_version` | 100 | стабильный `id` главы (§2.2) |
|
||
| Машиночитаемая таблица ПОДПИСИ | 101 | экран подписи (§2.9) |
|
||
| Событийный эмиттер + событие потолка | 103 | весь поток (§2.11), событие `note`, событие `ceiling` |
|
||
| **Артефакт экспорта БАНКА** | **строки нет — заводит оркестратор** | чтение `GET /books/{id}/bank` |
|
||
| **Механизм поднятия потолка** | **строки нет — заводит оркестратор** | `POST /runs/{id}/resume` после стопа по потолку (см. ниже) |
|
||
| HTTP/SSE, аутентификация, воркер | П-1 | всё; в `platform/` ноль строк кода |
|
||
|
||
⚠ **Отдельно про банк — дыра, найденная ревью оркестратора.** Строка 101 даёт таблицу
|
||
ПОДПИСИ (стоп-таблица, кап 20 на stdout — `cmd/tmctl/render.go:98`), а не экспорт всего банка;
|
||
сам банк живёт в приватном SQLite движка, читать который платформе запрещено (D39.85). То есть
|
||
у чтения `/bank` сегодня **нет канала вообще**. Фронт этого не решает — нужна строка единого
|
||
бэклога, и заводит её оркестратор.
|
||
|
||
Та же природа у события `note`: пер-юнитных замечаний посреди прогона движок не эмитит —
|
||
зависимость на словарь строки 103.
|
||
|
||
⚠ **Потолок: `resume` сам по себе не сдвинет прогон** (ревью round-2, пункт 5). Движок
|
||
продолжает «once the ceiling is raised» (`pipeline/stagerun.go:488-489`), а канала поднятия
|
||
в контракте нет — и в MVP-интерфейсе быть не может: денег на экране нет вовсе (D39.84). Значит
|
||
между стопом по потолку и продолжением обязан стоять механизм ПЛАТФОРМЫ (поднятие по политике,
|
||
или явное действие вне интерфейса книги), и до него `resume` после потолка возвращает прогон
|
||
в то же состояние. Что при этом видит пользователь — К-8, вопрос владельцу; чем поднимают —
|
||
строка единого бэклога, которой нет.
|
||
|
||
---
|
||
|
||
## 4. Открытые вопросы
|
||
|
||
| # | Вопрос | Кому |
|
||
|---|---|---|
|
||
| К-1 | Десять статусов (§2.6) — принять или поправить? Три контракт выводит, а не получает | владелец / автор контракта |
|
||
| К-2 | Титул главы: отдать полем `heading` (предложено) — или оставить вклейку в текст, и фронт отрезает строкой? | автор контракта + бэкенд |
|
||
| К-3 | Метка главы в дереве: титул это ровно «Глава N», узлов 2284 — дерево одинаковых по форме строк | владелец (продуктовое) |
|
||
| К-4 | Ревизия: одна сквозная на прогон или своя на ресурс? И несут ли её чтения вообще (§2.10) | платформа |
|
||
| К-5 | Показывать ли оценку времени. **ПТ-19 существует** (`docs/product-requirements.md:49`: «видимый прогресс/ETA — из Ф3-видения ридер-IDE»), то есть посылка «не запрошено» неверна; вопрос в том, показываем ли в MVP | владелец (продуктовое) |
|
||
| К-6 | Ступени замечания: сколько их и где граница. Сегодняшние две — проекция ОПЕРАТОРСКОЙ лестницы рангов, а она не обязана совпадать с продуктовой осью | владелец (продуктовое) |
|
||
| К-7 | Пагинация: 2284 главы и 1200 терминов одним ответом или курсором? Фронт виртуализует, ему годится любой | платформа |
|
||
| К-8 | Стоп по потолку: каким статусом и каким словом? В `failed` мапить нельзя — стоп резюмируемый | владелец (продуктовое) |
|
||
| К-9 | **Отказ прескрина не выразим ни одним из десяти статусов.** Абьюз/misuse-прескрин до трат токенов и UI-контракт отказа — строка 94 (ПТ-16); книга, отклонённая прескрином, это не `rejected` (тот про неразобранный файл) и не `failed` | владелец + автор контракта |
|
||
| К-10 | **Выполнение главы — тот самый несплитованный счётчик, который §2.5 объявляет негодным.** `Chapter.units_done` не разведён по фазам, значит дерево глав показывает ноль всю черновую волну — ровно то, из-за чего прогресс книги сделан пофазным. Развести и тут (цена — пофазные счётчики НА ГЛАВУ в строке 99) или показывать в дереве другое | автор контракта + бэкенд |
|
||
| К-11 | **Условная обязательность полей — выражена у банка, не выражена у чтений.** У `BankDecision` констрейнт поставлен (`if action=promote → dst` непустой), и вот что это стоило, измерено: spectral его валидирует, а **openapi-typescript его игнорирует** — в генерённых типах `dst?: string` как был. То есть 3.1-условие защищает сервер, но не экран; клиентское сужение (юнион `promote`-с-`dst` ↔ `decline`) — работа подписного экрана S5, писать его до экрана не на чем проверить. Остаётся решить то же для чтений: `Note` не требует ни `chapter_id`, ни `unit_id`, `Unit.target` не обязателен при `translated`; обе схемы служат и вложенно, и отдельно, поэтому простое `required` соврало бы | автор контракта |
|
||
| К-12 | **Завершение выгрузки: опрос или событие?** Чтение `GET /books/{id}/exports/{id}` заведено — без него создающий вызов был тупиком (`ready:false` и ни слова дальше). Но пушить ли завершение ещё и кадром потока, чтобы не опрашивать, решает платформа: у неё воркер и её цена | платформа |
|
||
|
||
---
|
||
|
||
## 5. Проверка ревью-вопросом строки 95
|
||
|
||
**«Сменится стадия конвейера — придётся ли править фронт?»**
|
||
|
||
| Изменение в движке | Правит ли фронт |
|
||
|---|---|
|
||
| переименована стадия / добавлена волна | **нет** — имена стадий не пересекают шов, прогресс пофазный, а не постадийный |
|
||
| сменилась модель или маршрутизация | **нет** — `routing`/`content_labels` в allowlist не входят |
|
||
| добавлена новая причина флага | **нет** — на провод идёт продуктовая фраза, карта живёт в контракте |
|
||
| добавлен новый тип термина | **нет** — словарь расширяется минором, ветка неизвестного стоит на шве |
|
||
| добавлено новое продуктовое состояние | **да, один файл** — карта «статус → вид» на шве `src/api/`; это и есть контрольный вопрос владельца |
|
||
| сменился чанкер, главы пере-разобраны | **частично** — код фронта не правится (ключ непрозрачный, номер отображаемый), но **сохранность соответствия старых `id` новым главам контрактом не гарантируется**: это работа персиста манифеста (строка 100). Если соответствие потеряно, у пользователя разъезжаются открытые вкладки и закладки — не правка кода, но видимый ущерб, и решать его строке 100 |
|
||
|
||
Единственное безусловное «да» — то, которое и должно быть «да».
|
||
|
||
---
|
||
|
||
## Приложение А. Карта «вердикт → продуктовая фраза» — ЗАГОТОВКА
|
||
|
||
Заполняет автор контракта вместе с бэкендом и владельцем. **Правило: фраза пишется по
|
||
доккомменту `disposition.go`, а не по имени константы, и рядом кладётся цитата** — иначе
|
||
повторяется инверсия, стоившая двух фраз (`glossary_miss` подан как «термин не подписан»,
|
||
хотя термин ПОДПИСАН и его проигнорировали, `disposition.go:78-79`; `sanitizer_stripped` подан
|
||
как потеря текста, хотя «the chunk is NOT lost», `disposition.go:99`).
|
||
|
||
⚠ **Русские фразы ниже — плейсхолдеры, а не предложение фронта.** Словарь продуктовый, его
|
||
слова выбирает владелец (ПТ-33, В-3). Ступень — тоже: сегодняшние `attention`/`glance`
|
||
унаследовали ОПЕРАТОРСКУЮ ось рангов, а она не обязана совпадать с продуктовой (К-6).
|
||
|
||
| Причина движка | Ранг | Продуктовая фраза | Ступень |
|
||
|---|---|---|---|
|
||
| `hard_refusal` · `soft_refusal` · `content_filter` · `hard_block` | 0 | ⬜ | ⬜ |
|
||
| `cjk_artifact` · `excision_suspect` · `coverage_fail` | 1 | ⬜ | ⬜ |
|
||
| `sanitizer_defect` | 2 | ⬜ | ⬜ |
|
||
| `loop_degenerate` | 3 | ⬜ | ⬜ |
|
||
| `decode_error` | 4 | ⬜ | ⬜ |
|
||
| `glossary_miss` | 5 | плейсхолдер: «Подписанный термин не применён в переводе» | ⬜ |
|
||
| `length` · `empty` | 6 | ⬜ | ⬜ |
|
||
| `sanitizer_stripped` | 7 | плейсхолдер: «Служебная разметка вычищена автоматически» | ⬜ |
|
||
| `upstream_not_ok` | 8 (по умолчанию) | ⬜ | ⬜ |
|
||
| незнакомая причина | 8 (по умолчанию) | ⬜ нейтральная, НЕ «ошибка» | ⬜ |
|
||
|
||
Причин пятнадцать; `upstream_not_ok` в первой редакции отсутствовал — у него нет своей ветки
|
||
в `flagReasonSeverity`, поэтому он падает в ранг по умолчанию (`pipeline/status.go:174`),
|
||
как и любая будущая причина. Последняя строка — не формальность: контракт обязан иметь фразу
|
||
для причины, которой ещё не существует.
|