Build the tail-vocabulary gate the refusal only named: honour escaped pipes, read the register weight from the front, and judge the tail by vocabulary rather than cell count

This commit is contained in:
heaven 2026-08-27 14:59:21 +03:00
parent 371f15ff2b
commit fb1d09be5f
3 changed files with 43 additions and 6 deletions

View file

@ -13,6 +13,7 @@
> ⚠ **Эррата 27.08 (D39.158 п.7):** D39.153 п.7б объявляет «ОСТАТКА-РАТИФИКАЦИИ НЕТ… гейт проверяет полноту ФАЙЛА решений, а не подписей, и ОДИН файл со всем банком его снимает — конфликта с моделью владельца нет». Читать так: диспозиция отвечала на вопрос «снимает ли гейт один файл» (снимает) и верифицировала СОСТАВНОЕ поведение — платформа снимает флаг, движок уходит по авто-ветке. Вопрос «работает ли флажок БЕЗ обхода» ею не закрывался, и заказанное той же строкой 191(б) «ослабить границу самой паузы» исполнено не было. Исполнено D39.158: движковая половина УСТРАНЕНА, не обойдена.
> ⚠ **Эррата 27.08-б (D39.158, страница `18-bank-ontology.md`):** названный там класс дефекта «проекция без потребителя и без якоря свежести» на момент ратификации имел ОДИН ярус и не судил носитель с ДВУМЯ писателями и нулём читателей. Дописан второй ярус: расхождение писателей ничем не судится, пока читателя нет, а в день его появления СТАНЕТ семантикой, разрешаемой задним числом по накопленной истории. Найдено платформенной сессией при вычитке приёмки D39.159 (живой член — `PD-396`); правило «у источника РОВНО один писатель» с этого дня читается в обе стороны.
> ⚠ **Эррата 27.08-в (D39.158 п.1):** «движок исполняет продуктовую модель сам» сказано без ратифицированной ОГОВОРКИ — правка ОТДЕЛЬНОГО термина через дверь доступна только для mined-термов; правка СИД-терма дверью отклоняется по имени (`internal/membank/decisions.go:359`), потому что это перенос базового снапшота и пере-оплата черновой волны — другое решение (щель ратифицирована, строка бэклога 192). Ещё три места, где модель шире кода, названы строками: окно глав одним решением не правится (named-узость v1) · банк-экспорт на стопе пуст (224) · «полный останов» — это граница ПЕРЕД редакторской волной, черновая довершается (S16). Найдено вычиткой старшего при ревью D39.159.
> ⚠ **Эррата 27.08-г (D39.159 п.8):** отказ от правки `n != shape` СТОИТ, но обоснование было шире истины — «краснит семь ЗАКОННЫХ строк» неверно для двух из них. `PD-99` несла корректно markdown-экранированные черты (виноват был парсер, не строка), а `PD-197` — сырую черту в регексе, которая молча сдвигала ВЕС, потому что он читался с конца, а от конца далеко. Названный там остаточный риск ПОСТРОЕН 27.08 на эмпирике зоны (три молчаливых случая за двое суток): парсер уважает экранирование · вес регистра читается с НАЧАЛА · гейт `tail_vocab` судит словарь хвоста. Проверено подсадкой; числа не сдвинулись. Строка `PD-398` остаётся открытой на отсутствие автоматического пина у самого гейта.
> ⚠ **Навигация (актуализация 07.08, эра D39.1xx):** append-only-дисциплина (D23.3) означает, что
> НЕВЕРНЫЙ ФАКТ внутри старой ноты не переписывается, а получает эрратау — и тогда он опасен ровно

View file

@ -126,8 +126,13 @@ def read(rel: str) -> str:
return got.stdout
# `\|` — ЗАКОННОЕ markdown-экранирование: таблица рендерит его как черту внутри ячейки. Наивный
# split() его не понимает и дробит строку лишний раз (поймано на PD-99 27.08).
CELL_SPLIT = re.compile(r"(?<!\\)\|")
def cells(line: str) -> list[str]:
return [c.strip() for c in line.split("|")]
return [c.strip() for c in CELL_SPLIT.split(line)]
# ⚠ Колонки читаются С КОНЦА, а не с начала. Первая содержательная ячейка («Хвост»/«Суть») законно
@ -140,7 +145,7 @@ REGISTER_SHAPE = 9 # '' | ID | класс | серьёзность | где |
def col(line: str, from_end: int) -> str:
"""Ячейка, считая с конца: 2 = последняя содержательная, 3 = предпоследняя и так далее."""
c = line.split("|")
c = CELL_SPLIT.split(line)
return c[-from_end].strip()
@ -148,9 +153,9 @@ def malformed(rows: list, shape: int, what: str) -> list:
"""Строка таблицы неожиданной формы — расхождение, а не тихий IndexError."""
bad = []
for l in rows:
n = len(l.split("|"))
n = len(CELL_SPLIT.split(l))
if n < shape:
bad.append(f"{what}: строка «{l.split('|')[1].strip()}» имеет {n - 2} колонок, ожидалось {shape - 2}")
bad.append(f"{what}: строка «{cells(l)[1]}» имеет {n - 2} колонок, ожидалось {shape - 2}")
return bad
@ -174,11 +179,40 @@ def backlog(text: str) -> dict:
}
# ⚠ Хвостовой словарь — вторая половина PD-398, построена 27.08 по эмпирике трёх случаев за двое
# суток (правка PD-396 · первая редакция PD-398 · пере-формулировка PD-398 приёмкой). Счёт ячеек
# ловить ИЗБЫТОК не может: `|` внутри первой содержательной ячейки легален и намеренно (см. col()).
# Но смягчение «читаем с конца, значит лишняя черта безвредна» ОПРОВЕРГНУТО: когда черта попадает в
# одну из трёх ПОСЛЕДНИХ колонок, статус или вес съезжает МОЛЧА — счёт печатает мусорный ключ или
# «иное», и никто не смотрит. Поэтому судим не форму, а словарь хвоста.
STATUS_VOCAB = ("open", "fixed", "accepted-risk", "closed")
WEIGHT_VOCAB = ("major", "minor", "info", "blocker")
# Доброкачественные исключения — статус прозой, заведены ДО словаря. Расширять только сознательно:
# каждая новая строка здесь — это строка, которую гейт перестал проверять.
STATUS_GRANDFATHERED = {"PD-59"}
def tail_vocab(rows: list, what: str) -> list:
"""Статус и вес читаются С КОНЦА: лишний «|» в хвосте сдвигает их молча. Судим по словарю."""
bad = []
for l in rows:
pid = cells(l)[1]
st = col(l, 3).replace("*", "").strip().lower()
if not st.startswith(STATUS_VOCAB) and pid not in STATUS_GRANDFATHERED:
bad.append(f"{what}: строка «{pid}» — статус «{st[:40]}» вне словаря "
f"(лишний «|» в одной из трёх последних колонок?)")
sev = cells(l)[3].replace("*", "").strip().lower()
if not any(k in sev for k in WEIGHT_VOCAB):
bad.append(f"{what}: строка «{pid}» — вес «{sev[:40]}» вне словаря "
f"(лишний «|» в одной из трёх последних колонок?)")
return bad
def register(text: str) -> dict:
rows = [l for l in text.splitlines() if REGISTER_ROW.match(l)]
weight, status, open_ids = Counter(), Counter(), []
for l in rows:
st, sev = col(l, 3), col(l, 6).replace("*", "")
st, sev = col(l, 3), cells(l)[3].replace("*", "")
if st.startswith("open"):
status["open"] += 1
open_ids.append(cells(l)[1])
@ -195,6 +229,7 @@ def register(text: str) -> dict:
"вес открытых": dict(weight),
"открытые": open_ids,
"битая форма": malformed(rows, REGISTER_SHAPE, "регистр"),
"хвост вне словаря": tail_vocab(rows, "регистр"),
}
@ -570,6 +605,7 @@ def main() -> int:
bad.append(f"дубли ID строк бэклога: {b['дубли ID']}")
bad.extend(b["битая форма"])
bad.extend(r["битая форма"])
bad.extend(r["хвост вне словаря"])
if not h["порядок = номер"]:
bad.append(
f"внеочередной аппенд в D-логе: последняя по порядку {h['последняя по порядку файла']}, "

File diff suppressed because one or more lines are too long