docs(hotfix): финал 7/7 + root cause ssh/claude + file retention policy
Some checks are pending
Auto-tag / Create date-tag (push) Waiting to run
Some checks are pending
Auto-tag / Create date-tag (push) Waiting to run
Второй проход после ручного ретеста владельца: добавил RemoteHost ssh на dev-vm, объяснил почему e2e п.4 не поймал (не запустил), и описал owner-policy про удаление файлов + cleanup legacy. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
0ff93b6daa
commit
7745d018af
|
|
@ -147,4 +147,75 @@ E2E_ADMIN_URL=https://test.admin.food-market.kz pnpm exec playwright test \
|
||||||
|
|
||||||
## 5. Stage retest + итог
|
## 5. Stage retest + итог
|
||||||
|
|
||||||
_(заполняется)_
|
### Второй проход: coord-уточнение после ручного ретеста владельца
|
||||||
|
|
||||||
|
Владелец после первого фикса (п.1) загружал настоящее фото и получил:
|
||||||
|
|
||||||
|
```
|
||||||
|
Ошибка распознавания:
|
||||||
|
An error occurred trying to start process 'claude' with working directory '/app'.
|
||||||
|
No such file or directory
|
||||||
|
```
|
||||||
|
|
||||||
|
**Root cause.** В контейнере `food-market-stage-api-1` CLI `claude` НЕ установлен; extractor имел ветку `RemoteHost` по ssh (из `~/ref/sklad`), но env-переменная не была задана → ветка не бралась, экстрактор пытался запустить локальный `claude`.
|
||||||
|
|
||||||
|
**Почему мой e2e п.4 это не поймал.** Я добавил сам spec, но запустил только `menu-walk` в разделе 5-первом проходе. `stage-ui-invoice-upload.spec.ts` не был прогнан до этого фикса. Сам spec ДЕЙСТВИТЕЛЬНО ловит эту ошибку — текст `"Распознавание упало в failed: An error occurred trying to start process 'claude'..."` появляется в assert'е. Урок: регресс-проверка существует, если её запустили.
|
||||||
|
|
||||||
|
**Фикс (коммит `0ff93b6`).**
|
||||||
|
|
||||||
|
1. `ClaudeCliInvoiceExtractor.cs` — передаёт в `ssh`/`scp` опции `-i <SshKey>`, `-o StrictHostKeyChecking=accept-new`, `-o UserKnownHostsFile=<SshKnownHosts>`, `-o BatchMode=yes`, `-o ConnectTimeout=10`. Два новых конфига: `Invoices:ClaudeCli:SshKey`, `Invoices:ClaudeCli:SshKnownHosts` (default `/tmp/.fm-ssh/known_hosts` — writable путь, т.к. `/root/.ssh` в контейнере read-only).
|
||||||
|
2. `deploy/Dockerfile.api` — `+openssh-client` в runtime stage.
|
||||||
|
3. На prod-vm (192.168.1.190): сгенерирован ed25519-ключ `~/food-market-stage/deploy/secrets/invoice-ocr-key[.pub]` (не в репо; `secrets/` под `./deploy/`).
|
||||||
|
4. На dev-vm (192.168.1.192, где установлен и залогинен `claude`): `~/.ssh/authorized_keys` + строка `from="192.168.1.190",no-agent-forwarding,no-port-forwarding,no-X11-forwarding,no-pty <pub>` — ограниченный доступ только с prod-vm.
|
||||||
|
5. `docker-compose.yml` на prod-vm (не в репо):
|
||||||
|
```yaml
|
||||||
|
environment:
|
||||||
|
Invoices__ClaudeCli__RemoteHost: ${INVOICE_REMOTE_HOST:-nns@192.168.1.192}
|
||||||
|
Invoices__ClaudeCli__Executable: /home/nns/.local/bin/claude
|
||||||
|
Invoices__ClaudeCli__SshKey: /app/.ssh/invoice-ocr-key
|
||||||
|
Invoices__ClaudeCli__SshKnownHosts: /tmp/.fm-ssh/known_hosts
|
||||||
|
volumes:
|
||||||
|
- ./secrets/invoice-ocr-key:/app/.ssh/invoice-ocr-key:ro
|
||||||
|
```
|
||||||
|
`Executable` — полный путь, т.к. non-interactive non-login ssh не наследует `~/.profile`.
|
||||||
|
|
||||||
|
**Прогон на stage (после фикса):**
|
||||||
|
|
||||||
|
```
|
||||||
|
Running 4 tests using 1 worker
|
||||||
|
[invoice-upload] id=f4d9e256-dacf-4a47-b454-b79ad9538ab5 initial status=pending
|
||||||
|
[invoice-upload] final status=needs_review err=-
|
||||||
|
✓ 1 ... invoice upload full-cycle — demo-keeper … (31.6s)
|
||||||
|
✓ 2 ... menu-walk … demo-admin — reload каждого пункта меню, 0 ошибок (1.9m)
|
||||||
|
✓ 3 ... menu-walk … demo-keeper — reload каждого пункта меню, 0 ошибок (1.1m)
|
||||||
|
✓ 4 ... menu-walk … demo-cashier — reload каждого пункта меню, 0 ошибок (29.2s)
|
||||||
|
4 passed (4.1m)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Файловая политика (owner decision 2026-10-05)
|
||||||
|
|
||||||
|
Фото/сканы накладных на сервере **хранить запрещено**. После распознавания (успех + ошибка) файл удаляется.
|
||||||
|
|
||||||
|
**Что сделано:**
|
||||||
|
|
||||||
|
- `InvoiceProcessor.ProcessAsync` — `DeleteStored(up)` сразу после `ExtractAsync` (успех) и в catch (ошибка). Хелпер затирает `up.StoredPath = ""`, безопасно к гонкам и падениям FS.
|
||||||
|
- `ClaudeCliInvoiceExtractor.ExtractAsync` — `rm -f` временной копии на RemoteHost в `finally` (как и было, подтверждено).
|
||||||
|
- `InvoiceWorker` — удалён периодический `SweepAsync` + поле `_lastSweep`. Конфиг `Invoices:KeepFilesDays` больше не читается; оставлен как no-op с комментарием в XML-доке.
|
||||||
|
- На stage prod-vm `/opt/food-market-data/uploads/invoices` — все накопленные legacy-файлы удалены вручную (4 шт от 18:57–19:36, до фикса); `invoice_uploads.StoredPath` обнулён для всех записей с terminal-статусом.
|
||||||
|
|
||||||
|
**Верификация:** после двукратного e2e-прогона в `/opt/food-market-data/uploads/invoices` — 0 файлов, в БД — 0 записей с непустым `StoredPath`.
|
||||||
|
|
||||||
|
### Итог 7/7
|
||||||
|
|
||||||
|
| # | Пункт | Статус |
|
||||||
|
|---|---|---|
|
||||||
|
| 1 | 500 на upload (UpdatedAt NOT NULL) | ✅ fix + integration-test на реальной Postgres |
|
||||||
|
| 2 | Фоновые 403 (presets, employees, telegram) | ✅ 3 места у источника + silent-GET-403 политика |
|
||||||
|
| 3 | menu-reload regression e2e | ✅ в quality-watchdog (step 9) + 3/3 green на stage |
|
||||||
|
| 4 | Real invoice upload e2e | ✅ pending → needs_review на stage |
|
||||||
|
| 5 | deploy-stage + retest + итог | ✅ 4/4 e2e green |
|
||||||
|
| 6 | Claude CLI via ssh на dev-vm | ✅ RemoteHost config + ключ смонтирован ro |
|
||||||
|
| 7 | Не хранить фото накладных | ✅ delete-on-done + sweep удалён + legacy cleanup |
|
||||||
|
|
||||||
|
Прод не трогал, global.json не трогал, nginx gateway не трогал.
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue