Raise the platform's announced contract to the ratified minor and give its reason map the sixteenth row, which had been reaching readers as an unspecified note

This commit is contained in:
heaven 2026-09-04 20:46:09 +03:00
parent bfa2b6d6b4
commit 7e204db57a
5 changed files with 38 additions and 6 deletions

File diff suppressed because one or more lines are too long

View file

@ -34,6 +34,10 @@
**Рецепт самодостаточен:** `<скретчпад сессии>/stand/UP.sh` — поднимает Postgres, если он упал, поднимает демона, дожидается `readyz` и кладёт cookie-jar. Пробы — `. <скретчпад>/stand/api.sh`, дальше `api GET /v0/capabilities`.
**СТЕНД ОПУЩЕН 04.09 после окончания приёмки — но опущен НАПОЛОВИНУ, и вторая половина жива НАРОЧНО.** Остановлен только демон на `8099`. ⛔ **Postgres на `55433` НЕ гасится ни при каких условиях: кластер ОБЩИЙ** — на нём и `tmstand34` (этот стенд), и `tmstand`, база ЧУЖОГО живого стенда (демон на `8080`, аптайм с ~31.08, `pid 1090739`). Остановка кластера уронила бы чужую сессию: это ровно тот случай, когда «прибраться за собой» означает сломать соседа. Запрет продублирован в самом `UP.sh` — читают рецепт, а не журнал.
**Данные платного прогона пережили остановку и сверены ПОСЛЕ неё** (`psql` по `tmstand34`): 2 книги · 10 глав · 14 экспортов · 5 прогонов; деньги по `credit_ledger``grant 0.600000`, `settlement -0.278319`, холды в ноль (`-1.512494` / `+1.512494`), **остаток `0.321681 USD`**. Списание сходится с отчётом пака до микродоллара. Поднять обратно — `UP.sh`; порт `8099` свободен.
**Адреса стенда:** книга `bk_SS5VES2JELESJSTR` · аккаунт `u_YZE7TAXW5YQNRLHM` · БД `tmstand34` на сокете `/tmp/.s.PGSQL.55433` · демон `127.0.0.1:8099` · метрики `127.0.0.1:9474`. Всё ВНЕ дерева репозитория.
**ДЕМОН УМИРАЛ ТРИЖДЫ ЗА СМЕНУ, И ПРИЧИНУ Я СНАЧАЛА НАЗВАЛА НЕВЕРНО — исправляю здесь же, потому что первая редакция этого абзаца успела записать ошибку как факт.** Я написала «хост убивал демона по нехватке памяти, `dmesg | grep -ci 'killed process'` даёт 56». Число верное, вывод — нет: **`dmesg` не называет `tmplatformd` НИ РАЗУ** (`dmesg | grep -i 'killed process' | grep -ci tmplatformd`**0**; ядро убивало `python3` соседей). Это была улика окружения, выданная за улику события — ровно тот класс, от которого предостерегает канон. **Настоящая причина названа третьей смертью, пришедшей с объяснением:** демон запускался ФОНОВОЙ ЗАДАЧЕЙ сессии и жил в её группе процессов, поэтому останов задачи уносил демона с собой. ⚠ **Лечение — в рецепте:** `UP.sh` поднимает демона через `setsid nohup … < /dev/null &` + `disown`, и он становится лидером сессии (проверено: `PGID == SID == PID`, `ps -o pid,pgid,sid`). Смерть от чужой памяти этим не исключена — она просто ни разу не подтвердилась. ⚠ **Что при этом ОСТАЁТСЯ верным и важным:** `connection refused` от стенда сначала проверяется как смерть демона, а не как дефект зоны, и все три смерти пережиты БЕЗ ПОТЕРЬ — книга, банк, замечания и артефакты на месте, Postgres не падал ни разу. Состояние цело потому, что оно в Postgres, а прогоны — транзиентные юниты (`D39.106`): ратифицированное решение, предъявившее себя в бою трижды. Сверено после третьего подъёма: `chapters_done 1` из 10, `note_count 6`, `revision 74`.
@ -42,6 +46,20 @@
**И одно живое подтверждение, которого я не планировала.** Пока сессия занималась другим, GC двери отработал сам, и его числа сошлись: `owed == artifacts == rows` в обоих проходах (`rows=4 artifacts=4 owed=4`, затем `1/1/1`), а `select state, count(*), count(path) from exports group by state` дал `expired | 12 | 0` — ни одна истёкшая строка не называет файл. То есть **повторяемый unlink (дофикс F5) ни разу не оставил долга на следующий проход**, и проверено это не тестом, а стендом, работавшим сам по себе.
## ПОСЛЕ ЛЕНДИНГА `adf5e53`: КАНОН УЕХАЛ В `0.10.0`, А СБОРКА ОБЪЯВЛЯЛА `0.9.0` — зона была КРАСНОЙ (04.09, `textmachine-main-34`)
**Найдено собственной пере-проверкой уже ПОСЛЕ того, как пак принят и заленджен.** Оркестратор ратифицировал контрактный минор (`D39.194`, канон `58bca19`) следом за моим кодом (`adf5e53`), и `go test ./internal/gates/` покраснел: `this build announces contract 0.9.0 and the ratified canon is 0.10.0`. Гейт читает канон, а не копию числа, поэтому поймал сразу.
**Поднять одну константу было НЕЛЬЗЯ, и это главное в этой записи.** Минор объявляет шестнадцатую причину замечания — `off_target_lang``wrong_language` (Приложение А). Карта причин на платформе **рукописная и знала пятнадцать** (`internal/ingest/notes.go`), то есть причина ехала читателю как `unspecified` со ступенью «взгляд». Подъём версии без этой строки объявил бы контракт, которого сборка не отдаёт, — ровно тот дефект, ради которого гейт и существует, только вывернутый наизнанку.
**Сделано две половины, а не одна.** (1) Шестнадцатая строка карты: `"off_target_lang": {"wrong_language", StepAttention}`. (2) `ContractVersion``0.10.0`. Батарея после: `make check` выход 0, скипов 0, «every test ran», линтер `0 issues`, `ALARM 11 (baseline 11)`.
**СТУПЕНЬ — МОЁ РЕШЕНИЕ, и я называю его решением, а не выводом.** Канон оставляет колонку «Ступень» пустой (⬜, ждёт слов владельца). Правило этой карты — «потерял ли читатель текст», и ответ на чужом языке не оставляет от главы ничего пригодного, поэтому `StepAttention`, рядом с `empty_answer`. Ранг движка для этой причины сегодня — дефолтные 8 («недоступно»), что для внятного ответа на чужом языке ложно; ранг 1 приезжает движковым паком. **Эта половина карты не читает ранг вовсе**, так что расхождение на неё не влияет.
**ПРАВКА ТЕСТА ОБЪЯВЛЯЕТСЯ (D39.183).** `TestEveryReasonAnswersItsOwnStep` держал `len(notes) != 15` и перечисление пятнадцати причин; стало шестнадцать и добавлена строка `off_target_lang → StepAttention`. Что изменилось в поведении: причина, ранее ехавшая как `unspecified`/«взгляд», теперь едет как `wrong_language`/«внимание». Куда уехала гарантия: тест по-прежнему пинит КАЖДУЮ строку поимённо и их счёт — он не ослаблен, он описывает новую карту. Правка вызвана ратификацией, а не желанием зелени.
**`PD-246` НЕ закрыт этой правкой и закрыт быть не мог.** Ряд предсказывал ровно это: словарь принадлежит движку, карта платформы — рукописная копия, программы выпускаются независимо. Событие наступило, строка добавлена — но окно между двумя релизами закрывает механизм, которого нет. Замер стоимости окна теперь есть и вписан в ряд: **один рабочий день, всё это время код на проводе был ложным.**
## СМОУК, КОТОРЫЙ ПРОХОДИЛ ПРИ МЁРТВОМ ДЕМОНЕ — оба стендовых рецепта зоны исправлены (04.09, `textmachine-main-34`)
**Находка не моя: её увидел оркестратор №22, когда пошёл ПРОВЕРЯТЬ ИСПОЛНЕНИЕМ мою ретракцию про причину смертей демона.** Я назвала чужой стенд на 8080 неудобством («следующая сессия решит, что видит свои данные»). Он проверил и показал, что это не неудобство: **мой же рецепт воспроизводит коллизию и глушит её ложной зеленью.**
@ -433,7 +451,7 @@ make vuln # No vulnerabilities found.
4. **Третий адрес двери** (`.../exports/{exportId}/content`) канон описывает словами и `operationId` ему не даёт. Предлагаю дать: сегодня это единственный маршрут поверхности, которого нет в перечне операций, и генерируемый клиент про него не знает — а браузер по нему НАВИГИРУЕТ, что и есть его способ существования.
5. **Шестнадцатая причина флага.** Бэкенд-сессия завела `off_target_lang` (`backend/internal/pipeline/disposition.go`, ещё не заленджено). Приложению А нужен ряд: новый код замечания + ступень. ⚠ Фраза пишется по ДОККОММЕНТУ константы, а не по её имени — правило уже записано в компаньоне канона. Карту `internal/ingest/notes.go` НЕ дополняла: контрактного кода ещё нет, а изобрести его значило бы править канон своей рукой.
6. **`Export.url` объявлен `format: uri`, а отдаётся ОТНОСИТЕЛЬНЫЙ путь** — расхождение с ратифицированным контрактом, найдено адверсариальным проходом. Предлагала минор `uri``uri-reference`; **✅ РАТИФИЦИРОВАН оркестратором 04.09**, правит он, и по его же сверке вхождений ДВА, а не одно. Довод принят в усиленном виде: абсолютный URL заставил бы сервис знать свой публичный ориджин, которого за edge-прокси он надёжно не знает — тот же класс, что `PD-101`. Кода это не касается: относительный путь и был тем, что дверь отдаёт.
6. **`Export.url` объявлен `format: uri`, а отдаётся ОТНОСИТЕЛЬНЫЙ путь** — расхождение с ратифицированным контрактом, найдено адверсариальным проходом. Предлагала минор `uri``uri-reference`; **✅ РАТИФИЦИРОВАН оркестратором 04.09** нотой `D39.194` (контрактный минор `0.10.0`), правит он. ⚠ **ВХОЖДЕНИЕ ОДНО — как я и называла изначально.** Первая редакция этого пункта повторяла его сверку «вхождений ДВА»; он её ОТОЗВАЛ, прочитав второе место перед правкой: там поле `type` проблемы RFC 9457 со значением `about:blank`, а это по стандарту абсолютный URI, и трогать его нельзя. ⚠ Класс ошибки назван им прямо и стоит того, чтобы жить здесь, а не в оговорке: **ратификация по грепу без чтения предмета** — совпадение строки принято за совпадение смысла. Тот же класс, что мой собственный счёт красных якорей по обрезанному выводу. Довод принят в усиленном виде: абсолютный URL заставил бы сервис знать свой публичный ориджин, которого за edge-прокси он надёжно не знает — тот же класс, что `PD-101`. Кода это не касается: относительный путь и был тем, что дверь отдаёт.
7. **`409 book_not_ready` на `createExport` — ✅ РЕШЕНО 04.09 ОРКЕСТРАТОРОМ И ИСПОЛНЕНО: код СНЯТ.** Я вынесла это как «канон спорит сам с собой»; оркестратор прочитал описание целиком и показал, что противоречия нет — есть одна недвусмысленная фраза «Nothing about a book's state conflicts with exporting it», которую нарушал КОД. Он прав, и `book_not_ready` на этой двери был изобретением кода. Снято: запрос принимается ВСЕГДА, а что получает книга без дерева глав — отвечает сам экспорт. Пины: `TestABookThatWasNeverCutIsAcceptedAndAnsweredByItsExportRatherThanRefused`, `TestNoBookStateIsARefusalOnThisDoor`, `TestTheWorkersReadKnowsWhetherTheBookWasEverCut`; посадки P1/P2/P3 — все три красят адресно.
**И ОДНО ВОЗРАЖЕНИЕ ПО ОСТАТКУ ТОГО ЖЕ РЕШЕНИЯ, С ЗАМЕРОМ.** Оркестратор велел отдавать книге без глав «артефакт с пометкой», применяя `D39.178` («дверь ВСЕГДА строит файл»), и оставил мне текст пометки. **Артефакта не существует, и это проверено исполнением, а не чтением:**

View file

@ -10,7 +10,7 @@ import "net/http"
// client generated against another one refuses to work and says so — which is why this must be
// raised in the same commit as the code that implements a new minor, and never as a courtesy
// afterwards.
const ContractVersion = "0.9.0"
const ContractVersion = "0.10.0"
// Capabilities is what this deployment can do: one flat document, the same for every account.
type Capabilities struct {

View file

@ -57,6 +57,18 @@ var notes = map[string]note{
"glossary_miss": {"term_not_applied", StepGlance},
"length": {"length_mismatch", StepGlance},
"sanitizer_stripped": {"markup_cleaned", StepGlance},
// The sixteenth, ratified with contract minor 0.10.0 (D39.194). It is the row PD-246 predicted:
// the engine owns the vocabulary, this map is a hand copy, and the two ship independently — so
// the reason arrived here as `unspecified` until this line existed.
//
// ⚠ The STEP is this side's reading and not the engine's rank, by the rule above: an answer that
// came back in the wrong language is text the reader LOST — nothing of the chapter is usable — so
// it is Attention, alongside `empty_answer`. The canon leaves the column ⬜ awaiting the owner's
// phrases; the reading is recorded here so it can be argued with.
// ⚠ The engine's own rank for it is TODAY the default 8 ("unavailable"), which is false for a
// fluent answer in the wrong language; rank 1 arrives with the engine pack. That is the engine's
// half and it does not reach this map: nothing here reads the rank.
"off_target_lang": {"wrong_language", StepAttention},
}
// NoteCode maps one engine reason onto what the wire carries.

View file

@ -61,12 +61,14 @@ func TestEveryReasonAnswersItsOwnStep(t *testing.T) {
"loop_degenerate": StepAttention, "decode_error": StepAttention,
"empty": StepAttention, "upstream_not_ok": StepAttention,
"glossary_miss": StepGlance, "length": StepGlance, "sanitizer_stripped": StepGlance,
// The sixteenth (minor 0.10.0): the reader lost the chapter, so Attention.
"off_target_lang": StepAttention,
} {
if _, step := NoteCode(reason); step != want {
t.Errorf("%s answers step %q, want %q", reason, step, want)
}
}
if len(notes) != 15 {
t.Errorf("appendix A has fifteen reasons, the map has %d", len(notes))
if len(notes) != 16 {
t.Errorf("appendix A has sixteen reasons, the map has %d", len(notes))
}
}