The intern (and gis-api-sourced) rows showed an empty "jud." with no UAT name
or county, and a few dead cancelled/test rows cluttered the list.
- gis-api returns siruta + uatName but judetName is null there, and the
CfExtractRow type didn't even declare those fields so adaptCfRow blanked
them. Added the fields to the type; adaptCfRow now surfaces uatName + siruta.
- New enrichCfLocations(rows) fills missing uatName/judetName from SIRUTA via
the local GisUat table (batched, one query). Applied in both list proxies
(/api/cf/orders for gis rows, /api/ancpi/orders for old legacy intern rows
whose judetName was stored empty). So intern rows now read "LOCALITATE,
jud. X".
- Hide status='cancelled' rows from the Extrase CF list (dead — payment
refused / cleaned-up bad orders, e.g. the old 354686 test). failed/review
stay (actionable via Reincearca).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Live-path hardening from the 2026-06-04 deep-dive (11 confirmed criticals).
ArchiTools-only; the legacy queue is still the sole fulfiller.
Security:
- requireCfAccess() — staff-only, portal accounts blocked, fail-closed
in-route on download / download-zip / cf-status / orders (C4 IDOR/PII)
and order / recover (C3). order also enforces a daily credit cap
(ANCPI_DAILY_CREDIT_CAP, default 200) and stamps userId.
- /api/ancpi/test returns 404 in production — it was a GET that spends 2
real credits, CSRF-able (C5).
- drop the token-metadata debug blob from the session (QW8).
Correctness / robustness:
- cart hygiene (C1): build the ePay cart under an invariant — the Nth add
must report N items; any excess = pre-existing junk, so we wipe + abort
(never submit a cart we didn't fully build). Pre-submit failures clean
up our basket rows; post-submit we never touch the cart (recover owns
it). metadata-less rows are deleted from the cart.
- getOrderStatus fetches the whole order in ONE page (itemsPerPage, QW4);
navDir loop kept only as fallback. index-fallback matches are flagged
'review' instead of silently 'completed' with a possibly-wrong PDF (R4).
- downloadDocument asserts %PDF magic bytes — a login page returned mid
session no longer gets stored as a .pdf (R2). Session reuse TTL aligned
under ANCPI's ~10min expiry.
- recover accepts ?extractId= and pre-submit states; retry buttons in the
ePay tab re-run poll+download with no new charge (QW2/QW3).
Performance:
- parallel document downloads (V1, concurrency 4); poll writes only on
status change via updateMany (QW5); getNextFileIndex scans the cadastral
prefix instead of the whole bucket — and actually works now (it was
^-anchoring the full key, so every file got index 1) (V2); download-zip
streams instead of buffering the whole archive, capped at 100 (V3).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Foundation (Phase 1):
- CfExtract Prisma model with version tracking, expiry, MinIO path
- epay-types.ts: all ePay API response types
- epay-counties.ts: WORKSPACE_ID → ePay county index mapping (42 counties)
- epay-storage.ts: MinIO helpers (bucket, naming, upload, download)
- docker-compose.yml: ANCPI env vars
ePay Client (Phase 2):
- epay-client.ts: full HTTP client (login, credits, cart, search estate,
submit order, poll status, download PDF) with cookie jar + auto-relogin
- epay-session-store.ts: separate session from eTerra
Queue + API (Phase 3):
- epay-queue.ts: sequential FIFO queue (global cart constraint),
10-step workflow per order with DB status updates at each step
- POST /api/ancpi/session: connect/disconnect
- POST /api/ancpi/order: create single or bulk orders
- GET /api/ancpi/orders: list all extracts
- GET /api/ancpi/credits: live credit balance
- GET /api/ancpi/download: stream PDF from MinIO
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>