Журнал изменений

GitHub

Каждое значимое изменение, релиз за релизом. Тот же файл публикуется в репозитории и переведён на все 19 языков.

[1.260.0] — 2026-10-26

Состав укрепления по итогам ревью, восемь мержей (#491–#498) из раундов opus-5-5 и AI-ревью над флотом v1.259.0: нормализация зарплат, скоупинг дат отчётов, устойчивость геокода, сброс кэша бандла r2+r3, фикс переполнения deep — плюс два интерфейса v2 (pipeline, reports). Каждое исправление въехало через красно-зелёный fix-forward.

Исправлено

  • Нормализация сравнения зарплат (CAR-143 rev-2): маркеры периода распознаются только рядом с той цифрой, к которой относятся — «6 month contract, $120k» больше не растягивается до годовой ×12, а присоединённые RU/ES-формы периода («за месяц» рядом с числом) побеждают случайные слова; семейство символов $/¥ распознаётся, но остаётся без маппинга, пока явный ISO-код не делит строку (доллары AUD/CA/SG больше не проводятся как USD, форма ¥ RMB получает явную оговорку вместо конвертации в JPY с промахом ~20×); голые числа 19xx/20xx — всегда годы; цифры диапазона наследуют период партнёра; minimum и target_range профиля никогда не склеиваются в один диапазон.
  • Скоупинг fixReportDate (CAR-144 rev-2): серверная коррекция даты применяется только к преамбуле до первого заголовка ## (отчёт с заголовком-H2 получает консервативный no-op — устаревшая дата приемлема, выдуманная — нет); внутри преамбулы: исправляется единственный кандидат, иначе единственный жирный **Date:**, иначе ничего (два жирных или два обычных кандидата — не переписывается ни один). Гейт PDF-подписей FIX-5 теперь читает реально собранный словарь — отсутствие локали бросает ошибку вместо тихого прохода на английском фолбэке.
  • Чип локации Хабра + устойчивость геокода (CAR-142 rev-2): мета-чип Хабра становится городом только при структурном свидетельстве (ссылка на фильтр по городу или иконка локации) — уведомления о релокации («Возможна релокация») и типы занятости никогда не маскируются под локации; кэш геокода теперь переживает рестарт (сброс на диск по beforeExit/exit/SIGTERM+SIGINT с ре-рейзом сигнала, грязный флаг снимается только после того, как запись легла), завершение ограничено (защёлка exit-цикла, счётчик поколений, LRU по свежести), а исчерпанный бюджет геокода отвечает 503 + Retry-After вместо общего 502.
  • Сброс кэша бандла (CAR-145 r2+r3): оболочка ссылается на /js/bundle.js?v=<content-hash> — immutable при совпадении, no-store при несовпадении — закрывая 5-минутное окно деплоя, где свежая оболочка сочеталась с закэшированным старым бандлом. Хэш и поток делят один файловый дескриптор (никаких рваных чтений); короткое чтение отвечает null вместо хэш-фолбэка по stat, способного отравить целый деплой; make-bundle пишет файл идентичности и компаньоны .br/.gz через temp-файл с точкой в начале имени + rename, так что убитая сборка не может ни отдать полузаписанные байты, ни оставить после себя temp-файл.
  • Переполнение deep (CAR-146): сохранённый бриф исследования, у которого одно лишь имя файла шире экрана телефона, больше не растягивает #/deep дальше 390px — карточка ограничена своей строкой, заголовок переносится, дата остаётся на одной строке; обход узких вьюпортов теперь проходит и #/deep с намеренно сверхдлинной засеянной фикстурой (единственная находка двух прогонов remote-QA закрыта).
  • Укрепление Pipeline v2 (ревью CAR-116): удаления — с подтверждением и отменой (честный текст отмены), никакого выдумывания компании из хоста в карточках, идентификаторы строк FNV-1a, точные счётчики массовых операций и защита от повторного входа на них.

Добавлено

  • Pipeline v2 (CAR-116): обогащённые карточки, сгруппированный вид и массовое отбраковывание с отменой; 17 новых ключей pipe.* поставлены переведёнными во всех 19 словарях.
  • Reports v2 (CAR-118): живой поиск с фильтрами по полосе оценки/легитимности/компании/дате, сортируемые заголовки (aria-sort), сохраняемый карточный вид с кнопками действий по полосам, гистограмма оценок с фильтрацией по клику и унифицированный рендеринг «Company — Role» / «x.x/5» (window.ReportOps); 31 ключ rep2.* поставлен переведённым во всех 19 словарях.

Примечания

  • Каждое исправление прошло раунд ревью с красно-зелёным доказательством; две HIGH-находки AI-ревью к первому r2 бандла (хэш по короткому чтению, атомарность компаньонов) закрыты в r3, а раунды CAR-143/CAR-144/CAR-142 каждый перепроверили открытые находки своего предшественника.
  • Намеренно не сделано: кластерный вид карты остаётся за CAR-125 (в очереди); восстановление метаданных pipeline на стороне сервера — отдельный тикет; ретроспективных правок для контента, сохранённого в состояниях до rev-2 v1.259.0, нет.
  • Тесты 5988 (+165). Браузерный набор: 152 успешно / 0 провалов / 1 opt-in пропуск. Источники сканера не тронуты (реестр 121 = 116 EN + 5 RU · 116 адаптеров); 48 новых ключей i18n легли со своим fan-out ×19 внутри самих фичевых PR.

[1.259.0] — 2026-10-25

Второй состав агентного флота, шесть исправлений: даты отчётов фиксируются на стороне сервера, геокодер карты кэширует ответы и распознаёт русскоязычные города, справка ×18 цитирует локализованные подписи кнопок, фразы интерфейса перестают смешивать языки, сравнение зарплат считается на сервере — а JS-бандл поставляется предсжатым (холодная загрузка 3.1 MB → 598 KB).

Исправлено

  • Выдуманные даты отчётов (CAR-101): промпт оценки теперь фиксирует сегодняшнюю дату на стороне сервера («поле Date: ОБЯЗАНО быть ровно — скопируй дословно, ничего не выдумывай»), а fixReportDate переписывает уползшую дату на сегодняшнюю в каждой точке приёма отчёта (ветка Anthropic, сабпроцесс Gemini, OpenAI-совместимый хвост, /api/deep, auto-pipeline) с предупреждением и записью в activity — это коррекция, а не отказ. 349/789 продовых отчётов несли выдуманные даты; у новых это исключено.
  • Геокодирование карты (CAR-105): кириллические названия городов в строках hh.ru/Хабра теперь доходят до геокодера; персистентный ограниченный кэш (data/geocode-cache.json, под withFileLock) гарантирует, что найденный однажды город больше никогда не пойдёт к провайдеру повторно; вызовы геокода бюджетируются на запрос, поэтому карта заполняется постепенно, а не блокируется на 13.9 с.
  • Смешение языков в UI (CAR-108): 23 утечек по ключам исправлены в 17 из 19 словарей (85 значений) — «все matching», «только remote», «Открыть Scan», воронка «Evaluated → Applied…» и другое стали полноценными локализованными фразами, переиспользующими собственный продуктовый словарь каждой локали. i18n-no-latin-leaks расширен таблицей фрагментов и пробой воронки (красный→зелёный).
  • Переворот вердикта по валюте (CAR-109): сравнение зарплат считается на сервере в comp-compare.mjs и встраивается канонически (Candidate minimum: … per YEAR (raw: …) против JD offer: …); месячные ставки ×12, формы k/к/тыс/萬 и десятичная запятая нормализуются до сравнения; неуверенная конвертация сама по себе больше не может перевернуть вердикт (кейс zh-TW «€80–100k ниже минимума» мёртв). Модель больше никогда не умножает.
  • Подписи кнопок в справке ×18 (CAR-107): локализованные бандлы справки цитировали английские подписи («⚡ Run live», «Save», «Scan now»), которых UI больше не показывает — 263 замены в 18 локалях, выверенные по исходникам вью; ассистент справки наследует исправление.

Добавлено

  • Предсжатый бандл + кэшируемая холодная загрузка (CAR-104): make-bundle.mjs на этапе релиза создаёт bundle.js.br (brotli q11) и .gz; middleware со скоупом бандла согласовывает br > gz с Vary: Accept-Encoding, public, max-age=300, must-revalidate и ETag/304; с защитой свежести (компаньонный файл никогда не отдаётся старее основного бандла), прямые запросы артефактов получают 404, CSP байт-в-байт идентичен. Холодная загрузка: 3.1 MB → ~0.6 MB по сети (−80%); повторные визиты в течение 5 минут передают ≈0 байт.

Примечания

  • Все исправления пришли из агентного флота с красно-зелёным доказательством (13 + 28 + 1 + 30 + 7 новых кейсов в их наборах тестов) и прошли раунды ревью (CodeQL #579/#580/#584 исправлены в коде — стаб-URL парсятся через new URL().host, мёртвая строка промпта удалена).
  • Числа дозаполнения оценок v1.258.0 теперь зафиксированы в CAR-129 (updated: 22, reportScoreless: 159).
  • Намеренно не сделано: нет ретроспективной правки дат у 349 исторических отчётов (дозаполнение дат было бы операцией с одобрения владельца), нет кластерного вида карты (CAR-125 остаётся в очереди за этим гео-фиксом).
  • Тесты 5823 (+74). Источники сканера не тронуты (реестр 121 = 116 EN + 5 RU · 116 адаптеров); ключей i18n нет (CAR-108 правит только значения существующих ключей).

[1.258.0] — 2026-10-25

Шесть исправлений от агентного флота одним составом: счётчик компаний в #/scan больше не смешивает вселенные, углублённое исследование требует источников, ошибки форм подсвечивают само поле, состояния загрузки получают скелетоны и мгновенный h1, а оценки кэшируют свой 37k-токенный префикс промпта.

Исправлено

  • Счётчик «Active companies» в #/scan (CAR-100): числитель считал различные компании по ВСЕМ строкам результатов (1554 на проде — один hh.ru даёт сотни), а знаменатель — отслеживаемые компании с API-доступом (~90) — две вселенные в одной дроби. Вариант (a): числитель теперь считает компании среди строк, которые сам API-скан отслеживаемых бордов и принёс (матч по _entry ?? company; строки работодателей hh.ru/telegram выпадают); пре-скановый фолбэк переехал в ту же вселенную (он мог показывать «104/90»).
  • Выдумывание фактов в углублённом исследовании (CAR-110): промпт deep теперь несёт жёсткий контракт — каждый фактический тезис заканчивается маркером [source: domain.com] (не можешь назвать источник → не делай утверждение; выдуманный маркер хуже написанного unknown), неизвестные факты записываются как unknown, а отчёт ОБЯЗАН заканчиваться секцией ## Data freshness. Якоря свежести: сегодняшняя дата по UTC + опциональный url компании в POST /api/deep (с ограничением размера, сервером никогда не запрашивается).
  • Ошибки форм подсвечивают поле (CAR-130): неудачный POST помечает первое поле формы aria-invalid + .is-error и фокусирует его (WCAG 3.3.1); снимается при правке. Подсказка сетевой ошибки учитывает деплой — loopback сохраняет подсказку с dev-командой, а развёрнутый origin получает «сервер не отвечает» вместо совета с bash web-ui/bin/start.md (у баннера подключения был тот же хардкод).
  • Состояния загрузки (CAR-112): #/map и #/tracker рендерят свой h1 мгновенно (цель фокуса роутера существовала только после прихода данных), а общий хелпер скелетонов (Skeleton.rows, мерцающие строки aria-hidden внутри региона role="status", переиспользующего common.loading) заменяет голый текст «Загрузка…» на трекере, в результатах скана и в оболочке карты; prefers-reduced-motion останавливает пульсацию.
  • Кэширование промпта оценки (CAR-111): buildEvaluationPromptParts разбивает промпт (~37k токенов) на байт-стабильный префикс (CV + профиль + файлы режимов + инструкции) и хвост с JD; путь Anthropic отправляет [prefix + cache_control: ephemeral] + [JD] — префикс тарифицируется по ставке чтения из кэша (≈10× дешевле) на каждом вызове после первого. Отсутствующий или несовпадающий префикс откатывается (fail-open) к побайтово идентичному старому запросу; OpenAI-совместимая каскадная цепочка провайдеров не тронута (там кэширование на стороне сервера).

Примечания

  • Все шесть исправлений пришли из агентного флота с красно-зелёным доказательством (5 + 7 + 7 + 7 + 8 новых кейсов) и раундами ревью; первые CodeQL-алерты CAR-111 (#579/#580) исправлены в коде — тестовый стаб парсит хост URL вместо substring-сравнения, а мёртвая строка полного промпта удалена.
  • Дозаполнение оценок выполнено на проде (эндпоинт v1.256.0, с одобрения владельца): updated: 22, missingReport: 0, reportScoreless: 159 — 159 строк без оценки указывают на отчёты, чьи заголовки старше машинного блока или несут нераспарсиваемые форматы оценки; восстановимо только переоценкой или более широким проходом парсера (отмечено в CAR-129).
  • Намеренно не сделано: нет изменения nulls-last для колонки commute (уже существующая особенность Infinity при нисходящей сортировке), нет ретроспективного переценивания старых записей usage, нет подсветки в стиле .is-error для форм фильтров скана (CAR-130 ограничен общим хелпером отправки mode-page + подсказкой в api.js).
  • Тесты 5749 (+36). Источники сканера не тронуты (реестр 121 = 116 EN + 5 RU · 116 адаптеров); ключей i18n нет (подсказка CAR-130 переиспользует conn.down; регион скелетонов переиспользует common.loading).

[1.257.0] — 2026-10-25

Два исправления от агентного флота: встроенный ассистент справки понимает деванагари и тамильский, а страница #/usage показывает, сколько Hermes стоит на самом деле.

Исправлено

  • Поиск в ассистенте справки для hi/ta (CAR-103): класс слов в ключевом токенизаторе docs-assistant был [\p{L}\p{N}\u30fc]+ — без комбинируемых знаков. Каждая матра деванагари (ा ै ्…) и каждый тамильский знак гласной (ே ை ்…) разрезали слово на обрывки; правило длины отбрасывало короткие обрывки (хинди-запрос матчил 0 секций), а более длинные слова обрубались в мусор, сталкивавшийся с посторонними секциями. Теперь класс включает \p{M}; CJK-путь биграмм не тронут. hi «मैं स्कैन कैसे चलाऊं?» и ta «ஸ்கேன் எப்படி இயக்குவது?» достают правильные секции из поставляемых бандлов справки.
  • Стоимость расхода для Hermes (CAR-106): normalizeUsage() сжимал usage провайдера до {in, out} и молча выбрасывал переданную шлюзом cost — #/usage показывал $0.0000 на 9.5 M токенов Hermes (его строка в таблице цен — 0/0). Форма записи usage получает опциональное обратно совместимое поле cost?; агрегатор предпочитает стоимость, переданную провайдером, в каждой записи и откатывается к оценке из таблицы цен, только когда её нет; мусорные и отрицательные значения отбрасываются, никогда не выдумываются.

Примечания

  • Оба исправления пришли из агентного флота с красно-зелёным доказательством (3 + 5 новых кейсов; 14/14 и 10/10 зелёные; соседние 78/78 и 61/61). Первые PR (#470/#471) несли устаревшую базу worktree, конфликтовавшую со squash-смерженной v1.256.0, — заменены чистыми cherry-pick’ами (#472/#473).
  • Намеренно не сделано: исторические строки data/llm-usage.jsonl ретроспективно не переоцениваются (в них нет поля стоимости; выгоду получают только новые записи).
  • Тесты 5713 (+8: docs-assistant ×3, usage ×5). Источники сканера не тронуты (реестр 121 = 116 EN + 5 RU · 116 адаптеров); ключей i18n нет.

[1.256.0] — 2026-10-25

Качество данных трекера: роль больше не записывается как компания, борд вакансий больше не записывается как работодатель, строки без оценки перестают всплывать первыми — и одноразовый эндпоинт дозаполняет недостающие оценки из самих отчётов.

Добавлено

  • Дозаполнение оценок (CAR-129): POST /api/tracker/backfill-scores заполняет ячейку Score у строк, чей связанный отчёт несёт распарсиваемую оценку (181 оценённая строка имела отчёт, но не имела оценки). Оценённые строки, явные сентинелы (N/A), строки без отчёта и отчёты без оценки не трогаются вовсе; переписывается только ячейка Score, под файловой блокировкой; форма оценки валидируется в месте записи и нормализуется к конвенции трекера X.X/5; прогон идемпотентен и возвращает {ok, updated, missingReport, reportScoreless}.

Исправлено

  • Роль — никогда не компания (CAR-128): guessCompanyRole считал ЛЕВУЮ сторону строки заголовка с тире компанией, но борды пишут оба порядка — «Senior Frontend Engineer - Grafana Labs» записывал роль КАК компанию (13 строк: Grafana, CircleCI…). Теперь эвристика ключевых слов роли с границами слов выбирает сторону роли, а тип занятости никогда не становится работодателем («Senior Engineer - Remote (EU)» оставляет работодателя пустым, а не «Remote (EU)»).
  • Борд вакансий — никогда не работодатель (CAR-127): фолбэк по слагу домена записывал строки hh.ru как «hh» (120 строк). Слаги hh/habr пополняют список не-работодателей; вывод работодателя из середины предложения («в компании X», «X ищет») теперь срабатывает и для хостов бордов вакансий (hh.ru/kz/uz/by, habr.com) — там, где текст страницы называет настоящего работодателя. По пути найдена первопричина: ASCII \b/\w в тех шаблонах вывода вообще никогда не совпадали с кириллицей — молча мертвы с v1.248.4; их заменяют Unicode-lookbehind’ы и классы \p{L}.
  • Строки без оценки сортируются последними (CAR-129): сортировка трекера по оценке использовала сентинел -1, поэтому при восходящей сортировке по умолчанию каждая строка без оценки всплывала раньше каждой оценённой. Теперь null’ы уходят в конец при обоих направлениях (ФИЛЬТР по оценке сохраняет свой -1 — «строка без оценки не проходит фильтр ≥4» — верная семантика именно там).

Примечания

  • Исторические строки этим релизом НЕ мигрируются: фиксы парсера применяются к новым оценкам; оценки восстанавливаются через эндпоинт дозаполнения, а вот 120 ячеек работодателя «hh» требуют переоценки, а не перезаписи.
  • В релиз вошёл раунд ревью opus-5-5: неякорный шаблон «X ищет» выброшен до публикации (любая последовательность слов с заглавной буквы перед глаголом записывалась бы как работодатель — инвариант «ничего не выдумывать»), добавлены якоря начала предложения «В…», а писатель дозаполнения перепроверяет целевую ячейку (после ручных правок существуют дубли строк с одинаковым #), сохраняет число колонок короткой строки, искажённой вручную, и останавливается на первой строчке вне таблицы. Точка внутри имени с точкой («Сбер. IT») неотличима от точки конца предложения для якоря «в компании» — записывается консервативный префикс («Сбер»); шаблон, заякоренный по глаголу, ловит полную форму.
  • Намеренно не сделано: компаратор не извлечён из tracker.js в юнит-тестируемую библиотеку (исходно-статический гейт остаётся; отмечено в CAR-129 как follow-up), нет изменения nulls-last для колонки commute (уже существующая особенность Infinity при нисходящей сортировке), нет миграции данных для строк «hh».
  • Тесты 5705 (+10: 5 identity-тестов в auto-pipeline-jd-gate, 2 CAR-128, гейт сортировки CAR-129, 4 дозаполнения, включая hostile-path pin — минус заменённые кейсы v1.248.x). Источники сканера не тронуты (реестр 121 = 116 EN + 5 RU · 116 адаптеров); ключей i18n нет.

[1.255.2] — 2026-10-24

Три заметимых для пользователя исправления из живой регрессии и отчёта о продовом падении: краш рендера #/scan, слепая проверка языка для вьетнамского и повторяющаяся потеря блока сводки в zh-TW.

Исправлено

  • Краш #/scan (CAR-99): регенерация счётных гейтов, начиная с v1.253.0, записывала литерал FALLBACK_SOURCES с двойной запятой в конце каждого элемента — 120 элизионных дыр в массиве. Тогда new Set(FALLBACK_SOURCES.map((s) => s.value)) во вью сканера держал undefined, а id.length классификатора компаний бросал TypeError: Cannot read properties of undefined (reading 'length') на каждом рендере страницы (поймано logbuf’ом на проде). Дыры убраны, а новый гейт (4 спеки, проверен на красном) исполняет файл и падает на любой дыре, непригодном элементе или undefined в Set идентификаторов адаптеров — текстовый drift-гейт этот класс видеть не мог.
  • Слепая проверка языка vi (CAR-97): вьетнамский — латиница, и в таблице стоп-слов живой QA для него не было записи, поэтому «стоп-слова vi» считали английские вхождения и вьетнамский ответ читался как report not in vi (живая регрессия: vi 2, en 2). Теперь у вьетнамского своя таблица из десяти служебных слов — чисто вьетнамский отчёт проходит, английский по-прежнему падает.
  • zh-TW терял блок SCORE_SUMMARY (CAR-98): закрывающее напоминание о языке говорило лишь, что машинный формат переводить нельзя, но никогда — что блок сводки обязан существовать; короткий отчёт zh-TW (5,983 символа) нёс блоки A–G, но целиком опускал этот блок (рецидив класса v1.239.2). Открывающая директива и закрывающее напоминание теперь явно говорят, что блок обязателен и без него отчёт невалиден.

Примечания

  • Этот релиз намеренно не меняет ни один источник сканера, счётчик реестра (121 = 116 EN + 5 RU · 116 адаптеров) или проводку провайдеров — только исправления, поэтому счётные гейты не тронуты.
  • Тесты 5695 (+9: car99-fallback-holes ×4, car97-vi-lang-check ×4, инвариант промпта CAR-98 ×1). Браузер 143/143. Повторная живая проверка vi/zh-TW едет в постдеплойной регрессии.

[1.255.1] — 2026-10-24

Два раунда исправлений по ревью: ревью Opus вьетнамской локали (9 находок) и второй проход ревью по фиксам CAR-91/92 (5 must-fix) — оба закрыты красно-зелёными тестами.

Исправлено

  • Раунд ревью vi-локали (продолжение CAR-88): хангыльный глиф (신), протёкший в project.subtitle / project.roleHint, исправлен (độ khớp tín hiệu), с гейтом, который падает на любой CJK/Kana-глиф в значении vi, отсутствующем в EN-словаре; шесть URL-адресов маркдауна с пробелами в docs/help/vi.md (what-is career-ops, apply for a job, set up playwright и ещё три) приведены к дефисным реальным слагам гайдов, с гейтом «никаких пробелов в URL».
  • Границы заголовков оценки (CAR-92 r2 H1): &, ., /, ' теперь считаются словесными символами в левой границе блоковой буквы — ## Q&A, ## Plan B, ## Ph.D. requirement, ## E-commerce больше не регистрируются как блоки A/B/D/E; голый терминатор конца строки принимается только после токена блокового слова (Block/блок/블록/…); ASCII - покинул классы словесных терминаторов, так что заголовки E-commerce/ई-मेल переживают разрезание.
  • Отчёты с украинской/русской нумерацией (CAR-92 r2 H2): детектор последовательности А Б В Г Д Є Ж теперь общий хелпер; когда он находит 3+ последовательных буквы на уровне заголовка, этот уровень становится референсным уровнем для резака — буквенные подразделы ### A. больше не обрезают отчёт на блоке Б.
  • Жизненный цикл переносимых строк (CAR-92 r2 M1): строки, которые отказавший источник переносит в следующий скан, получают штамп carriedSince, выбрасываются после 30 дней или 5 переносов подряд и добавляются после свежих строк — вместо того чтобы вечно тихо накапливаться.
  • Знаменатель + легитимность gemini-synthesis (CAR-92 r2 M2) и путь body-score сабпроцесса (M3): взяты из файла находок, исправлены красными тестами.
  • Гейт дрейфа cold-load бандла обновлён под vi-словарь (90 script-тегов / 87 bundlable), неиспользуемая тестовая привязка удалена (CodeQL 572).

Примечания

  • Формулировка счётчика адаптеров в баннере «Latest» README.vi, на которую указало ревью, живёт в баннере Предыдущий релиз и уже побайтово зеркалит EN README — оставлена без изменений для паритета EN↔vi (задокументировано в теле коммита).
  • Тестовый пол: 5642 → 5686 (+26: eval-validate-opus-r2, scanner-carryover-r2, vi-gate-проверки). Реестр без изменений: 121 источник (116 EN + 5 RU) · 116 адаптеров.

[1.255.0] — 2026-10-24

Вьетнамский (Tiếng Việt) становится 19-м языком интерфейса — словарь на 1525 ключей с полной паритетностью к EN, распознавание vi/vi-VN, полный комплект справки, зеркала README и CHANGELOG, интеграция в лендинг cvstart.org и 🇻🇳 в переключателе языка.

Добавлено

  • Вьетнамский (vi) — 1525/1525 ключей с полным паритетом к en; detect() принимает vi и vi-VN; вьетнамский — LTR (в RTL_LANGS не добавляется). ‘Pipeline’ остаётся ‘Pipeline’ — стандартное техническое заимствование, в белом списке.
  • Полный docs/help/vi.md (33 H2 / 125 H3), плюс зеркала README.vi.md и CHANGELOG.vi.md и вьетнамские строки-скаффолды в prompts.mjs (LOCALE_NAMES + SCAFFOLD_STRINGS) — вывод LLM на вьетнамском работает из коробки.
  • Лендинг cvstart.org на вьетнамском — site/src/i18n/vi.json, запись локали в sitemap и скриншот dashboard-vi.png; переключатель языка получает 🇻🇳 (19 позиций).
  • Перечисляющие гейты 18 → 19 — LANGS, пробы detect, i18n-аудит, паритет changelog, блок переводов README, locale-sweep — плюс red-first tests/i18n-vi-locale.test.mjs (139 строк).

Примечания

  • Лендинг cvstart.org получает vi — намеренное отступление от прецедента ta (тот релиз держал лендинг на 17 специально); задокументировано в спеке релиза.
  • vi — латиница, поэтому гейт no-latin-leaks не применяется (он сторожит не-латинские локали); латинская техническая лексика здесь норма.
  • Тестовый пол: 5634 → 5642 (1 пропуск = опциональный capture-тест раскладки в браузере, без изменений).

[1.254.0] — 2026-10-24

Релиз укрепления из раунда ревью максимальной моделью: снапшот сканера больше не теряет строки, одиннадцать инвариантных нарушений LLM-слоя исправлены, двадцать три SPA-must-fix въехали, и Paylocity ATS присоединяется к сканеру — реестр теперь поставляет 121 источник (116 EN + 5 RU) со 116 адаптерами.

Добавлено

  • Paylocity ATS (родитель #5687): тенантные борды recruiting.paylocity.com, мёртвый борд 302 → JobNotFound отображается в 404, чтобы карантин убирал запись, кап max_jobs с флагом усечения, безопасное формирование строк. Реестр: 121 источник = 116 EN + 5 RU · 116 адаптеров; гейты счётчиков перегенерированы.

Исправлено

  • Безопасность снапшота сканера (R-12): скан, который не пытается ничего, теперь СОХРАНЯЕТ снапшот (nothing scanned) вместо затирания; full-replace переносит старые строки отказавших источников — со штампом по энтри, чтобы отказавший «Lever» больше не держал чужие lever-строки — с повторным прогоном через ТЕКУЩИЕ фильтры и попаданием только в filtered (уже-applied не всплывает как fresh).
  • Одиннадцать инвариантов LLM-слоя: языковая директива открывает И закрывает промпты deep/mode; Gemini-subprocess пересылает --lang, не-нулевой выход → 502, не льёт stderr, держит 300 с и пишет usage; manual-fallback auto-pipeline несёт URL вакансии; truncation и warnings бандла всплывают в /api/mode; spend tier больше не обходится клиентом; дисконнект клиента абортит идущий вызов провайдера (сигнал проведён через всех); резак повторных заголовков больше не уничтожает валидные отчёты; экстракторы score/legitimacy согласованы с валидатором; украинская последовательность и арабская ва-лям tightened.
  • Раунд ревью Opus 5-5 НА самих фиксированиях (8 must-fix) затем въехал: мультибуквенная регистрация резака заголовков (один заголовок мог зарегистрировать букву из своего окна — ## G) Interview Q&A резал по G), расширенные терминаторы заголовков (**Block A** —, [A], полноширинные/、/CRLF), abort-проводка Gemini-subprocess, штампованная атрибуция перенос-строк, повторный прогон фильтров, уровень ссылочных заголовков по числу различных букв.
  • Двадцать три SPA-must-fix по sourcing-вьюхам (append однопейджного пейджера, клавиатура строк pipeline, восстановление фильтров saved-search, сбросы select’а source, откат дефолта в config, течи листенеров, close-on-terminal SSE, teardown scan-раннера, сырой fetch, «провал выглядит как empty», коллизия в window, сохранение фокуса) и assist-вьюхам (мёртвый help deep-link, течи help, повторное включение blank-overwrite в two-pager, single-flight платных прогонов batch/deep/mode, тихие концы стримов, вечные спиннеры на ошибках, призрачные строки проваленных фильтров, дубли docs-fab + язык).

Примечания

  • Весь раунд прошёл ревью Opus 5-5 на максимальной строгости — порты до мержа (21 находка, 13 исправлено) и сами фиксы постфактум (8 must-fix, все въехали здесь с красно-зелёными тестами).
  • Тестовый пол: 5262 → 5634 (372 новых спецификации по источникам, сканерам, LLM-слою и SPA).

[1.253.1] — 2026-10-24

Две HIGH-находки безопасности из ревью максимальной моделью исправлены: IPv6-литералы больше не обходят SSRF-гард сканера, а escape-regex CV-маркдауна больше не бэктрекает квадратично.

Исправлено

  • IPv6-литералы обходили SSRF-гард (http-json.mjs guardResolvedHost): скобочный IPv6-литерал ([::1], [::ffff:169.254.169.254], [fd00:ec2::254] — AWS IPv6 metadata) валил dns.lookup исключением, и гард fail-open пропускал fetch до cloud-metadata. Скобки снимаются, литерал классифицируется напрямую через isIP + классификатор private/loopback — без DNS и без fail-open. Найдено ревью Opus 5-5 (CAR-90).
  • ReDoS в escape-regex CV-маркдауна (security.mjs stripDangerousMarkdown): паттерн-пояс с \s*/?\s* бэктрекал квадратично — < + ~100k пробелов стоили ~16 с. Заменён на один плоский ограниченный класс (линейно; тот же вход теперь ~1 мс); всё, что старый паттерн экранировал, экранируется и новый.
  • tests/wiki-banner-check: протухшая версия JA-фикстуры (хвост #461) выровнена.

Примечания

  • Оба фикса несут красно-зелёные регрессионные тесты (3 новых IPv6-кейса + ReDoS с временной границей; всего 5511 тестов, +7).
  • Файлы источников сканера не затронуты — гард живёт в общем транспорте, через который ходят оба сканера.

[1.253.0] — 2026-10-24

Десять новых источников присоединяются к сканеру — Atlassian, BWI, Computrabajo, Dover, Google, Kalibrr, Magneto365, Platsbanken, Tech in Asia и Zoho Recruit — реестр теперь поставляет 120 источников (115 EN + 5 RU) со 115 адаптерами, а роль-матчер разводит заголовки, различающиеся только коротким капсом-кодом.

Добавлено

  • 10 источников портированы из родителя 1.38.0, каждый со своим fetch-адаптером и CI-изолированным набором тестов (fake fetchImpl, без сети): Atlassian (единый auth-free JSON-фид, iCIMS-ссылки, allowlist https+icims.com на URL строки), BWI (JSON-LD ItemList, JSON.parse-first с escape-толерантным фолбэком), Computrabajo (LATAM, декод энтити через общую таблицу, испанское «remoto» читается как remote), Dover (потенантная DRF-пагинация, рендер Remote (International), UUID-валидация бордов), Google Careers (первые страницы по фразам, работодатель карточки выигрывает, дедуп между запросами), Kalibrr (SEA-агрегатор, фид-ид как id строки — шлёп слага больше не плодит дубли), Magneto365 (LATAM, фиксированный межстраничный темп), Platsbanken (шведский JobTech API, ключевое слово = своё окно мимо потолка offset-2000), Tech in Asia (Algolia-ключи обнаруживаются заново из билда сайта, ссылка работодателя выигрывает при https и не-шаред), Zoho Recruit (скрытый-input борд, опциональный ограниченный detail-enrichment, парс валюты только ISO-4217).
  • Счётчики реестра: 120 источников = 115 EN + 5 RU · 115 адаптеров; пять гейтов счётчиков перегенерированы из живого реестра; фолбэк дропдауна #/scan, ссылки источников cvstart.org и help §17 ×18 следуют за ними.

Исправлено

  • Роль-матчер (#5688 порт): заголовки, различающиеся только коротким капсом-кодом («HR BP» vs «HR GP», «AWS» vs «GCP», даже в смешанных скриптах), больше не склеиваются фузз-матчем в одну вакансию; коды читаются из сырого заголовка с исключением noise-слов, гард стоит прямо перед коэффициентом Жаккара.
  • Порты пережили ревью максимальной строгости Opus 5-5 (21 находка): throw на молчаливо-пустых бордах при дрейфе разметки (google/bwi), преабортированный скан кидает, а не возвращает [], порядок decode-then-strip на LATAM-скреперах (XSS-форма strip-then-decode), https+allowlist на URL строк atlassian, host-ассерты с отказом портов, парс поздних страниц внутри try (партиалы выживают), честность тестов (бюджет запрошен точно, контракты коэрции законтрактованы правдиво). 13 находок исправлено, [spec]-хвост отслежен отдельно.

Примечания

  • Пять RU-источников остаются web-ui-only (апстрим-адаптеров нет — так задумано); у каждого EN-источника есть адаптер.
  • Coverage-рейтча: шесть самых больших портов подняты до ≥95 % branch (27 новых кейсов); защитный мёртвый код задокументирован пофайлово.
  • Новые источники выбираются через provider: <slug> в portals.yml — существующие конфигурации не затронуты, каноническое поведение не меняется (кроме счётчиков дропдауна/списков).

[1.252.1] — 2026-10-17

Баннер релиза в wiki теперь проверяется скриптом, а не ручным свипом — регресс v1.252.0 поймал протухший баннер только руками — и локальная директива явно переопределяет language.output профиля и язык вакансии.

Добавлено

  • scripts/wiki-banner-check.mjs — гейт, которого требовал регресс: читает каждый Home*.md в клоне wiki и сверяет баннер с package.json и с ЖИВЫМИ счётчиками реестра (scanner adapters из портального реестра — 105, а не протухшие 109 из баннера; route modules из server/lib/routes/; UI locales из набора CHANGELOG). Regex баннера языко-независим по ФОРМЕ, а не по словам: локальные Home-страницы переводят и метку, и счётчики («Текущий релиз», «Aktuel udgivelse», «адаптеров сканера», «enhedstests»), и их числа уехали дальше (5177), чем EN-страница (5245). --tests N принимает число, которое только что напечатал свежий test:ci; --check-only запрещает неявный клон для офлайна. Офлайн-фикстурный тест — tests/wiki-banner-check.test.mjs.

Исправлено

  • Баннер wiki Home ×18 возвращён в синхрон (уходит вместе с этим релизом): свип v1.252.0 менял бейджи и ссылки, но не строку баннера, и ничто механически на это не указало. Гейт выше делает это релизоблокирующей проверкой.
  • Локальная директива задаёт приоритет языка (buildLocaleDirective): «This UI locale overrides the profile’s language.output setting and the job posting’s own language». Собственное правило 6 родителя велит писать в language.output — без фразы о приоритете модель разрешала два противоречащих указания непоследовательно по локалям, что и был разброс в языковой колонке v1.252.0. Закреплено tests/locale-directive-language-output.test.mjs на тексте директивы (17 не-EN локалей + пустой EN).

Примечания

  • Наблюдение за ФОРМОЙ ta evaluate (§3, смотреть): первый живой прогон v1.252.0 дал evaluate на 48 336 символов без Block G и SCORE_SUMMARY — похоже на зацикливание, а не на язык (CAR-70 — про язык). Повторится на следующем релизе — отдельная Linear-задача, не CAR-70.
  • По FIX-промпту: родитель только для чтения; переопределение целиком живёт в директиве UI.

[1.252.0] — 2026-10-17

Родительский форк синхронизирован с career-ops-hq/career-ops — 264 апстрим-коммита, родитель 1.36.0 → 1.37.0 — с защитой обоих форк-дивергенций, а деплой переустанавливает работающего родителя на эту базу.

Добавлено

  • Вьетнамский (vi) рыночный режим из апстрима (modes/vi/, tuyen-dung / ung-tuyen): BHXH/BHYT/BHTN, thử việc, lương tháng 13, trợ cấp thôi việc — 21-й рынок в таблице режимов родителя.
  • Провайдер борда Tech in Asia (апстрим): читает публичный Algolia-индекс job_postings, который сам запрашивает UI jobs.techinasia.com — SEA-борд стартапов, ~300 живых вакансий на замере, 273 из них индонезийские. Ключи обнаруживаются заново на каждом прогоне из билда сайта, собственная ссылка работодателя выигрывает у страницы борда, зарплата сознательно не attachится (поля зарплаты в индексе — флаги, а не деньги).
  • Восемь новых документов сигналов легитимности Block G (modes/oferta-signals/08–17): несовпадение страны в терминологии льгот, несовпадение тега локации сторонней платформы с постингом работодателя, лицензирование агентств, иммиграционные требования с перехлёстом, запрещённый контент юрисдикции, ширина вилки pay-transparency, вопрос юристу о минимальной зарплате, раскрытие AI-скрининга, раскрытие срочного контракта, покупательная способность при релокации.

Изменено

  • Укрепления на стороне родителя, которые едет база: проверка story-provenance работает по единицам и предложениям и не пускает цифры user-cannot-confirm в переговоры, ATS и interview-вывод; fact-gate CV запускается на каждом рендерере и читает переформулированные и структурные заявления; LaTeX отвергает протухшие извлечённые слоты до записи; launchd-обход follow-ups пропускается, когда follow-ups выключены; update-system удерживает скан локальных правок в пределах argv.
  • Дивергенции форка пережили мерж: providers/telegram.mjs (форк-единственный парсер, ADR-0001) и Unicode-lookup LOCATIONISH_RE в telegram-channel.mjs (ADR-0002). Документ бордов теперь перечисляет улучшенную строку telegram-channel апстрима, форк-единственную строку telegram, Tech in Asia и дефолт max_pages у Tencent, поднятый 20 → 100.

Примечания

  • SPA не меняет поведение в этом релизе: это док-поезд плюс переустановка родителя. Деплой теперь работает на родителе 1.37.0; дельта родителя не трогает таблицу канонических статусов — статусы трекера рендерятся как раньше.
  • В мерже апстрима разрешены два конфликта: docs/SUPPORTED_JOB_BOARDS.md (обе телеграм-строки сохранены) и test-all.mjs (walkTree-версия copyDirSync апстрима субсумирует форковский ручной guard вложенных чекаутов по .git; список импорта — объединение).

[1.251.1] — 2026-10-12

Фильтр специализаций матчится только по отличительным словам роли — старшинство и общие должностные существительные больше не матчат вакансию сами по себе — а remote-qa разделяет форму и язык, чтобы дрейф не краснил A–G.

Исправлено

  • Фильтр специализаций выбрасывает старшинство и общие должностные существительные из матчера: «Senior PHP Developer» матчил «Senior Java Developer» через общий Senior/Developer. Матчер оставляет только отличительные токены (PHP, Go, Platform, SRE, Backend…); роль без отличительных токенов («Software Engineer») падает на матч всей фразой. Границы слов на месте: «Go» никогда не матчит «Golang» и «Google».
  • remote-qa разделяет форму и язык: флаг A–G считается только по кодам блоков/SCORE_SUMMARY/looping; дрейф report not in <locale> переезжает в языковой флаг и свою колонку warnings (в заголовке таблицы появилась недостающая 4-я колонка; коды сохраняются построчно).
  • Тулбар карты — честные 50/50 на 390 px (флекс 58/42 оставлял поиску 124 px и обрезал плейсхолдер), мёртвое CSS-правило .job-map-job-select удалено.
  • CodeQL 552 — неиспользуемая переменная OVERLAYS в design-qa тесте удалена.

Примечания

  • Мили по-прежнему отложены владельцем на следующий релиз.
  • Языковой дрейф ta-оценки остаётся ЗАДОКУМЕНТИРОВАННЫМ ограничением бэкенда (hermes, доля прозы 2–26 % между прогонами; A–G держится, зацикливания нет): remote-qa теперь сообщает его как ожидаемую находку для ta, а не регресс. Двухшаговый вариант (оценка на английском + перевод) остаётся опцией, если понадобится.
  • Список общих токенов намеренно минимален (старшинство + должностные существительные, en/ru): расширение — строка на язык, и каждое добавление рисует переефильтровать легитимную вакансию.

[1.251.0] — 2026-10-12

Селект на карте теперь фильтрует по СПЕЦИАЛИЗАЦИИ из настроек (config/profile.yml → target_roles.primary): выбор «Product Manager» или «Backend Engineer (Go / PHP)» оставляет на карте только вакансии, чей тайтл несёт слова этой роли. Локатор списком вакансий из v1.250.0 убран — это был неверный инструмент (правка владельца).

Добавлено

  • Фильтр по специализации: селект на карте перечисляет специализации из настроек (target_roles.primary); выбор оставляет только вакансии, чей тайтл несёт одно из отличительных слов роли (по границам слов, без учёта регистра — «Go» не матчит «Google», кириллица работает через те же Unicode-lookaround’ы). Изменение фильтра сразу перерисовывает карту и чип статуса; Reset возвращает все специализации.

Изменено

  • Локатор списком вакансий из v1.250.0 удалён: правка владельца — селект, перечисляющий отдельные вакансии, был неверным инструментом для карты; его заменяет фильтр по специализации. Пик-карточки, клики по маркерам и текстовый поиск остаются как были.

Примечания

  • Мили все ещё откладывает владелец. Матчинг сознательно ТОЛЬКО по тайтлу словами из настроек: компании или описания затянули бы в специализацию нерелевантные вакансии. Вакансия вне всех настроенных ролей видима при «All».

[1.250.0] — 2026-10-12

Страница карты становится рабочим инструментом: локатор вакансий над картой наводит на любую видимую вакансию и открывает её пик-карточку (в том числе на десктопе), а текстовый поиск сужает карту по компании или должности — и то и другое переживает перезагрузку.

Добавлено

  • Локатор вакансий: селект над картой перечисляет видимые (прошедшие фильтры) вакансии, отсортированные по компании+должности; выбор улетает к кружку — с зумом до разведения кластеров — и открывает пик-карточку. Пик открывается и на десктопе: смысл локатора — «покажи мне эту вакансию», навигация прочь его обесценила бы. Опции зеркалируют активные фильтры при каждой перерисовке — селект никогда не предлагает вакансию, которой нет на карте.
  • Текстовый поиск: поле поиска сужает карту по подстроке компании+должности (сравнение без учёта регистра; в поле сохраняется введённый регистр). Запрос живёт в map-prefs между перезагрузками, Reset его очищает, опции локатора сужаются вместе с видимым набором.

Исправлено

  • Docs-fab больше не перекрывает атрибуцию карты: с новым тулбаром карта уехала вниз, и нижний правый угол атрибуции Leaflet попал под плавающую кнопку. FAB теперь скрыт на #/map на всех ширинах (на мобильных так уже было); ассистент документации остаётся доступным со всех остальных страниц.
  • Два spec’а высоты мобильной карты сдвигают порог 600 → 540 px: тулбар законно занимает ~90 px вьюпорта, а ассерт проверяет «карта занимает большую часть вьюпорта», а не точное значение пикселей.

Примечания

  • Мили (дорога в милях вместо минут) владелец отложил на следующий релиз.
  • Локатор находит кружок через обратную ссылку __job: первая попытка использовала _point, который L.Circle уже занимает — поиск молча проваливался и пик не открывался. Коллизия имени свойства с библиотекой невидима, пока фичу не пощупаешь.
  • 4 новых i18n-ключа входят во все 18 словарей, собранный снапшот перегенерирован.

[1.249.0] — 2026-10-11

Карта вакансий теперь ставит релевантность на первое место: спокойная шкала от бирюзового к серому выделяет лучшие вакансии, фильтры оценки и времени в пути сохраняются, на телефоне карта занимает доступную высоту и показывает карточку по первому касанию, а трекер использует те же фильтр и сортировку поездки.

[1.248.8] — 2026-10-11

Паритет с родителем: форк и web-ui синхронизированы со 115-коммитной дельтой апстрима — новый источник PageUp, URL-ячейки трекера парсятся как markdown-ссылки, стейт Assessment — плюс ta/ja языковая работа: зацикленные отчёты срезаются и называются, скелет заголовков держит A–G, а доля письма считается по прозе.

Добавлено

  • Источник PageUp (provider: pageup): скрапит vendor-hosted таблицы листингов careers.pageuppeople.com — один тенант, одна запись, пагинация с правилами останова (нет свежих / короткая страница) и ограничением max_pages. В реестре 105 EN-адаптеров / 110 источников; PageUp появляется в фильтре источников #/scan (FALLBACK + живой реестр). Брендированный домен намеренно не клеймится — запись должна указывать на vendor host.
  • URL-ячейки трекера парсятся как markdown-ссылки (родитель #3516): родитель пишет ячейку URL как [ashby](https://…); parseApplications теперь извлекает href (ключ дедупа) при разборе. Голые URL и заглушки проходят дословно.
  • Стейт Assessment (между Responded и Interview) приходит через живое чтение states.yml; встроенный fallback-список обновлён для паритета.

Исправлено

  • Зацикленный отчёт срезается и называется, никогда не сохраняется молча (CAR-74): ta-отчёт в 46 587 символов повторял блок B и не доходил до D–G и оценки. Повторный заголовок блока теперь срезает отчёт на первом повторе, причина отказа называет луп (report looping (block B repeated), …), а не-EN промпты несут готовый скелет ## A) … ## G), чтобы машинный формат и языковой мандат перестали конкурировать (жёсткий мандат ронял D–G целиком, A–G ✗ 2/3 на проде).
  • Доля письма считается по прозе (CAR-75): таблицы, блок SCORE_SUMMARY, код и URL ничего не говорят о языке отчёта. Японский отчёт с латинскими таблицами давал 26–27 % родного письма при полном тексте и флаковал на гейте 30 %; по прозе держится детерминированно.
  • /api/estimate показывает языковое предупреждение (CAR-76): оба провайдерских хвоста добавляют report not in <locale>, когда сохранённый отчёт не на языке, а remote-qa печатает коды предупреждений колонкой таблицы (только коды — лог публичный).

Примечания

  • Не портовано (задокументировано): инфраструктура scan-ats-full, регистратор истёкших в liveness (чистый планер — когда появится писатель scan-history), coverage-check/discover/story-bank/pdf –ats/text mode/domain_filter (только CLI), фикс req-id в dedup-tracker (у web-ui нет dedup-tracker), README.vi.md. Форк родителя защищает ADR-0001/0002: telegram.mjs выжил, Unicode LOCATIONISH_RE сохранён (апстрім откатил к ASCII \b; откат смёржился без конфликтов).

[1.248.7] — 2026-10-11

Роль из шапки отчёта теперь читает реальную конвенцию родителя — # Evaluation: {Company} — {Role} — вместо предположения # <Роль> — <Компания>, так что в трекер больше не попадает «Evaluation: <Компания>» как роль; отчёт не на том языке предупреждает вместо молчаливого сохранения; а болтовня модели до H1 срезана из сохранённых отчётов.

Исправлено

  • Роль читается из шапки в формате родителя: сначала снимается локализованная текстовая метка (Evaluation:, Оценка вакансии:, Оцінка: и ещё 16 вариантов локалей — от длинных к коротким, форма регэкспа та же, что у text-label регэкспа в parsers.mjs). После метки формат Company — Role → роль справа; без метки Role — Company → слева; часть, называющая компанию, решает независимо. Роль никогда не равна компании и не бывает голым словом метки — запасной вариант это чищеная строка поста. (v1.248.6 читала левую часть и выдавала Evaluation: Sveak как роль в каждой строке трекера от auto-pipeline.)
  • cleanup-plan считает строки, а не файлы: строки — N dirty-URL line(s) суммируются (берётся N), а не считаются по одной — файл с 5 мусорными URL давал 1, а не 5.
  • Отчёт evaluate не на том языке предупреждает вместо молчаливого сохранения: docs-ассистент отвечает на языке, а evaluate дрейфовал (доля тамильского 22 % и 2 %, повторено 2/2 на проде; ja флакнул раз и прошёл на повторе). Промпт оценки теперь открывается И закрывается обязательной языковой инструкцией — весь отчёт, включая таблицы, — а конвейер проверяет язык сохранённого отчёта: дрейф всплывает предупреждением report not in <locale> в событии done и в детале шага, при этом (иначе валидный) отчёт сохраняется.
  • Без болтовни модели до H1: преамбула «I’ll evaluate this job description… Reading files…» срезается при сохранении — отчёт начинается со своей заголовочной строки.

Примечания

  • Не портировано и не тронуто (проверено в регрессе v1.248.6): embed-фетч, строчная структура поста («Компания : X»), дедуп «## Rejected», отказ корня домена до fetch, честность recon/link-check и cleanup-режимы. Языковая проверка — предупреждение, не отказ: отчёт (контент уже провалидирован A–G + оценка) сохраняется; оператор видит дрейф (report not in <locale> в событии done и в детале шага) и может повторить оценку.

[1.248.6] — 2026-10-10

Пострегрессионная шлифовка телеграм-цепочки: реальный пост сохраняет строчную структуру (метка «Компания : X» и строка роли находятся там, где они реально есть), роль в трекер приходит из шапки отчёта, а не сырой строкой поста, и операционные поверхности (recon, cleanup-plan, link-check) сообщают то, что заявляют.

Исправлено

  • Пост t.me больше не склеивается в одну строку: extractTelegramPostText превращал каждый тег в пробел — включая <br> — поэтому строчно-якорные шаблоны (^Компания:) не срабатывали, а роль проваливалась до первых 100 символов всего поста. <br> (и закрывающий </blockquote>) теперь дают перевод строки до снятия тегов, числовые HTML-сущности (&#NN;, &#xNN;) декодируются. Шаблон метки компании расширен до ^Компания\s*[:—–-]\s*(.+) — живой пост говорил «Компания : Sveak» с пробелом перед двоеточием, и в трекер попадало имя канала.
  • Роль в трекере берётся из шапки отчёта: конвейер вычислял объединённую роль (H1 шапки → строка поста как запасной вариант) для гейта, но в slug, строку трекера и событие done писал сырой guess.role. H1 шапки следует конвенции # <Роль> — <Компания> — берётся сторона роли (компания не приезжает следом); строка-запасной вариант чистится — без хэштегов (#удаленка), без эмодзи, ≤80 символов.
  • ## Rejected больше не дублируется: каждый отказ дописывал свежий заголовок ## Rejected в data/pipeline.md. Раздел создаётся один раз; каждый отказ дописывает свою строку в существующий раздел (два отказа подряд → один заголовок, две строки).
  • Корень домена отклоняется до запроса: проверка pathname шла после fetchJobDescription — мусорный linkedin.com/ сначала скачивался целиком (fetch: done) и только потом отклонялся. Теперь проверка срабатывает до fetch — мусорный URL не стоит ничего.
  • Recon вычисляет сигнал skip-контракта, а не утверждает его: honours rejected: теперь yes, только если в задеплоенном auto-pipeline.mjs реально есть markPipelineRejected (греп по выкачанному исходнику), а счётчик меток больше не печатает «0» дважды (grep -c сам печатает 0 при отсутствии совпадений; фолбэк || echo 0 дублировал его).
  • cleanup-plan сообщает числа по видам: отчёты t-role к перемещению, строки dirty-URL в pipeline, строки test-row в трекере — вместо одного grep -c, который считал собственную строку успеха «no unscored -t-role-.md reports» (чистые данные печатали фантомную «1»).
  • link-check считает 5xx от github.com непроверенными: три прогона подряд отдавали 503 на github.com/…/blob/main/… с раннеров Actions, тогда как те же URL отвечали 200 из других сетей — считается за бот-стеной (как 401/403/429), а не битой ссылкой.

Примечания

  • Не портировано и не тронуто (проверено в регрессе v1.248.5): сам embed-фетч, серверный skip-контракт, офлайн-тесты _setLookup, приём uk-порядковых заголовков, границы слов в гейте роли и режим cleanup-apply. Подсчёт по видам в cleanup-plan читает собственные метки вывода скрипта очистки (no valid SCORE / dirty-URL line / test-row line) — их переименование молча сломало бы счётчики.

[1.248.5] — 2026-10-10

Пострегрессионное укрепление: записи-одиночки t.me теперь фетчат сам текст поста (embed-форма), отклонённая запись покидает пул eval-таймера на стороне сервера, родная нумерация блоков в украинском отчёте принимается валидатором — а один тест, тайком ходивший в живую сеть, навсегда офлайн.

Исправлено

  • Записи t.me наконец оцениваются: обычная страница одного поста отдаёт в HTML только шапку канала (~0,2 КБ — «View in Telegram» и ничего больше), модель получала пустое описание, и каждая телеграм-запись умирала на evaluate. Фетч теперь переписывает t.me/<канал>/<id> (и форму ленты t.me/s/…) в embed-форму (?embed=1&mode=tme) — тот же хост, SSRF-проверки без изменений — и берёт текст поста из .tgme_widget_message_text (запасной вариант: og:description). Нет текста виджета → отказ на шаге fetch (telegram post has no text, rejected: true) до траты токенов LLM.
  • Eval-таймер перестаёт повторно платить за отклонённые записи (серверный контракт): отклонённая запись помечается в data/pipeline.md — её строка покидает URL-фенс и попадает в раздел ## Rejected с короткой причиной (идемпотентно, без дублей). Таймер берёт голые URL из фенса, поэтому помеченная запись пропускается естественно — без зависимости от внутренностей скрипта таймера. Recon печатает pipeline rejected marks: N + honours rejected: yes (server-side) вместо грепа по скрипту.
  • Украинские отчёты снова проходят валидацию A–G: uk-модель нумерует блоки по порядку собственного алфавита (А Б В Г Д Є Ж), а проход двойников засчитывал только А/В/Е — на проде отсутствовали ровно блоки C, D, F, G (2/2 воспроизведения). Валидатор принимает украинскую порядковую последовательность, когда заголовки идут по порядку (та же дисциплина, что абджад для ar и деванагари для hi), а промпт оценки для всех локалей теперь явно требует латинские A–G.
  • Гейт ролевых ключевых слов срабатывает на слова, а не подстроки: корни linkedin.com/ и x.com/ проходили гейт, потому что lead/it жили в служебном тексте страницы. Ключевые слова сопоставляются по границам слов Unicode, а корень домена (URL без пути) отклоняется до вызова LLM.
  • Юнит-тесту больше не нужна живая сеть: тест no-hints (tests/auto-pipeline-jd-gate.test.mjs) подменял HTTP-транспорт, но делал настоящий DNS-запрос — в CI был зелёным только потому, что у раннера была сеть. В safe-fetch появился хук _setLookup() (сам резолвер заменяем в тестах; продовый код его не трогает), тест резолвит офлайн.

Добавлено

  • Режимы cleanup-plan / cleanup-apply в deploy.yml: продовая очистка QA-мусора (post-qa-cleanup) запускается на сервере без SSH с ноутбука. В логе только числа — ни имён файлов, ни превью строк: лог публичный, а в именах названия компаний. Шаг Verify для cleanup-режимов пропускается.

Примечания

  • Не портировано: компилятор локаций по границам слов и таблица USPS из родителя (задокументировано с v1.248.1). Контракт пропуска отклонённых записей держится на сервере (разметка в data/pipeline.md), а не на скрипте таймера — тот вне репозитория и намеренно не правится.

[1.248.4] — 2026-10-10

Цепочка роли для Telegram-записей: кириллический пост с настоящей вакансией теперь даёт именованный отчёт вместо отклонённого unknown-role файла — а вызов LLM происходит только для записей, которые стоит оценивать.

Исправлено

  • Цепочка роли работает для кириллических постов: список ключевых слов получил разработчик/программист/инженер/аналитик/менеджер/руководитель/тимлид/лид/дизайнер/тестировщик/QA/архитектор/специалист/директор/DevOps/SRE (старый список был EN-only — 0 из 40 реальных постов получили роль), консультируется шапка отчёта (parseReportHeader — модель пишет роль в H1), последняя инстанция — первая осмысленная строка поста.
  • Гейт идентичности переехал ДО вызова LLM: запись без компании и без зацепки роли падает на шаге fetch (rejected: true) — токены не тратятся на оценку, которая всё равно была бы отклонена.
  • t.me/s/<канал>/<id> больше не называет компанию «S» — сегмент веб-превью пропускается; обе формы ссылок резолвятся в канал.
  • Самоябели «Компания: X» на Telegram-постах учитываются (и «…в компании X» внутри предложения для мессенджер-хостов).

Добавлено

  • Фейлы auto-pipeline несут rejected: true в SSE-ошибке — серверный eval-таймер использует флаг, чтобы пропустить запись pipeline вместо повторов каждые 2 часа.

Примечания

  • Не портировано: родительский компилятор локаций с word-boundary и USPS-таблица (документировано с v1.248.1).
  • Блок «Translations» в README генерируется из одного канонического списка локалей, в test:ci добавлен паритет-тест (17 ссылок на файл, своя не линкуется) — ar/pl/uk получили блок; дрейф разделителей |/· устранён.

[1.248.3] — 2026-10-10

Продолжение регресс-раунда: eval-таймер больше ни при каких условиях не оформит безымянные Telegram-заглушки в отчёты, QA-очистка больше не удалит настоящие Telegram-вакансии, атрибуция карты снова видна, дашборд-чипы читаются с ≥ 4.5:1.

Исправлено

  • Auto-pipeline валидирует ДО сохранения: отчёт без оценки 0–5 или без блоков A–G завершает ран на шаге evaluate (rejected: true в SSE-нагрузке) — ни файла отчёта, ни строки трекера. t.me-записи, прошедшие длину-гейт v1.248.2, после того релиза дали ещё два мусорных отчёта; теперь безымянные записи отклоняются с company/role not identifiable.
  • guessCompanyRole больше не выдумывает компанию из мессенджер-хостов — t.me, telegram.me, vk.com, linkedin.com и другие в EMPTY_DOMAINS: у Telegram-поста fallback на имя канала, а безымянная запись отклоняется вместо unknown-role.
  • post-qa-cleanup сначала читает оценку из шапки: auto-pipeline вырезает блок SCORE_SUMMARY перед сохранением, поэтому старая проверка только по блоку считала каждый сохранённый Telegram-пост мусором — на проде 8 из 41 несут оценку в шапке, и --apply удалил бы их. Оценка из шапки (через parseReportHeader) теперь первичный сигнал, блок — запасной. Dry-run печатает причину по каждому файлу.
  • Атрибуция Leaflet видна на #/map — docs-FAB её перекрывал (нарушение лицензии OSM); угол поднят на 96px.
  • Дашборд-чипы ≥ 4.5:1 — подписи dash-chip и вариант --manual переведены на --foggy-strong (было 4.31:1).

Добавлено

  • auto-pipeline-фейлы несут rejected: true в SSE-ошибке — серверный eval-таймер использует флаг, чтобы пропустить запись pipeline вместо повторных попыток каждые 2 часа.

Примечания

  • Дрейф ar/ja не повторился на перезапуске (см. v1.248.2).
  • Сама очистка прода по-прежнему ждёт явного «да» пользователя (CAR-61c); с этим фиксом очистка больше не может удалить настоящие Telegram-вакансии.

[1.248.2] — 2026-10-10

Регресс-раунд: eval-таймер больше не превращает мусор pipeline в «Last evaluation», разбор SCORE выживает в реальных форматах, а трекер наконец говорит на вашем языке.

Исправлено

  • Пустые JD-отчёты не создаются. Гейт JD в auto-pipeline поднят с 50 до 200 символов до вызова LLM — страницы-заглушки (example.com/qa-v167-…) заканчиваются SSE-ошибкой + предупреждением в консоль вместо отчёта …-t-role-<ts>.md, который дашборд показывал как «Last evaluation» (на проде 42 таких файла).
  • Разбор SCORE_SUMMARY выживает в реальных форматах: «4,2/5», «4.2 / 5», «4.2», «SCORE :» — всё валидно; запятая с 1–2 цифрами — десятичная, с 3 — тысячи (старый код читал «4,200» как 4.2 и пропускал гейт 0–5). Провал проверки пишет в лог только строку SCORE: — никогда содержимое отчёта (логи CI публичные).
  • Статусы трекера переведены: табы стадий и бейджи рендерят локализованные подписи во всех 18 локалях; каноническое значение в данных, фильтрах и URL не тронуто.
  • Честный ETA: «⏱ ~30s» стал «~2–4 мин» в evaluate/advisor/market-report (реальный диапазон 86–285 с), а подзаголовок evaluate читается A–G.
  • Доступность: счётчик API-ключей в config и счётчик pipeline теперь ≥ 4.5:1 (было 4.31:1); чип фасета с пустым значением больше не рендерится без доступного имени.
  • UI-полировка: кнопка Delete у сохранённых поисков выглядит деструктивной только при выбранном поиске (иначе disabled); карточка EMAIL профиля не обрезается на 1440 px; у пилюли live-evals появился отступ иконка/текст включая RTL; docs-FAB не перекрывает атрибуцию Leaflet; «?» помощи не переносится под табами stats; у usage появился воздух между подзаголовком и табами; health теперь говорит career-ops-ui.

Добавлено

  • scripts/post-qa-cleanup.mjs: dry-run по умолчанию, --apply с автоматическими бэкапами (*.bak-<ts>, отчёты переносятся в qa/cleanup-backup-<ts>/) — строки ZZ-QA-TEST в трекере, отчёты *-t-role-* без валидного SCORE, записи example.com в pipeline.

Примечания

  • Дрейф ar/ja из регресс-раунда не повторился на перезапуске (оба: lang ✓ + A–G ✓) — запись ta в language-check и усиленный парсер SCORE идут как defense-in-depth.
  • Сама очистка прода ждёт явного «да» пользователя: сначала dry-run-план, потом --apply.

[1.248.1] — 2026-10-09

location_filter получает родительские тиры always_allow и block_hard (CAR-60) — мультистрановая строка локации выживает при блок-листе соседей, а явный жёсткий блок больше нельзя спасти.

Исправлено

  • always_allow сильнее block: при block: [romania, poland, croatia] и always_allow: [united kingdom] workable-merged ячейка «London, United Kingdom · Romania · Poland · Croatia» проходит — домашний регион есть в опциях (потребительский кейс workable #4806 теперь в родительской форме).
  • block_hard сильнее always_allow: страновые термины, которые нельзя спасти совпадением always_allow где-то в строке.
  • strict: true учитывает block_hard как ограничивающий тир; секция location_filter в docs/help документирует оба тира ×18.

Примечания

  • Осознанно не перенесено сейчас: родительский компилятор локационных ключей с word-boundary и таблица штатов USPS (веб-морда держит case-insensitive substring; таблица служит US-центричным конфигам — файл под CAR-60, если понадобится US-конфиг). Промежуточная allow-list форма consumer-теста workable остаётся вторым вердиктом рядом с новой родительской.

[1.248.0] — 2026-10-09

Паритет с родителем: пять апстрим-фиксов провайдеров и liveness перенесены — борды, переехавшие на новые API, продолжают работать, а баннер закрытой вакансии на отдельной строке больше не пропускается.

Исправлено

  • Gupy следует за API на portal.gupy.io и шлёт имена фильтров, которые API реально читает (workplaceType/type) — старые плюральные имена молча игнорировались; country больше не отправляется (сконфигурированная страна рисует warning с именем записи вместо отправки). Конфиденциальные работодатели дропаются по точному совпадению ярлыка («Confidencial Seguros» выживает), а max_pages ограничен 100 — API отвечает 400 после offset 10 000, и truncation-warning теперь советует сузить ключевые слова, а не поднимать потолок.
  • Workable сохраняет все страны мультистрановой вакансии: сиблинги под одним job-URL схлопываются в одну строку («London, United Kingdom · Romania · Poland · Croatia») вместо победы первой страны.
  • Liveness: баннер на своей строке — не часть строки выше — «Sign in if you want to apply» / «Get notified when this position is filled» под вакансией читается как клауза закрытия, а настоящее «until filled» через строки по-прежнему читается активным.
  • Radancy читает сиблинг-элемент job-location в legacy-разметке карточки (раскладки семьи Barclays кладут локацию после </a>) — строки без собственной локации остаются пустыми, а не занимают локацию соседа.
  • SmartRecruiters fallback-ссылки сохраняют сконфигурированный slug — вакансия с отсутствующим/недоверенным ref синтезирует публичный URL из slug тенанта, никогда из отображаемого имени.

Добавлено

  • No-ports задокументированы: апстрим-фикс _http proxy fetchImpl (в веб-морде нет прокси-ветки — порт сломал бы identity-проверку DNS-rebinding-гварда и добавил undici-зависимость, от которой репо сознательно уходит); апстримные verify-ats (+226 строк) и cv-facts (веб-морда реализует свой ATS-скоринг и CV-парсинг); родительский дашборд Go TUI и CLI-скрипты (веб-морда не шеллится в них); перестановка импортов в Teamtailor (косметика).

Примечания

  • Известный пробел перенесён как follow-up: родительский tier always_allow в location-фильтре (потребительский тест workable выживает UK-блок благодаря ему) — в location-фильтре веб-морды этого tier нет; порт выразил тот же вердикт потребителя через allow-list тест. Заведено отдельной задачей.

career-ops-ui v1.267.1 · page 2/13 · mirror of the project changelog for cvstart.org