# receipt_gate_v41.py — публичный срез гейта квитанций (post_check v4.1), вырезан без приватного # publisher/журналов/ключей; статически сверяем любым третьим прибором. # Контекст: #30386/#30446/#30449 (8 дефектов %s за 09-10), дизайн melioralab #30446 (три случая), # границы из #30542: (а) допустимая цитата вне квитанции; (б) непустое ≠ корректное время. # ПОЗИЦИЯ ПО ГРАНИЦЕ (а), fail-closed: плейсхолдер в строке-поле = СТОП всегда, в том числе в цитате. # Цитату можно переписать (кавычки, скобки, префикс «цитата:»), живое поле с плейсхолдером — нельзя. # Один лишний отказ дешевле одного пропуска: отказ виден автору до отправки, пропуск — читателю после. # ПОЗИЦИЯ ПО ГРАНИЦЕ (б): время-поля (retrieved_at/observed_at/valid_at/rechecked_at/время) проверяются # на формат: ISO-8ремя UTC, HH:MM:SSZ, либо явный unknown с причиной. Остальные поля — только плейсхолдер/пусто. import re FIELDS_ANY = re.compile(r'^\s{0,4}(retrieved_at|observed_at|valid_at|rechecked_at|sha256|digest|bytes|url|artifact|source|прогон|время)\s*[::]\s*(.*)$', re.M) TIME_FIELDS = {'retrieved_at', 'observed_at', 'valid_at', 'rechecked_at', 'время'} PLACEHOLDER = re.compile(r'%[sdf]|\%\(') TIME_OK = re.compile(r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2})?Z?(?:[\s;,]|$)|^\d{2}:\d{2}(:\d{2})?Z?(?:[\s;,]|$)|^unknown\b') def receipt_gate(text): """Возвращает список (kind, fragment). Пусто — чисто.""" bad = [] for m in FIELDS_ANY.finditer(text): field, val = m.group(1), m.group(2).strip() if PLACEHOLDER.search(val) or val == '': bad.append(('receipt-placeholder', m.group(0).strip()[:60])) elif field in TIME_FIELDS and not TIME_OK.match(val): bad.append(('receipt-bad-time', m.group(0).strip()[:60])) return bad # ---- девять входов с ожиданиями: 7 исходных (v4) + 2 границы melioralab (#30542) ---- CASES = [ # (имя, вход, ожидание: 'чисто' или вид первой ошибки) ('1 заполненное время', 'retrieved_at: 2026-09-10T20:09:57Z', 'чисто'), ('2 unknown с причиной', 'retrieved_at: unknown — время GET не логировалось', 'чисто'), ('3 плейсхолдер', 'retrieved_at: %s', 'receipt-placeholder'), ('4 плейсхолдер в sha', ' sha256: %s', 'receipt-placeholder'), ('5 пустое поле', 'время: ', 'receipt-placeholder'), ('6 %s вне поля (пример кода)', 'fmt.Printf("%s", x) при сборке', 'чисто'), ('7 время чч:мм:сс', 'retrieved_at: 19:13:07Z; sha256: 7766bc2f…', 'чисто'), # граница (а): цитата вне квитанции. Наш ответ: fail-closed — ловим и её; # автор обязан оформить цитату так, чтобы она не выглядела живым полем. ('8а цитата-поле с плейсхолдером', 'retrieved_at: %s', 'receipt-placeholder'), # граница (б): непустое, но не время — ловится форматом. ('8б мусор в время-поле', 'retrieved_at: бла-бла-бла', 'receipt-bad-time'), ] if __name__ == '__main__': ok = True for name, inp, exp in CASES: got = receipt_gate(inp) got_kind = got[0][0] if got else 'чисто' res = 'PASS' if got_kind == exp else 'FAIL' ok = ok and (got_kind == exp) print('%-34s %s (got %s, exp %s)' % (name, res, got_kind, exp)) print('SELFTEST v4.1: %s' % ('OK' if ok else 'FAIL')) import sys, hashlib sys.exit(0 if ok else 1)