Журнал изменений
GitHubКаждое значимое изменение, релиз за релизом. Тот же файл публикуется в репозитории и переведён на все 19 языков.
[1.247.0] — 2026-10-09
Хвост дизайн-свипа CAR-49…58 закрыт — все оставшиеся minor/polish-находки плюс тёмная карта, которую прошлый релиз осознанно отложил.
Исправлено
- Карта рендерит настоящий тёмный режим. Зум/слои/атрибуция потемнели ещё в v1.246.0; теперь и тайлы следуют теме: тёмный режим рисует OSM-слой с CSS-инверсией (без вендорных ключей), а keyed-пресет Carto-dark доступен через
MAP_TILE_DARK_URL(его хост добавлен в разрешение CSPimg-src). Слой перемонтируется живьём при переключении темы. - У жёлтых callout’ов появились тёмные варианты (
config,batch, hero-warning) через токен-механику v1.246.0 — и попутно пойман регресс: стили.callout aзабивали.btn-primary; теперь скоупятся:not(.btn). - URL в инфо-карточке apply больше не обрезается посреди слова (overflow-wrap anywhere).
- Кнопки заголовка CV — одна иконочная система (текст; микс 📁/📄/💾 убран).
- Табы статусов трекера в одном регистре — sentence case, с локальными особенностями (CJK/ar/hi/ta без родовых форм).
- Иконки топбара — единый stroke-SVG набор — микс 🔔/🌙/🩺 убран; кнопка темы рисует sun/moon через CSS, приложение больше не переписывает текст кнопки.
- Карточка Pipeline на дашборде лишилась розового highlight’а (кольцо только на hover/focus).
- Delete у сохранённых поисков явно деструктивен (
.btn-danger, а не нейтральный ghost). - У preview pipeline появилось настоящее пустое состояние (заголовок + подсказка + CTA, паттерн трекера).
- Мета времени/стоимости — один паттерн:
UI.pageMetaрисует ⏱ + стоимость одной строкой под заголовком вью (evaluate/deep/auto/mode-page/orientation). - Auto-pipeline лишился ✨ — единственный декорированный h1 вернулся на общий левый край.
- Batch docs URL — инлайн-ссылка (без голого переноса посреди абзаца).
- У interview-digest / orientation появились спроектированные пустые состояния (заголовок + подсказка + CTA).
- Export career-plan неактивен, пока план пуст, действия — в одну строку.
- stats: дублирующий заголовок «Market report» убран, плейсхолдер региона помещается (короче ×18, фикс-ширина).
- Config больше не утекает абсолютные пути — серверный
displayPath()показывает~/…или голое имя файла. - Хелпер LLM_PROVIDER — три короткие строки (×18 +
pre-line) вместо 150-символьной mono-стены. - EMAIL-карточка профиля держит адрес в одну строку (ellipsis; перенос только ≤480px).
- Текст Health больше не ломает слова («Run buttons» остаётся целым; overflow-wrap anywhere).
- Инлайн-код в help выживает в RTL (LTR-изоляция, nowrap, скролл;
career-opsв docs/help/ar.md теперь закодирован как токен). - Пилюля «Live evals» в hero больше не сталкивается в ru/ar (flex-wrap, несжимаемые иконки).
Добавлено
- Пресет тёмных тайлов (
MAP_TILE_DARK_URL+ attribution-env) задокументирован в коде; добавление в CSPimg-srcпокрывает хост Carto для него.
Примечания
- Дефолт тёмных тайлов — CSS-инверсия OSM: keyless Carto закрыл базовые карты за API-ключ (проверено запросом), поэтому keyed-пресет — опция, а не дефолт.
[1.246.0] — 2026-10-09
Все десять топ-находок сеньор-дизайн прохода CAR-48 исправлены — включая блокер и все семь мажоров.
Исправлено
- Apply dark: ссылка в инфо-карточке снова видна. Карточка держала светлый surface в тёмной теме; ссылка «Need Playwright? See …» сидела на ≈1.0:1. Theme-aware
.callout--info(токены surface/border/text/link) поднимает тёмную ссылку до ≈7.3:1. - RTL больше не переворачивает числовые композиции. Score-пилли («1.5 / 5»), бэнды отчётов («≥ 4.5», «< 3.5») и дашбордное «/ 5.0» получают направленную изоляцию (LTR-span’ы); ar-словарь там, где прозе лучше, переписывает диапазоны словами.
- CV-markdown остаётся LTR. Textarea и превью рендерятся
dir="ltr"в арабском; обвязка вокруг остаётся RTL. - Колонка DATE в отчётах больше никогда не переносит ISO-дату (nowrap + табличные цифры).
- Сайдбар заканчивается выше USAGE HUD (резерв скроллпорта + fade) — последний пункт навигации больше не уезжает под панель; RTL-зеркалирование учтено.
- Мобильный скан @390: «Save search» полностью виден — ряд контролов переносится и сжимается вместо обрезки у края экрана.
- Контролы карты следуют тёмной теме (zoom, слои, атрибуция). Сами тайлы намеренно не тронуты: их origin закреплён серверным CSP
img-src, а клиентская подмена слоя означала бы правку security-заголовка. - Chat FAB больше не перекрывает интерактив — скролл-паддинг страниц очищает его (кнопки Disable в Portals, score-пилль в Reports, инпут two-pager, атрибуция Leaflet).
- Журнал активности говорит на языке интерфейса: 22 слага действий локализованы ×18 локалей (с fallback на сырой id для неизвестных) и чипы фильтров переводятся; колонка TARGET остаётся — её заполняет auto-pipeline.
- Формы assessments получают настоящие лейблы (sentence case, связка
htmlFor/id) вместо полей только с placeholder’ами.
Примечания
- Не в этом релизе: тёмные тайлы карты (нужно расширение CSP
img-srcна тёмный tile-origin — серверная правка, клиентом не делали намеренно); тёмные варианты жёлтых callout’ов (config,batch) — механика токенов callout уже готова для продолжения.
[1.245.0] — 2026-10-09
Тамильский (தமிழ்) присоединяется как 18-я локаль интерфейса — первый новый язык после волны из 17 локалей, с полным паритетом ключей.
Добавлено
- Тамильский (
ta) — 1449/1449 ключей с полным паритетом кen, без потерянных плейсхолдеров, в хинди-стиле Indic-tech-конвенций (провайдеры, модели, пути и команды остаются латиницей).detect()принимаетta,ta-IN,ta-LK; тамильский — LTR (закреплено тестом). - Полный docs/help/ta.md (33 H2 / 125 H3, счётчики источников сверены с живым реестром, канареечные якоря Hermes, дословный YAML), плюс зеркала README.ta.md и CHANGELOG.ta.md.
- Перечисляющие гейты 17 → 18: LANGS, detect-пробы, i18n-аудит, changelog-parity, RTL-страж, CI
langs— заодно подхватываетhi, ранее отсутствовавший в CI-списке.
Примечания
- Лендинг cvstart.org пока остаётся на 17 локалях — перевод лендинга отдельная задача; синк контента сайта уже отдаёт docs/help/ta.md и ta-changelog.
report-header.jsREPORT_LABELS остаётся на 17 — парсинг тамильских отчётов придёт вместе с генерацией тамильских отчётов в родительском репо.
[1.244.2] — 2026-10-08
Полировка страницы сканирования: лаунчер, строка состояния, панель репостов и фильтры переработаны в один консистентный хром — и таблицу результатов больше не можно растянуть данными бордов.
Исправлено
- Клетки зарплаты показывают только денежный диапазон. Некоторые борды кладут весь текст льгот в поле зарплаты — видимая клетка теперь показывает только денежную часть, а текст перемещает в подсказку (раньше: 6-строчная клетка, растягивающая каждую строку).
- Строки с пустым заголовком показывают компанию (затем нейтральное тире) вместо гигантской пустой клетки.
- Бейдж seniority никогда не переносится в своей колонке фиксированной ширины.
- Строки без оценки показывают приглушённое «◎ —» с подсказкой, что сравнение с two-pager не нашло совпадений (раньше: ничего, что выглядело сломанным рядом с оценёнными строками).
- Контракт высоты aux-ячеек теперь измеряет контентный элемент внутри aux-клетки — td растягивается по высоте строки, что маскировало реальные переносы.
Добавлено
- Редизайн хрому: карточка-лаунчер (выровненный ряд контролов, доминантная primary-кнопка), строка состояния терминала с точкой состояния (простой/выполнение/готово/ошибка) поддерживает reduced-motion, панель репостов имеет потолок высоты со sticky-заголовком (датасет из 1,560 кластеров рендерил панель 155,000 px), и блок фильтров как равномерная адаптивная сетка с выровненным футером.
Примечания
- Тесты 5045 → 5068 unit; браузерные layout-контракты расширены (предел высоты aux в одну строку).
- Не в этом релизе: URL-hash состояние фильтров и клавиатурная навигация строк (отложено); live-LLM регресс × 7 локалей заблокирован кредитами апстрима (разблокировано — CAR-44).
[1.244.1] — 2026-10-08
Исправлено
- Иконки скана объявляют реальные значения. Доступные имена иконок boost/fit/score утекали сырые шаблоны словаря («Соответствие: {band}», «Продвинуто: {by}») —
{band}/{by}/{score}теперь подставляются (слово банды, ключевое слово буста, числовой скор). [HIGH a11y, поймано живым регрессом v1.244.0] - Пайпы в заголовках режутся правильно. Заголовок из 4 сегментов вида «C++ | Rust | Go Developer | Onsite» больше не выносит «Go Developer» как «страну» — для разделения требуется, чтобы сегмент распознавался как настоящая страна (консервативный
Countries.detectCountry). - Даты в мета-строке рендерятся для каждой строки.
daysSinceпринимал только дату без времени, а API отдаёт полные таймстампы для ~83% датированных строк — у таких строк сегмент даты не отображался.
Примечания
- Всё остальное в #/scan идентично v1.244.0 — изменились только три фикса выше.
- Сознательно не сделано: состояние фильтров в hash URL, навигация строк клавиатурой и парсинг
5 lakhs INR(всё — отложенные хвосты).
[1.244.0] — 2026-10-08
Результаты скана читаются как список, а не как стена: одна вакансия = две строки, сигналы — иконки, а скан на 700 строк — таблица с постраничной разбивкой вместо страницы в 37,000 px.
Исправлено
- Анатомия строки. Каждая вакансия рендерится ограниченной двухстрочной строкой: строка 1 — заголовок, строка 2 —
company · location · source · date · work-type. Бейдж буста по грейду, полоса соответствия заголовка и балл соответствия стали иконками с локализованными accessible-именами и тултипами (скринридеры читают «Соответствие: сильное» / “Fit: strong”, а не стену слов); бейджи доверия и релокации встали в тот же ряд. Грейд и зарплата остаются вспомогательными колонками — только на десктопе. - Гигиена заголовков. Хвостовой сегмент
| Germany | Remoteвырезается из заголовка в мета-строку — с учётом Unicode и только когда хвост — настоящий маркер формата работы, а средний сегмент похож на место (легитимные пайпы вроде “C++ | Rust | Go Developer” не режутся никогда). - Никакого горизонтального разлива. Фиксированная раскладка таблицы,
min-width: 0, заголовок и мета в одну строку с многоточием и полным текстом в тултипе: ноль переполнения страницы и ноль внутренней прокрутки таблицы на 1440 px и 390 px (старая страница вылезала на 675 px при телефонной ширине). - Пагинация. Результаты рендерятся по 50 на страницу (на выбор 25/50/100/200) — скан на 700 строк теперь высотой ~4,600 px вместо ~37,000 px, а DOM держит ~1,000 узлов вместо 5,500+.
Добавлено
- Контрактные тесты раскладки: двухстрочная строка, accessible-имена иконок (en + ru), бюджет узлов пагинации и гард количества/порядка title-fit работают в браузерном наборе — редизайн не может тихо деградировать.
Примечания
- Юнит-тестов 5036 → 5045 (браузерный набор обзавёлся контрактами раскладки). Среднее покрытие 98.14 % строк / 89.34 % ветвей (нижний порог 96/86).
- То, что находит сканер, формулы скоринга и
last-scan.jsonне тронуты — title-fit остаётся аннотацией. - Не вошло в релиз: состояние фильтров в URL-хеше и навигация по строкам с клавиатуры (последующая работа); парсинг
5 lakhs INR(досидевшая проблема).
[1.243.2] — 2026-10-08
Исправлено
- Деплоибельный релиз вырезан как v1.243.2. Тег v1.243.1 был перевыставлен после публикации, а серверный
git fetch --tagsкорректно отказывается затирать существующий тег — поэтому v1.243.1 оказался недеплоибельным. Этот тег несёт то же дерево (фикс window-листенера модалки на базе v1.243.0) плюс этот подъём версии. Больше ничего не менялось; опубликованный npm 1.243.1 уже содержал фикс.
Notes
- App code is identical to v1.243.1 — only the deployable tag is fresh (the server’s
git fetch --tagsrefused the re-pointed v1.243.1). - Deliberately not done: force-updating the server-side tag (published tags are never moved).
[1.243.1] — 2026-10-08
Исправлено
- Модалка «Записать итог» теперь действительно закрывается при навигации. Гард v1.243.0 слушал
hashchangeнаdocument, где это событие не срабатывает — фикс приехал мёртвым, и модалка по-прежнему блокировала следующую вьюху (поймано живым регрессом на проде в раунде v1.243.1). Гард слушает наwindow; тест теперь диспетчит туда же и проверен красным на сломанном коде.
[1.243.0] — 2026-10-08
Браузерный слой сохраняет вашу работу и говорит на вашем языке: без потерь несохранённых правок, без молчаливых сбоев, без молчаливого английского в переведённом интерфейсе, AA-контраст повсюду.
Исправлено
- Несохранённые правки переживают перерисовки. Редактор CV держит «грязный» буфер и восстанавливает его при каждом рендере — «Отмена», смена языка или переход в CV Studio теперь запрашивают подтверждение вместо отбрасывания; карьерный план точно так же сохраняет свой сгенерированный текст. Сбои загрузки в Config/CV Studio/career-plan/memory/two-pager показывают ошибку и отключают Save вместо того, чтобы тихо перезаписать файл пустыми значениями.
- Модалка «Записать итог» больше не переживает навигацию. Открытая на
#/tracker, при клике на другой вид она оставалась висеть поверх всей страницы (найдено в двухстендовой регрессии v1.242.1) — теперь закрывается при смене маршрута; её собратья из вида сканирования тоже останавливают свои SSE-потоки и отписывают слушатели при навигации. - Разбор зарплат и ключевых слов читает любую письменность. Чипы ключевых слов используют Unicode-границы слов (кириллица,
c++,.netфильтруются правильно, а не в ноль строк); один общий парсер зарплат чинит$182.9K - $240K,от 100 000 руб,100 тыс. руб.,50 000 Kč(было 50 млн долларов) и семантику диапазонов ($100-150K); обязательное «Germany or Netherlands» больше не считается одновременно и совпавшим, и нарушенным. - Потоки завершаются ровно один раз. Оборванное SSE-соединение доставляло ошибку дважды и могло навсегда оставить Generate неактивной; каждое терминальное состояние (done/error/close/throw, включая убитый дочерний процесс) закрывается одним путём и возвращает кнопку. Авто-pipeline выгружает свой последний буферизованный кадр вместо того, чтобы терять его.
- Приватность и корректность в мелких путях. Тело баг-репорта больше не несёт URL оцениваемой вакансии или slug’и отчётов; приватность CV маскирует голые
linkedin.com/in/…-ссылки и имена, набранные капсом, и перестаёт искажать европейские даты; определение страны берёт самое длинное совпадение и отказывается угадывать, когда названы две страны («Cambridge, MA» больше не → UK); скачивание файлов сохраняет кириллические имена. - Роутер и a11y. Skip-ссылка больше не ведёт 404 в
ontent; активный пункт навигации несётaria-current="page"; один<main>и один<nav>; Escape закрывает мобильную шторку (которая теперь действительно вне порядка табуляции, когда закрыта);scroll-padding-topдержит фокус видимым за липкими панелями; липкий баннер больше не накрывает топбар. - Контраст AA в обеих темах — тёмные тосты были белым по белому (1.10:1): тосты, кнопки, баннеры и ссылки на
--rauschтеперь проходят через инвариантные к теме токены, под защитой вычисляемых WCAG-контрактных тестов (≥ 4.5:1). - Всё остальное из раунда: evaluate показывает предупреждение об усечении; memory/config/two-pager отказываются сохранять поверх неудачной загрузки; scan больше не бросает свои SSE-потоки; результаты скана классифицируют доски с API по серверному реестру вместо разъехавшегося регулярного выражения; «Proceed with Caution» в трекере — предупреждение, а не красный бейдж; статистика сворачивает локализованные статусы; столбчатые диаграммы читаются правильно в RTL; docs-FAB больше не накрывает контент на телефонах;
GET /app.jsотвечает 404 вместо оболочки индекса;GET /api/runnersперечисляет свои действия.
Добавлено
- Датский присоединяется к авто-детекции — браузер с
da-DKтеперь получает датский интерфейс (словарь был полон, детекция его пропускала);zh-Hant*отображается на традиционный китайский.cv-diagnostics— чек-лист резюме — полностью локализован (28 ключейdiag.*× 17). Добавлено 26 подписей field-spec; удалено 31 мёртвый ключ; списки провайдеров больше не утверждают, что Hermes «последний в авто-порядке»; aria-label у docs-fab и меню локализованы.
Примечания
- Юнит-тестов 4812 → 5036, браузерных 118 + новые навигационные/контрастные контракты. Среднее покрытие 98.15 % строк / 89.34 % ветвей (нижний порог 96/86).
- Не вошло в релиз: редизайн
#/scan(Phase 4 — v1.244.0, спека уже набросана); отдельный ключ словаряexport.docxFailed(при сбое переиспользуетсяcommon.error);5 lakhs INRпо-прежнему парсится как 5 (досидевшая проблема).
[1.242.1] — 2026-10-08
Исправлено
- В релизном архиве больше нет симлинков
node_modules. Git-тег v1.242.0 нёс симлинкnode_modulesв корне иsite/node_modules(общийgit add -Aпровёл их мимо ignore-паттернов с завершающим слешем), из-за чего упала tar-распаковка серверного деплоя — деплой безопасно откатился, прод остался на 1.241.1. Оба симлинка сняты с трекинга, ignore-паттерны расширены; npm-тарбол не пострадал (npm исключаетnode_modulesпри упаковке).
Примечания
- Код приложения идентичен v1.242.0 — только упаковка. Контент «корректность источников» уезжает на прод этим деплоем (v1.242.0 на прод не доехал: серверный деплой откатился после сбоя tar).
- Сознательно не сделано: перевыпуск тега v1.242.0 (npm уже опубликовал 1.242.0; тарбол в реестре чист — npm исключает
node_modulesпри упаковке).
[1.242.0] — 2026-10-07
Слой данных сканера снова заслуживает доверия: мёртвые источники ожили, искажённый ответ больше не может выдать себя за пустую доску, а враждебный URL доски больше не может перенаправить скан.
Исправлено
- Мёртвые источники снова живы. justjoin (конверт API сменился на
{data, meta.next.cursor}) читает весь каталог обходом по курсору с новыми полями зарплаты; nofluffjobs отвечает 400 безsalaryCurrency— отправляется требуемый родителем запрос, а результаты пагинируются (pageTo). himalayas и jobicy видели лишь ~20 новейших вакансий из 100k+ — оба теперь обходят свои каталоги целиком (offset- и курсорная пагинация с лимитами страниц). - Ответ 200 неправильной формы выбрасывает ошибку на странице 1. ~40 источников читали искажённые или challenge-ответы как «0 вакансий», и скан молча «завершался успешно». Новые проверки
server/lib/sources/_shape.mjs(requireArray/requireContainer/requireObject) делают отказ громким во всех семействах; сбой на поздней странице сохраняет уже собранные страницы; пагинация останавливается по сырой длине страницы, а не по числу строк после фильтрации. - SSRF: проверки вендоров по подстроке заменены разобранными точными закреплениями хостов. lever (
clever.comсодержитlever.co), greenhouse (5 хостов + устаревшие path-slug-хостыboards[.eu].greenhouse.io), ashby, smartrecruiters, workable, gem REST, ibm, arbeitsagentur, hecklerkoch (только www), workingnomads (http://169.254.169.254был доступен в обход DNS-защиты), remoteok, remotive, rss, successfactors.redirect:'error'везде; URL вакансий должны бытьhttps:на закреплённом хосте (javascript:/data:/ чужие хосты отбрасываются). - Одна неверно настроенная запись portals больше не обрывает весь скан.
resolveAdapterловит исключения адаптеров,detectApiобёрнут по каждой компании, адаптеры с исключением возвращаютnull; ошибки fetch в comeet маскируют?token=, прежде чем те попадут в логи или карантинные записи. - Пагинация и свежесть. workday обходит тенантов дальше 100 вакансий (offset-страницы,
MAX_PAGES); telegram хранит НОВЕЙШИЕ посты и пагинирует?before=вместо того, чтобы молча оставлять самые старые; tencent продолжает обход, когдаCountотсутствует; taleo разбирает таблицы с французскими заголовками; avature останавливается по сырой длине страницы; штампы дат в UTC (amazon со сдвигом на единицу восточнее UTC, torre, workingnomads, trudvsem); HTML-сущности декодируются (geekjob, getmatch, hh); mycareersfuture выдаёт полную 12-полевую форму вакансии. - Детекция удалёнки ужесточена. arbeitsagentur, vdab, rippling, teamtailor: доля ставки («Verpleegkundige (100%)»,
bundesweit) и слово «distributed» («Distributed Systems Engineer») больше не помечают офисные вакансии как Remote.
Добавлено
tests/adapters-pin-coverage.test.mjsзавершает ветвное покрытие закреплений хостов, введённое этими закреплениями.
Примечания
- Юнит-тестов 4419 → 4812, браузерных 118. Среднее покрытие 98.15 % строк / 89.36 % ветвей (нижний порог гейта 96 / 86).
- Не вошло в релиз: клиентские находки того же раунда ревью (Phase 3 — views, libs, CSS/a11y, i18n); у
sources/habr.mjsпо-прежнему нет отдельного тестового файла (исключение из правила растущего порога).
[1.241.1] — 2026-10-07
Живые оценки работают от начала до конца на любой локали, карта вакансий размещает длинные объявления с несколькими локациями, а деплой больше не убивает идущий скан.
Исправлено
- Оценки видят метод целиком. Бюджет контекста (144 КБ) всё ещё обрезал конец
modes/oferta.md, когда подставлялись резюме и профиль, поэтому каждая живая оценка теряла последние блоки. Теперь бюджет — 192 КБ, а мягкий предел промпта — 256 КБ, заданные один раз вllm-dispatch.mjs. - Отчёты на выбранном языке. Языковая инструкция теперь и открывает промпт, и завершает его, а обычные слова (роль, команда, дизайн…) пишутся на языке отчёта; на английском остаются только имена, технологии и дословные цитаты. Исправляет хинди- и японские отчёты, которые были в основном на английском.
- Медленные оценки больше не заканчиваются HTTP 502. Таймаут оценки — 300 с (раньше был таймаут провайдера по умолчанию, 180 с).
- Буквы блоков на хинди. Отчёт, где буквы A–G записаны деванагари (
## ए),## बी)…## जी)), распознаётся, аSCORE:принимает локальные цифры (३.८,٣٫٨,3.8) и десятичную запятую. - Карта вакансий: длинные локации. Ячейка вроде «Berlin Office · Berlin · Germany · Bosnia · …» (300+ символов) отклонялась с HTTP 400, и у вакансии не было маркера. Теперь берётся первое реальное место (
·,;,|,/, переводы строк; сегменты «Remote …», «(HQ)» и «Office» пропускаются); отклоняется только запрос длиннее 2000 символов. - Деплой и ежечасный скан. Деплой приостанавливает таймеры скана и авто-оценки, дожидается завершения идущего (до 15 мин), перезапускает сервис и всегда возобновляет таймеры — в том числе при откате.
Добавлено
- Карта вакансий на cvstart.org на всех 17 языках, а также в английском и русском README.
- Скриншоты карты на всех языках — по одному на локаль (
scripts/capture-map-screenshots.mjs), во всех 17 README, на cvstart.org и в новом подразделе справки «Как пользоваться картой» (§33, ×17).
Примечания
- Тесты 4404 → 4419, браузерных 118. Удалённый QA (все маршруты, сайт, ссылки и живой LLM × 17 локалей) зелёный на продакшене.
- Не вошло в релиз: стриминг оценок — тайм-аут 300 с остаётся жёстким пределом (медленная локальная модель всё ещё может в него упереться), а мост Hermes по-прежнему работает без стриминга.
[1.241.0] — 2026-10-07
Релиз-ужесточение по итогам код-ревью 28 агентами (~190 подтверждённых находок): закрыты классы безопасности, падения процесса и потери данных, плюс карта вакансий, добавленная @bullitt186.
Добавлено
- Карта вакансий (
#/map) — вакансии из пайплайна, последнего скана и трекера на карте OpenStreetMap; геокодирование через Nominatim с идентифицирующим User-Agent, очередью 1 запрос/с, back-off и дисковым кэшем. Leaflet отдаётся с этого сервера (без CDN); CSP лишь добавляет origin тайлов вimg-src. Вклад @bullitt186 (#381). - Защита запросов — сервер отклоняет
Host, который он не обслуживает (DNS rebinding), а также кросс-сайтовые записи и GET с побочными эффектами (/api/stream/*,/api/run/*,/api/geocode). Обратный прокси, пробрасывающий публичное имя, перечисляет его вALLOWED_HOSTS; workflow деплоя пишет этот systemd drop-in из публичного URL. - Гейт покрытия —
npm run test:coverage:gate(scripts/coverage-report.mjs) печатает построчное и по ветвям покрытие по файлам и падает ниже растущего базового уровня.
Исправлено
- Некорректный запрос больше не останавливает сервер. Express 4 игнорировал отклонённые асинхронные обработчики, поэтому поле неверного типа в
/api/deep,/api/auto-pipeline,/api/career-plan,/api/trackerили NUL-байт в/api/statsзавершали процесс; теперь это JSON 400, а любое другое отклонение становится 500. - Прерванный или неудачный скан больше не стирает последние результаты.
last-scan.jsonсохраняется, когда скан остановлен или все источники упали, а скан одной компании сливается с прежним, а не заменяет его. Одна неверно настроенная записьportals.ymlбольше не обрывает весь скан. - Оценки видят метод целиком. Файлы режимов обрезались на 16 КБ, поэтому
oferta.md(92 КБ) терял блоки C–G; теперь лимиты на файл — 128 КБ для режимов и 64 КБ для резюме, а обрезка сообщается. Пустой или обрезанный ответ завершается ошибкой вместо записи пустого отчёта. - SSRF: переходы по редиректам повторно проверяются на приватные цели, проверяется каждый DNS-ответ, дедлайн скана охватывает тело ответа, ключи
*_BASE_URLуходят только на хост вендора (Hermes/Ollama: loopback или LAN), pandoc запускается с--sandbox. - Зависания event loop: два квадратичных регулярных выражения (очистка HTML, санитизация markdown) заменены линейными сканерами.
- Потеря данных: удаление строки пайплайна сохраняло компенсацию других строк;
$&в URL портилpipeline.md; сохранения мок-интервью, нетворкинга и глубокого исследования перезаписывали прежние файлы; история интервью хранила первые 40 реплик вместо последних. - Слой LLM: авто-пайплайн и глубокое исследование учитывают
LLM_PROVIDERи всех провайдеров через общую диспетчеризацию, фиксируют расход и сообщают об обрезке; оценка/тест Gemini больше не сохраняют отчёт; OpenAI используетmax_completion_tokens, аOPENAI_MODEL, подходящий только для Codex, отправляется какgpt-5; описание вакансии обрамляется как недоверенные данные. - PDF отчёта, глубокого исследования и встроенные передают
--skip-fact-check(проверка фактов резюме блокировала любой отчёт с числами); формат страницы из профиля больше не переопределяется. - Сканеры: карантин привязан к URL (исправление URL запускает повторную попытку), дедупликация внутри прогона — по каноническому URL, пустые записи списков больше не совпадают со всем, атомарные записи, строки истории
skipped_*не считаются «виденными». - Управление процессами: эскалация до SIGKILL никогда не срабатывала; пакетные запуски убивают всю группу процессов при отключении.
- CI и релиз: CI теперь запускает гейты паритета changelog, workflow и i18n; workflow с минимально необходимыми правами; секреты продакшена только в окружении
production; npm-пакет теперь включаетdocs/help/. - Тесты: ни один тест больше не обращается к сети или реальному родительскому проекту (явный
CAREER_OPS_ROOTвсегда соблюдается); скопированные алгоритмы заменены продакшен-кодом.
Примечания
- Тестов 4056 → 4404, браузерных 118. Базовый уровень покрытия сервера: строки 96,7 %, ветви 86,3 % (
scripts/coverage-baseline.json). - Спецификации теперь лежат в одной папке,
docs/sdd/specs/; план программы —docs/sdd/PLAN.md, матрица «инвариант→гейт» —docs/sdd/TRACEABILITY.md. - Заметка для операторов: за обратным прокси задайте
ALLOWED_HOSTS=<публичный хост>; базовый URL Ollama/Hermes на публичном хосте теперь отклоняется.
[1.240.0] — 2026-10-06
Паритет с родителем — career-ops main @ 62905981 (VERSION 1.35.0, 177 коммитов апстрима с b39931e). Шесть новых источников — 103 → 109 (104 EN + 5 RU) — исправления, зеркально перенесённые в восемь существующих провайдеров, и формат чек-листа пайплайна, внесённый @bullitt186.
Добавлено
- Шесть новых источников — 103 → 109 (104 EN + 5 RU), 98 → 104 EN-адаптера.
- ADP Workforce Now (
workforcenow.adp.comсcid+ccId) — пагинация$skipпо числу фактически возвращённых строк; ответ безjobRequisitionsи безmetaвыбрасывает ошибку вместо того, чтобы читаться как пустая доска; опциональное обогащение деталями (adpWorkforcenow.fetchDetails, по умолчанию 25, максимум 100). - Gupy (
employability-portal.gupy.io) — сквозной поиск по ключевым словам на всей площадке, сначала новые, с ранней остановкой поsince_days;workplaceTypeчитается как единственная строка pt-BR, поэтому гибридные вакансии не теряются; конфиденциальные страницы карьеры отбрасываются. - JazzHR (
*.applytojob.com) — парсер карточек с запасным вариантом по «голым» ссылкам, чтобы частичный редизайн всё равно давал вакансии; ссылки на вакансии при нуле разобранных карточек выбрасывают ошибку, по-настоящему пустая доска возвращает[]. - Startup Jobs (
startup.jobs/feeds/jobs) — RSS, один запрос; искажённое, HTML- или усечённое тело выбрасывает ошибку вместо возврата[](исправление родителя по итогам ревью после слияния,08fe5d06); CDATA и комментарии маскируются перед сопоставлением элементов. - Taleo (
<tenant>.taleo.net,tre.taleo.netотклоняется) — GET оболочки, затем POSTsearchjobs; оболочка без id портала выбрасывает ошибку как закрытая доска; столбцы сопоставляются по заголовку. - UKG Pro / UltiPro (
recruiting[N].ultipro.{com,ca}) —Top=50+Skip,totalCountникогда не считается условием остановки, лимит страниц 100 (ограничивается значением 1500), опциональные страницы деталей разбираются обходом фигурных скобок с учётом строк.
- ADP Workforce Now (
Исправлено
- Пайплайн в формате чек-листа родителя — upstream career-ops пишет
data/pipeline.mdкак секции## Pending/## Processedсо строками- [ ] url | …; UI читал такой файл как 0 в ожидании, а запись выполнял, дописывая ограждённый блок в конец файла, куда родитель никогда не смотрит.parsePipeline,addPipelineUrlиremovePipelineUrlтеперь читают и пишут такую форму (дедупликация и по обработанным строкам; компенсация — как помеченный сегментnote: comp …); формат с ограждённым блоком не изменился. Вклад @bullitt186 (#380). - Страна добавляется к локации — Ashby (основной
address.postalAddress), Breezy (country.name) и Recruitee (плоское полеlocation) дописывают страну, если в названии она ещё не содержится как целое слово, поэтому удалённая роль с основной локацией в Великобритании и дополнительной в США больше не отбрасывается фильтром локации. - MokaHR — региональный хост
hire-r1.mokahr.comпринимается (allowlist — ровноapp.mokahr.com+hire-r1.mokahr.com), POST к API и ссылки на вакансии используют собственный хост доски, аsuccess:falseв теле ответа теперь выбрасывает ошибку вместо того, чтобы читаться как пустая доска. - Avature — карточки
article--jobsразбираются (заголовок берётся из якоря в шапке, локация — изspanсicon-address). - SAP SuccessFactors — URL сохранённого поиска
/go/<Category>/<id>[/<offset>]/разрешается в корень арендатора; раньше он попадал на…/tile-search-results/, который отвечает 200 с нулём плиток. - Deutsche Bahn — db.jobs обнуляет любую страницу результатов, на которой есть вакансия, которую он не может отрисовать; теперь обход сортирует по
pubExternalDate_tdtпо 1000 на страницу (максимум 5 страниц), останавливается по числу «сырых» якорей попаданий и выбрасывает ошибку при отсутствии заголовка результатов или неразобранных ссылках на вакансии вместо того, чтобы вернуть неполную доску. - HigherEdJobs — проверочная страница Incapsula, отданная с HTTP 200, раньше разбиралась как ноль элементов и читалась как пустая доска; теперь она выбрасывает ошибку.
- Один тест читал настоящий родительский проект на машине разработчика —
tests/reports-list-cache.test.mjsзадавалCAREER_OPS_ROOTво временный каталог безcv.md/portals.yml, которыйpaths.mjsигнорирует, поэтому локально он считал отчёты родителя (6 вместо 3), а CI, где рядом нет родителя, проходил.
Примечания
- Тесты 3782 → 4056. Новые наборы:
sources-adp-workforcenow,sources-gupy,sources-jazzhr,sources-startup-jobs,sources-taleo,sources-ultipro,sources-location-country-fold,sources-deutschebahn-blanked-page,sources-higheredjobs-challenge-page, а такжеparsers-pipeline-checklistиз #380. - Родитель: форк приведён к upstream
main(отставание 144 → 0 коммитов), четыре расхождения из ADR-0002 проверены и остались нетронутыми; откат upstream кириллическогоLOCATIONISH_REне перенесён. - Уже выпущено в v1.239.0, не повторяется: AppliTrack, NEOGOV, Red Rover, SchoolSpring, OCC Mundial, встроенные доски Greenhouse, Workday
myworkdaysite.com.
[1.239.4] — 2026-09-29
Патч по итогам живой регрессии v1.239.3.
Исправлено
- Корейская и традиционная китайская оценки сообщали об отсутствии итогового блока. Такие отчёты занимают 10–11 тысяч символов, а итоговый блок идёт последним и оказывался за лимитом 8192 выходных токенов. Оценка (и авто-пайплайн) теперь допускает 16384 токена, а если провайдер всё же обрезает ответ, первое предупреждение говорит именно об этом, а не перечисляет недостающие блоки.
- Живая проверка сочла короткий немецкий ответ не немецким (3 немецких стоп-слова, ни одного английского). В ответе-списке их может быть мало; при полном отсутствии английских теперь достаточно трёх.
- Тесты 3778 → 3782.
Примечания
- Маршруты глубокого исследования и режимов сохраняют лимит 8192 токена: их ответы в живых прогонах до него не доходят.
[1.239.3] — 2026-09-29
Патч по итогам живой регрессии v1.239.2.
Исправлено
- Арабские оценки по-прежнему не проходили проверку A–G после того, как v1.239.2 исправил японский и хинди. Замаскированный скелет заголовков (без текста отчёта в логе) показал причину: арабский отчёт пишет букву блока по-арабски, в порядке абджад, что бы ни просил промпт. Проверка теперь принимает أ ب ج د هـ و ز как A–G, если буква стоит отдельным словом в начале заголовка, — так же, как уже принимала кириллические двойники; те же буквы внутри арабского слова не засчитываются.
- Живая проверка печатает этот замаскированный скелет при провале проверки A–G, чтобы следующий случай можно было разобрать по логу.
- Тесты 3776 → 3778.
Примечания
- В примечании v1.239.2 сказано, что проверка не будет ослаблена; для арабского она ослаблена, потому что три живых прогона показали: одним промптом латинскую букву не удержать.
[1.239.2] — 2026-09-29
Патч по итогам живой регрессии v1.239.1.
Исправлено
- Арабская оценка перевела буквы блоков, а японская потеряла итоговый блок оценки. В v1.239.1 последняя строка промпта повторяла только язык. Теперь она также говорит, что латинская буква A–G в каждом заголовке и блок
---SCORE_SUMMARY---(маркеры и ключи по-английски) не переводятся, и просит собственную письменность языка. - Тесты 3775 → 3776.
Примечания
- Проверка отчёта не ослаблена до приёма переведённых букв или маркеров: стабильность формата обеспечивает промпт.
[1.239.1] — 2026-09-29
Патч по итогам живой регрессии v1.239.0.
Исправлено
- Оценки на японском и украинском приходили почти целиком на английском. Указание языка стояло в начале промпта, перед десятками килобайт английских файлов проекта и текста вакансии. Теперь промпт повторяет язык последней строкой.
- Живая проверка считала скан упавшим, когда на сервере уже шёл другой (часовой скан, перезапущенный сразу после деплоя). Отказ сервера запускать второй скан — правильное поведение; проверка теперь ждёт и повторяет попытку, до 30 минут.
- Тесты 3774 → 3775.
Примечания
- Ограничение «один скан за раз» на сервере не ослаблено: второй скан по-прежнему отклоняется с
SCAN_BUSY. Изменилась только проверка.
[1.239.0] — 2026-09-29
Паритет с career-ops @ b39931e: пять новых источников и три перенесённых исправления.
Добавлено
- Пять новых источников (98 → 103; английских адаптеров 93 → 98): AppliTrack (
applitrack.com/<округ>), NEOGOV (SchoolJobs / GovernmentJobs), Red Rover (jobs.redroverk12.com/org/<id>) и SchoolSpring (<округ>.schoolspring.com) для вакансий в школах и госсекторе, плюс OCC Mundial — крупнейший сайт вакансий Мексики (явныйprovider: occс поисковыми запросами). Каждый привязан к своему хосту, работает только по HTTPS и отказывается от редиректов. - Тесты 3500 → 3774.
Исправлено
- Встроенные доски Greenhouse (
job-boards.greenhouse.io/embed/job_board?for=<доска>) вели на доску с именем «embed» и падали; теперь доска берётся из?for=, и только на настоящем хосте Greenhouse. - Доски Workday на
myworkdaysite.com(/recruiting/<tenant>/<site>) не распознавались; теперь они сканируются, а ссылки на вакансии ведут на тот же хост. - Имя отчёта со скобками (
042-acme-(berlin)-….md) обрезалось в трекере на первой); теперь имя сохраняется целиком.
Примечания
- Изменения родителя, касающиеся только CLI, не переносятся — шаблоны PDF и CV, scaffolder, пакетный запуск, импорт career-profile, покрытие ключевых слов ATS, переводы режимов интервью, CLI-хост
pi: web-ui их не запускает. - Чтобы открыть такой отчёт из трекера, маршрут отчётов должен принимать скобки в имени файла; этот маршрут в данном релизе не меняется.
[1.238.3] — 2026-09-27
Страницы отвечают и во время скана, а логи удалённого QA больше не раскрывают адрес прод-сервера.
Исправлено
- Дашборд и отчёты грузились секундами, а во время скана падали по таймауту.
/api/reportsи/api/dashboardчитали и разбирали все отчёты на каждом запросе — около 3,7 с на прод-сервере, блокируя все остальные запросы; во время скана загрузки страниц упирались в 30-секундный таймаут. Теперь разобранные заголовки кэшируются по файлу и обновляются при его изменении, так что список стоит одной проверки файла на отчёт (500 отчётов: 430 мс → 3 мс локально). - Находка удалённого QA могла вывести часть адреса прод-сервера в публичный лог. GitHub маскирует секрет, только если он виден целиком, а сообщение, обрезанное до 180 символов, заканчивалось посреди адреса. Теперь находки и отчёты целиком редактируются до обрезки.
- Каждая оценка через ИИ помечалась как некорректная. Промпт никогда не просил блок
---SCORE_SUMMARY---, которого требует проверка отчёта (скрипты оценки родительского проекта его просят), а переведённый отчёт переводил слово «Block», и проверка не находила разделы. Теперь промпт просит итоговый блок и букву блока в каждом заголовке, проверка принимает переведённое слово перед буквой, а итоговый блок удаляется до показа и сохранения отчёта.
Добавлено
- Тесты 3488 → 3500.
Примечания
- Сканер по-прежнему работает внутри процесса сервера; этот релиз убирает второй крупный источник блокировок, но в отдельный worker скан не выносит.
[1.238.2] — 2026-09-27
Исправление 502, которые прод-сервер всё ещё отдавал во время почасового скана.
Исправлено
- Страницы и ресурсы отдавали 502 во время скана. Node закрывает простаивающее keep-alive-соединение через 5 с, а Caddy переиспользует такие соединения до 2 минут. Пока скан внутри процесса загружал сервер, Node закрывал сокеты, в которые Caddy только что отправил запрос, и Caddy отвечал 502 connection reset by peer (62 раза за один скан, при этом сервер был жив). Теперь сервер держит простаивающие соединения 125 с — дольше прокси, — так что первым всегда закрывает прокси.
Добавлено
deploy.yml→verifyпоказывает, почему прокси ответил 502 (виды ошибок, адреса скрыты), число 502 по минутам с последнего перезапуска и итог почасового скана.release.ymlумеет создать недостающий тег при ручном запуске — релиз можно выпустить там, где теги не пушатся.- Тесты 3486 → 3488.
Примечания
- Скан по-прежнему идёт внутри процесса сервера и может занимать его на секунды; вынос в отдельный worker в этот патч не входит.
[1.238.1] — 2026-09-27
Исправление стабильности по итогам первого live-регресса прод-сервера: один скан за раз и исправления сайта.
Исправлено
- Два скана одновременно роняли сервер. Каждый
GET /api/stream/scanзапускал полный сканер внутри процесса сервера без всякой защиты; скан из интерфейса совпал с почасовым таймером, и процесс упал с JavaScript heap out of memory (дважды, в это время отдавались 502). Теперь скан один за раз: второй запрос получает SSE-errorс кодомSCAN_BUSY, и ничего не запускается. Workflow деплоя также поднимает лимит памяти сервера (heap_mb, по умолчанию 448 МБ). /helpи/changelogна cvstart.org прокручивались вбок на телефоне (а/changelogна ja/zh — и на десктопе): длинный код и оглавление теперь переносятся.- Хинди не было в языковых альтернативах sitemap; проверка i18n сайта теперь сверяет sitemap с реестром языков.
- Справка §19 просила добавить ключ во «все 8» языковых файлов — их 17.
Добавлено
- Удалённый регресс (
.github/workflows/remote-qa.yml): каждый маршрут × каждый язык развёрнутого приложения (только чтение), каждая страница × язык × телефон/десктоп на cvstart.org, каждая ссылка в README и вики и, по запросу, реальный скан и живые вызовы ИИ на каждом языке. - Workflow деплоя сервера (
.github/workflows/deploy.yml):recon,deploy(с побайтовым откатом) иverify(smoke-тест и отчёт о стабильности). - Тесты 3481 → 3486.
Примечания
- Потребление памяти самим сканером в этом релизе не уменьшено: защита не даёт идти двум сканам сразу, а drop-in поднимает потолок. Профилирование настоящего скана по 98 источникам требует продовой сети.
- Почасовой таймер
career-ops-scanне менялся;reconтеперь показывает, какие команды он запускает.
[1.238.0] — 2026-09-27
Паритет с родителем — career-ops main @ 993085ce (VERSION 1.34.0, 182 коммита с de2224a9). Четыре новых источника, десять зеркальных исправлений провайдеров, закрыта общесканерная брешь SSRF и бесплатная подсказка соответствия заголовка в результатах сканирования.
Добавлено
- Четыре новых источника — 94 → 98 (93 EN + 5 RU), 89 → 93 EN-адаптера.
- Eploy (
provider: eploy) — читает sitemap/live-jobs.xmlарендатора на любом брендированном хосте (только HTTPS; IP-литералы,localhost,*.local,*.internalи однословные хосты отклоняются). Ответ, не являющийся sitemap (страница входа), выбрасывает ошибку вместо того, чтобы засчитаться как ноль вакансий. Страницы деталей — опционально (eploy: { fetchDetails, detailLimit }). - HiringRoom (
*.hiringroom.com) — сначала JSON-LD/portal/jobs, устаревшие карточки/jobsкак запасной вариант; URL вакансийhttp://в payload перестраиваются на HTTPS. - PeopleSoft Candidate Gateway — определяется по пути
/psc/{site}/EMPLOYEE/HRMS/c/HRS_HRAM_FL.HRS_CG_SEARCH_FL.GBLна любом брендированном хосте. Сам следует редиректам сессии PeopleSoft в пределах того же origin (максимум 10 переходов, каждый переход заново привязывается к origin арендатора и проверяется на приватные адреса), повторяет форму «load more» и помечает частичный обход (peoplesoftIncomplete) вместо того, чтобы сообщать о нём как о полном. - PrevueAPS (
*.prevueaps.ca) — определяет id сайта со страницы вакансий, затем читает JSON API.
- Eploy (
- Подсказка соответствия заголовка в строках
#/scan. Каждый результат несёт грубый чип strong / related / weak fit, сравнивающий заголовок вакансии с целевыми ролями изconfig/profile.yml(target_roles.primary+ названия архетипов). Это бесплатная оценка по ключевым словам: она никогда не фильтрует, не переупорядочивает и не меняет количество, и подсказка об этом говорит. Вычисляется при выдаче результатов, поэтому правка профиля отражается только при следующем обновлении. Переведено ×17. Зеркалит parent #3261.
Исправлено
- Безопасность — защита от DNS rebinding ни разу не срабатывала при реальном сканировании.
http-json.mjsпроверял разрешённые адреса только тогда, когда получал голый глобальныйfetch, но оба сканера подставляютmakeTimeoutFetch(), поэтому каждое сканирование пропускало эту проверку. Источник, указывающий на имя хоста, разрешающееся в10.xили127.0.0.1, запрашивался. Теперь защита работает внутри самогоmakeTimeoutFetch, наперегонки с таймаутом сканирования, чтобы зависший резолвер не мог подвесить весь скан. Найдено при ревью этого релиза. - Безопасность — адреса IPv6, встраивающие адрес IPv4, не распознавались как приватные.
http://[::ffff:127.0.0.1]/(который парсер URL переписывает в[::ffff:7f00:1]) проходилisValidJobUrl. Теперь и точечная, и шестнадцатеричная формы::ffff:a.b.c.d/::a.b.c.dклассифицируются по встроенному адресу. Parent_ip-guard.mjs. - Rippling перешёл на API доски v2 (
ats.rippling.com/api/v2/board/<slug>/jobs), с пагинацией (по умолчанию 10 страниц, максимум 50), блокировкой хоста в ссылках на вакансии и валидацией slug. Вставленный URL API теперь даёт настоящий slug вместоapi. - Recruitee — предложения с несколькими локациями перечисляют каждое место (
A · B), демо-вакансии(Sample)и строки без заголовка отбрасываются, а предложение сохраняется, если хотя бы один из двух его URL валиден. - Pinpoint — засеянные демо-вакансии (задвоенное демо-видео) отбрасываются.
- Telegram-каналы — первая строка с несколькими
|больше не читается какЗаголовок | Компания, и распознаётся больше слов-ролей. Учитывающий кириллицуLOCATIONISH_REфорка не тронут (ADR-0002). - Workday — 422/401/403 на первой странице проверяется один раз; страница обслуживания Workday или редирект сбоя помечает доску как мёртвую (карантин в строгом режиме). Запросы CXS и деталей теперь отказываются от редиректов.
- BambooHR — перенаправленный
/careers/listтеперь триажируется через публичную embed-ленту: пусто означает живую доску без вакансий, что угодно другое — мёртвую доску. - Heckler & Koch / Rheinmetall — голый
heckler-koch.com/rheinmetall.comпереписывается наwww., чтобы транспорт без редиректов всё же до него добрался. Ashby отказывается от редиректов. beesite темпирует страницы отменяемымdelay(). - Фильтры заголовков сворачивают диакритику с обеих сторон (сканеры EN и RU), поэтому
Développeurловится негативомdeveloppeur. - Сопоставление компаний в cooldown понимает нелатинские названия и корпоративные формы (маркеры CJK/корейского, турецкая точечная İ). Названия CJK раньше сворачивались в пустой ключ и никогда не совпадали.
location_filter.strict: trueтеперь отказывает закрыто при пустой локации, если задан список разрешённых или заблокированных.- Liveness — новые проверки публичного API для встраиваний Greenhouse (
gh_jid), Arbeitsagentur и We Work Remotely; SmartRecruiters теперь решает по флагуactiveвакансии. - PeopleSoft — относительное действие формы «load more» теперь разрешается относительно страницы поиска, а не корня origin.
Примечания
- Тесты 3210 → 3481. Новые наборы:
sources-eploy,sources-hiringroom,sources-peoplesoft,sources-prevueaps,title-fit,fetch-timeout-dns-guard,cooldown-company-match-unicode,liveness-api-more-rungs,ru-scanner-title-accent-fold. - Не перенесено, с причинами: границы слов Unicode в фильтре локации (фильтр web-ui основан на подстроке, поэтому границы для исправления нет — осознанный полный порт фильтра локации родителя — задача на будущее); дедупликация заявок с одинаковым заголовком (web-ui дедуплицирует только по URL); предупреждения о репостах агрегаторов, область домена чёрного списка, квитанции неподтверждённого нуля и миграция
scan-runs.tsv(у web-ui нет ни одной из этих поверхностей); разбиение на фасеты и теги усечения Workday (web-ui получает одну страницу на доску);enrichDateBambooHR (нет хука обогащения даты); рефакторинг sleep в_http.mjsродителя примерно в 20 провайдерах (web-ui уже использует общий отменяемыйdelay); updater, doctor, PDF, tracker/merge-tracker,validate-profile,discover-new-companies, приложение Next.jsweb/, GitHub-боты — только CLI или ретранслируемые. - Осознанно оставлено иначе, чем у родителя: Heckler & Koch / Rheinmetall остаются только HTTPS (без апгрейда
http→https); мёртвая доска Workday при обычном сканировании по-прежнему возвращает[], а не проваливает скан.
[1.237.1] — 2026-09-22
Патч по итогам внешней QA-проверки v1.237.0. Исправление Jobstreet впервые подтверждено извне: на хостах AU, NZ, HK и MY старая форма /id/job/<id> отдаёт чистый 404, а новая /job/<id> доходит до настоящего маршрута под защитой Cloudflare; на id.jobstreet.com верно обратное. Потерянные ссылки были реальными. Обнаружены два дефекта — оба в том, что добавил сам v1.237.0.
Исправлено
Подпись ссылки на книгу была переведена, а её цель — нет. Все 17 локалей SPA и все 17 локалей cvstart.org указывали ссылку в футере на Agentic Coding Design Patterns на английское издание, хотя единственная переведённая подпись — русская, «Паттерны агентного кодинга», — обещала русскую книгу. Новый применитель data-i18n-href (той же формы, что data-i18n-title, ограниченный абсолютными значениями https://) и ключ footer.patternsUrl теперь отправляют ru на русское издание, а любую другую локаль — на английское. Регрессионный тест проверяет инвариант в обе стороны: переведённый заголовок подразумевает локализованный URL.
SEEK переезжает на новые хосты, а транспорт отказывается следовать редиректам. www.seek.com.au → au.seek.com и www.seek.co.nz → nz.seek.com: страницы деталей уже отдают 301, и обе пары хостов сегодня обслуживают поисковый API v5 с кодом 200. Поскольку http-json.mjs задаёт redirect:'error' (защита от SSRF, которую стоит сохранить), в день, когда путь API начнёт редиректить, запись AU/NZ на старом хосте начала бы падать — а поскольку в allowlist были только хосты www., мигрировать было бы некому. Теперь au.seek.com и nz.seek.com в ALLOWED_JOBSTREET_HOSTS; старые хосты остаются, поэтому существующая конфигурация и уже собранные ссылки продолжают работать. Похожие на них хосты (au.seek.com.evil.com, au-seek.com) по-прежнему отклоняются.
Вики: строка Agentic Practices в карте документации на Home была единственной из двенадцати, не экранировавшей | внутри [[…|…]], из-за чего GitHub обрезал ячейку — мёртвая ссылка, скрытое описание. Один символ.
Примечания
Число источников не изменилось: 94 источника (89 EN + 5 RU), 89 адаптеров. Тесты 3201 → 3210 (+5 локаль ссылки в футере, +4 хосты SEEK).
Не закрыто извне, а зафиксировано без претензии на подтверждение: Oracle Cloud (Akamai заблокировал оба REST-зонда), закрепление Personio и appendWorkType (нужна запись в portals.yml, а в UI нет удаления для одной из них), и строковые кейсы liveness (нет управляемой страницы; 40 живых зондов дали 12 живых, 28 неопределённых, 0 ложных истечений).
Help ×17 теперь перечисляют обе формы хоста для AU-Main и NZ-Main. Структура заголовков не изменилась: 32 H2 / 122 H3.
[1.237.0] — 2026-09-21
Паритет с родителем — career-ops main @ 93c4302b (VERSION 1.33.0, 75 коммитов, взятых из career-ops-hq/main). Новых источников в этот раз нет: четыре зеркальных исправления, и два из них здесь незаметно теряли вакансии.
Исправлено
Jobstreet / SEEK — каждая австралийская, новозеландская, сингапурская, малайзийская и гонконгская вакансия вела на мёртвую страницу. parseJobstreetItem безусловно строил URL детали как ${baseUrl}/id/job/${id}. Сегмент /id/ — это не путь, а индонезийский языковой префикс, и он существует только на индонезийских хостах. Любой другой хост платформы SEEK отвечает 404 на /id/job/<id>. web-ui уже добавил www.seek.com.au, www.seek.co.nz, sg.jobstreet.com, my.jobstreet.com и hk.jobsdb.com в ALLOWED_JOBSTREET_HOSTS, поэтому скан спокойно возвращал эти вакансии — и каждая указывала на 404. Теперь путь определяется хостом: ID_LOCALE_HOSTS (id.jobstreet.com, www.jobstreet.co.id, jobstreet.co.id) сохраняет /id/job/, всё остальное получает /job/, а неразбираемый origin проваливается в общий путь вместо выброса исключения. Два собственных теста закодировали баг — они утверждали hk.jobsdb.com/id/job/… и www.jobstreet.com/id/job/… — и были исправлены.
Oracle Cloud — короткая страница в середине списка обрывала обход и отбрасывала хвост доски. Условие остановки было listLen === 0 || listLen < PAGE_SIZE, но ORC отдаёт меньше строк, чем лимит, в середине списка, поскольку часть строк отфильтровывается на сервере. Замерено выше по течению на American Express: TotalJobsCount 454, страницы по 200 / 199 / 54 — 199 читалось как конец, и последние 54 вакансии, 12% доски, так и не были получены. Теперь пустая страница всегда завершает обход; когда тенант сообщает итог, постраничный обход продолжается, пока offset + PAGE_SIZE >= total; только тенант без итога для сверки завершается на короткой странице. Это согласуется с более широким соглашением о том, что API «может возвращать меньше результатов, чем запрошено… даже если это не конец коллекции» (Google AIP-158). hasMore по-прежнему намеренно игнорируется — некоторые тенанты возвращают hasMore:false на каждой странице доски из 7000 вакансий. Раз короткие страницы больше не обрывают обход, MAX_PAGES теперь единственный барьер против тенанта, сообщающего фиктивный итог, поэтому он покрыт отдельным тестом.
Liveness — «This role is closed» не распознавалось, а ложное истечение — навсегда. Паттерн был /this job (listing )?is closed/i, который пропускает не менее частые формулировки role и position; на одной доске выше по течению было замерено 111 из 111 неопределённых вакансий со словом «role». Расширено до /this (?:job|role|position)(?: listing)? is closed\b(?!-)/i. Завершающий \b(?!-) — это защита от составных прилагательных: один \b отклоняет closedown, но всё ещё совпадает с реальным текстом вроде «This role is closed-loop control of the platform», и именно опережающая проверка это останавливает.
Liveness — job expired теперь в мягком уровне, который не может перебить видимую кнопку отклика. HARD_EXPIRED_PATTERNS проверяется до теста на элемент отклика, поэтому всё, что туда помещено, побеждает даже живую вакансию. /\bjob expired\b/i ложно срабатывает на четырёх реальных формах живой страницы — запись карусели «Similar jobs», чип фильтра «Hide job expired», футер FAQ и вышеупомянутый текст про closed-loop — потому что классификатор получает текст всей страницы. Теперь он живёт в SOFT_EXPIRED_PATTERNS, проверяется после элемента отклика и до эвристики страницы списка, возвращая expired_body_soft; голый баннер «JOB EXPIRED» без элемента отклика по-прежнему считается истёкшим. Это направление важно, потому что ложный expired записывается в историю сканирования как skipped_expired и затем дедуп-фильтр исключает настоящую вакансию из каждого последующего скана, бессрочно, если не задан scan_history.recheck_after_days.
Добавлено
Personio — явный personio: <slug> закрепляет тенанта. Многие компании встраивают свою доску Personio как iframe на брендированной странице карьеры, поэтому careers_url указывает на собственный домен компании, а лента живёт на <slug>.jobs.personio.de; без закрепления такие доски не разрешаются ни во что. Слаг ограничен по набору символов заякоренным allowlist (/^[a-z0-9][a-z0-9-]{0,62}$/i), а получившийся URL всё равно проходит через существующий разбор new URL(), проверку HTTPS и хост-фильтр PERSONIO_HOST_RE — закрепление никогда не становится способом дотянуться до другого хоста. Слаг с ., /, @, :, .., пробелом, ведущим - или превышающий лимит DNS-метки в 63 символа проваливается в сегодняшнее поведение api/careers_url. В отличие от родителя, хост-регулярка web-ui регистрозависима, поэтому закрепление нормализуется через new URL() — AcmeGroup разрешается в acmegroup.jobs.personio.de, а не строит хост, который его же allowlist затем отклонил бы.
Jobstreet — опциональный appendWorkType добавляет к заголовку типы занятости вакансии, например Strategy Consultant [Part time], чтобы фильтры по заголовку и триаж видели тип занятости уже на этапе скана. По умолчанию выключено; отсутствующий или пустой workTypes не меняет ничего.
Примечания
Число источников и адаптеров не изменилось: 94 источника (89 EN + 5 RU), 89 EN-адаптеров. Этот релиз меняет только число тестов, 3164 → 3201 (+13 jobstreet, +7 Oracle Cloud, +9 liveness, +8 Personio).
Родитель удалил providers/telegram.mjs, консолидировавшись на telegram-channel. web-ui регистрирует оба как пользовательские источники, поэтому удаление не переносится: оно уменьшило бы реестр с 94 до 93 и тихо сломало бы всех, у кого настроен telegram. Возвращаться к этому только как к осознанному, объявленному удалению.
jd-skill-gap.mjs получил нераспознанную ранее форму заголовка «what we are looking for» без сокращений и удаление жирных маркеров. web-ui ретранслирует этот скрипт, а не зеркалирует его, поэтому исправление приходит вместе с задеплоенным родителем и не требует изменений здесь.
Не перенесено, с указанием причин: lib/latex-escape.mjs, browser-extract.mjs, openai-eval.mjs, update-system.mjs и скаффолдер (web-ui их не зеркалирует и не оборачивает шеллом); TUI Go-дашборда и форма конфигурации web/ (отдельные поверхности); doctor.mjs и analyze-patterns.mjs (ретрансляторы только для чтения, чей fail-soft поглощает изменение).
Кириллическое расхождение форка \p{L} в providers/telegram-channel.mjs пережило слияние с апстримом в неизменном виде и было сверено побайтово с копией до слияния.
career-ops-ui v1.267.1 · page 3/13 · mirror of the project changelog for cvstart.org