Срез 0: скелет scan-gateway — без фронта и БД, горячие папки local/smb, воркер NATS без JetStream, эмулятор ИС Сканирование #3
Loading…
x
Reference in New Issue
Block a user
No description provided.
Delete Branch "feat/1-slice-0-skeleton"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Issue #1 — реализация change
slice-0-skeleton(48 коммитов после merge proposal #2). Не закрывает issue: закрытие — после применения compose-фрагмента в docs (см. ниже),openspec archiveи команды «закрывай».Что сделано
web/, Alembic,db/,models/,repositories/,raisa_*; SMB оставлен (smbprotocol, сессия только приSCAN_HOTFOLDER_MODE=smb);README.mdиCLAUDE.mdпереписаны.api/pyproject.toml(ruff, mypy),Makefile(venv,lint,test,test-integration,check,check-full,up,emulator),Dockerfile(python:3.11-slim) и.dockerignore,.env.example/.env.test.api/contracts/(побайтово равны../docs/contracts), pydantic-моделиsrc/contract/, тесты.HotFolder,LocalHotFolder(dev, тесты) иSmbHotFolder(smbclient, прод); фабрикаcreate_hotfolder; имя файла<job_id>_<original_name>, запись атомарна (.part+ rename).scan-gateway; ackprocessingдо чтения S3; LRUjob_id; семафорSCAN_MAX_INFLIGHT=4; опрос ответа каждые 5 с доmin(reply_deadline, старт + 300 с); сырой ответ в S3raw/{job_id}.jsonдо нормализации; результат и конверт с повторами публикации; транспортные ошибки всегда становятся результатом (SCHEMA_MISMATCH,FILE_READ_ERROR,RECOGNITION_FAILED,INVALID_FORMAT,TIMEOUT,UNKNOWN_ERROR); корректная остановка;/healthcheckс полямиnatsиhotfolder.NullNormalizerпробрасывает ошибки ИС, успех →NOT_IMPLEMENTED.python -m src.scan_emulator; фикстуры../fixtures/ocr/<name>*.json, подстановкаfile_uid, неизвестное имя →INVALID_DOCUMENT,.doneпосле обработки.Проверки
make check-fullна HEAD, exit 0: ruff, format, mypy чисто; 636 unit + 8 integration passed (реальные NATS и MinIO);docker buildбез Nexus (30 с холодная сборка). Живой прогон:/healthcheck200 (nats ok,hotfolder ok); запрос дляaep_5.pdf→ ackprocessing→ результатINVALID_DOCUMENTсraw_s3_key; объектraw/<job_id>.jsonв MinIO,.doneв горячей папке; SIGTERM — штатное завершение.openspec validate --strict— valid.Rulings I made
Полный список —
openspec/changes/slice-0-skeleton/design.md, таблица «Decisions made without discussion» (№10–53). Ключевые: R-7 интеграционный тест запускает шлюз и эмулятор в процессе теста (контейнераscan-emulatorв compose ещё нет) — рискованный, отсюда C-1 (эмулятор не стартовал без настроек шлюза, исправлено); R-8argon2-cffiостаётся (транзитивminiopy-async); R-10 успех проверяется на синтетическом ответе: обе реальные фикстурыaep_5/aep_8_form4— сами ответы-ошибки ИС; R-13PUBLISH_RETRIES=3= 1 попытка + 3 повтора; R-16SCHEMA_MISMATCHбез ack (согласовано с tabular-parser); R-17 дедуп поjob_id(LRU); R-19request.s3.bucket≠ настроенного →FILE_READ_ERROR; R-20 дедлайн считается от записи файла.Deferred (в follow-up issue при закрытии)
I-3 (replay с
attempt+1дляjob_idиз LRU получает только ack — решение человека), T12.5 (штатная остановка не публикует конверты отменённых заданий: деплой превращает задания в работе вtimeout), зависшийpoll/putпо SMB до 60 с (нуженconnection_timeout),json.loadsответа ИС в event loop,NATS_URLв логах,S3Storage.get_bytesбез лимита размера, устаревший индекс codegraph и ещё ~60 minor по ревью задач (полный перечень в отчёте контроллера, ключевые — вopen-questions-proposal.md).Что нужно решить человеку
docs(профильservices);openspec archiveэтого change — только после применения фрагмента и проверкиdocker compose --profile services up -d --wait scan-gateway scan-emulator.openspec/changes/slice-0-skeleton/open-questions-proposal.md.scan-gateway: не запускать одновременно.Связанные
RSHB/arm-ka-docs(контракт v1.0, compose; PR после фазы 2) — тест идентичности контракта читает../docs, docs должен влиться раньше или одновременно.RSHB/arm-corePR #3,RSHB/arm-tabular-parserPR #3.🤖 Generated with Claude Code
https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
on_request проверяет тело (SCHEMA_MISMATCH без ack, R-16), дедуплицирует по job_id (задания в работе + LRU DONE_CACHE_SIZE, R-17) и запускает задачу: ack до чтения S3 и до ожидания слота, семафор SCAN_MAX_INFLIGHT, handle. handle: S3 (один бакет, R-19) -> горячая папка через asyncio.to_thread (R-15) -> опрос до min(reply_deadline, старт + SCAN_TIMEOUT_SECONDS) (R-14, R-20) -> raw/{job_id}.json до нормализации (R-18) -> нормализатор -> results/{file_id}/{job_id}/result.json -> конверт. Таблица ошибок D-7, публикация через NatsGateway с повторами, PublishFailed логируется. Текст ошибки очищается от одиночных суррогатов и ограничен 4000 символами (T10.1, T10.2). Тесты: фейки S3/NATS с общим журналом событий, настоящий LocalHotFolder и process_once эмулятора; каждый конверт проверяется JSON Schema контракта. Фикстура log_lines в conftest перехватывает loguru. src.core.worker и src.core.health — в строгом mypy. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>- Исправлена критическая ошибка: NullNormalizer считал ответ ошибкой при `error is not None`, а у реального успеха ИС error = {"code": "", "message": ""} — aep_3.json и aep_6.json давали UNKNOWN_ERROR. Успех файла теперь — error.code == "" (отсутствующий/null/не-объектный error и error без кода ошибкой не считаются); регрессия — на реальных фикстурах (маркер fixtures). - src/gateway/merge.py: merge_answers(job_id, files) -> MergedResponse {"job_id", "files": [{"name", "response", "parse_error"}]} по возрастанию имени; не JSON — parse_error файла; dump_merged — UTF-8 (с ASCII-экранированием при одиночном суррогате). - Воркер: опрос отдаёт готовый набор → склейка → raw/{job_id}.json (application/json) до нормализации → Normalizer.normalize(склейка). Перечитывание «не JSON» убрано (R-24). - NullNormalizer: все файлы ошибочны — код/сообщение первого по имени (parse_error и не-объект — INVALID_FORMAT, код вне контракта — UNKNOWN_ERROR с исходным кодом); есть успешный — NOT_IMPLEMENTED; ошибочные файлы — строки `<имя>: <код ИС>: <сообщение>`. - NormalizeOutcome.warnings (R-25) → metrics.warnings конверта и файла результата; пустые опускаются; не больше 100 строк по 1000 символов, лишние — сводной строкой (R-30). - Тесты: test_merge.py, test_normalizer.py переписан под склейку, test_worker.py — склейка в raw, ошибка одного файла, parse_error в warnings, все ошибочны, реальные aep_3+aep_3_ofr и aep_6, settle и таймаут ненастоявшегося набора; SUCCESS_ANSWER — в форме реального успеха. Интеграционный test_roundtrip.py минимально переведён на формат склейки (прогон — Task 5). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>Раскладка Output/<job_id>/*.json (склеиваются все файлы), SCAN_TMP_DIR, SCAN_SETTLE_SECONDS, структура raw/{job_id}.json, успех = error.code == "", ошибочные файлы в metrics.warnings, правило набора эмулятора. D-5: не строковый error.code тоже ошибка (UNKNOWN_ERROR, R-32). Решения R-36 (design #76). Тест TestAnswerSet на реальных фикстурах помечен fixtures. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>Доработка по ответам владельца ИС (04.10.2026), +18 коммитов в этой ветке
Решения человека: OQ-78 — ИС кладёт все распознанные документы одновременно в каталог
<job_id>, шлюз читает все json и склеивает; OQ-79 — временный файл записи уходит изInputв служебный каталог; OQ-76/77 — добавлены реальные образцыaep_3(две json: успех баланс+ОФР иINVALID_DOCUMENTпо ОФР) иaep_6(успех), PR в fixtures: RSHB/arm-ka-fixtures#1.Что изменено:
pollвозвращает все*.jsonкаталога<job_id>; готовность по стабилизации набора (SCAN_SETTLE_SECONDS=2); временный файл —Tmp/(SCAN_TMP_DIR), вInputпопадает только итоговый файл.raw/{job_id}.json={"job_id", "files":[{name, response, parse_error}]}; ошибка одного файла не отменяет остальные (metrics.warnings); контракт v1.0 не менялся.error = {"code": "", "message": ""}, а неnull; прежняя проверка «errorне null» принимала настоящий успех за ошибку. Регрессионные тесты на реальныхaep_3,aep_6,aep_5,aep_3_ofr,aep_8_form4(маркерfixtures).<name>.jsonи<name>_*.json, атомарно целиком);aep_3не забираетaep_30*.Проверка:
make check-fullexit 0 (745 unit + 10 integration,docker build). Реальный compose (профильservices):aep_3.pdf→error NOT_IMPLEMENTED+ warning про<job_id>_ofr.json;aep_6.pdf→NOT_IMPLEMENTEDбез warnings;aep_5.PDF→INVALID_DOCUMENT;raw/в MinIO побайтно совпадает с фикстурами (md5),Tmpпуст,.partвоInputнет.Новые решения без обсуждения (design №54–80): структура склейки, готовность по settle (отсчёт от первого опроса, итог не позднее
SETTLE + POLL),metrics.warningsдо 100 строк, ответ эмулятора «нет пары» как реальные ответы-ошибки, атомарная публикация набора эмулятором, при сбоях чтения файла ответа код итога остаётсяTIMEOUTс числом ошибок в тексте.Deferred (в follow-up issue): T1.12, T2.3 (на реальной SMB-шаре сверять
smbclient.stat().st_size), T2.5, T3.2, T3.4, T3.5, T4.2–T4.5, T5.2, T5.4, T6.5, T6.7, O-3, O-7.