Files
ArchiTools/docs/plans/002-architools-thin-client-review.md
T
Claude VM 28c870fb12 harden(epay): cart-hygiene invariant uses confirmed cart count + add service architecture plan
- cartCount tracks actual cart rows (decrement only on confirmed delete) so a
  failed cleanup delete can't trigger a false dirty-cart abort.
- docs/plans/006: the multi-tenant CF-service architecture (DB-backed
  fulfiller, account pool, catalog dedup, per-tenant credential model,
  reversible flag flip) — the executable next phase. The Phase-F flag flip is
  gated on the orchestrator fulfiller existing (Plan 003 Faza F was wrong).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 00:06:06 +03:00

179 lines
12 KiB
Markdown

# Plan 002 — ArchiTools thin-client migration: review feedback from eterra.live side
**Date:** 2026-05-17
**Reviewer:** Claude session in `/home/orchestrator/Code/eterra-live` (Marius)
**Context:** Reviews the "Faza 0..7" plan proposed by the architools claude that routes architools enrichment through eterra.live.
---
## TL;DR — change of direction
The original plan made **eterra.live** the gateway for architools/planhub enrichment via `POST /api/internal/enrich`. **This is the wrong boundary.** The correct gateway is **`api.gis.ac`** (the `gis-api` service on shop), which is already designed for cross-app consumption (JWT + RLS + scope filtering, Hono on `10.10.10.84:3100`, fronted by Traefik).
eterra.live is a single-instance Next.js end-user product. Putting architools' geoportal behind it makes eterra.live a SPOF — any deploy/restart breaks architools cadastre. The right model treats both architools AND eterra.live as **peer consumers** of api.gis.ac.
eterra.live already migrated to api.gis.ac for `CfExtract` (Sprint 2 Day 3-4, 2026-04-20). Same migration pattern applies to architools, just bigger surface area.
---
## What's already in place (don't reinvent)
`api.gis.ac` exists in production:
- Repo: `gitadmin/gis-api`, local clone `~/Code/gis-api`
- Stack: Next.js 16.1 + Hono 4 + Prisma 6.19 + Zod 4 + jose 6
- Auth: HS256 JWT today (`GIS_API_DEV_JWT_SECRET`) — **migrating to Authentik OIDC before architools cutover** (decision locked 2026-05-17)
- Claims: `{ sub, org_ids[], is_beletage_group, enrichment_scope: "none"|"basic"|"full", email? }``tenant` claim being added
- RLS: `withUserContext()` sets `app.user_id / org_ids / is_beletage_group / enrichment_scope` per Postgres tx
- Live endpoints (under `/api/v1/`):
- `GET /me` — claims echo
- `GET /parcela/:id` — full `GisFeature` read
- `GET /search?q=&limit=` — UAT + cadastralRef text search
- `GET/POST/PATCH /enrichment/cf*` — CfExtract CRUD with RLS
- `GET /enrichment/catalog/:nrCadastral` — catalog metadata
- `GET/POST /enrichment/cf/:id/pdf` — MinIO PDF stream
- RLS test suite: 6 scenarios, asserts BOTH HTTP and direct-SQL paths (CI: `.gitea/workflows/rls-test.yml`)
What it does NOT have yet (gaps to close before architools cutover):
1. **Authentik OIDC** (HS256 still in use)
2. **Live-fetch proxy endpoints** to orchestrator (parcel/tech, parcel/units, building/tech, building/condo-owners, imm-apps)
3. **Scope-based field filtering** on `/parcela/:id` (currently leaks `enrichment.PROPRIETARI` regardless of scope)
4. **`tenant` claim** in JWT
5. **CORS allowlist** for cross-origin app calls
6. **Per-tenant rate limiting**
7. **Audit log table** (`gis_meta.api_audit`)
These will be added in a dedicated gis-api session before architools starts consuming. Full spec is in gis-api project memory (`~/.claude/projects/-home-orchestrator-Code-gis-api/memory/`):
- `project_architools_planhub_plan.md` — endpoints to add, in priority order
- `feedback_jwt_authentik_decision.md` — Authentik OIDC spec (scope mapping from LDAP groups)
- `reference_orchestrator_endpoints.md` — exact contracts for the 5 LAN endpoints to proxy
- `reference_cross_app_architecture.md` — the diagram + consumer responsibility matrix
---
## Revised plan (replaces architools claude's Faza 0..7)
### Faza A — Wait for gis-api gaps to close (managed in gis-api repo, ~3 days)
Driven from `~/Code/gis-api` in a separate session. NOT architools' work to do, but architools depends on it. Deliverables:
1. **PR1 (gis-api):** Authentik OIDC support alongside HS256 (`src/lib/auth.ts` issuer-based dual path). Add `tenant` claim. Eterra.live keeps working on HS256 during the overlap. Provisioning needed in Authentik:
- New OAuth2 application + provider with slug `gis-api`
- Property mappings for `enrichment_scope` derived from LDAP groups:
- `beletage-staff``enrichment_scope=full`, `is_beletage_group=true`
- `planhub-pro``full`
- `planhub-free` / no match → `basic`
- missing claim → REJECT
- `tenant` claim from `aud` (application slug)
2. **PR2 (gis-api):** 5 proxy endpoints + scope filter on `/parcela/:id` + CORS allowlist:
```
POST /api/v1/parcel/tech → orchestrator POST /api/v1/parcel/tech
POST /api/v1/parcel/units/fetch → orchestrator POST /api/v1/parcel-units/fetch
POST /api/v1/parcel/imm-apps → orchestrator POST /api/v1/imm-apps
POST /api/v1/building/tech → orchestrator POST /api/v1/building/tech
POST /api/v1/building/condo-owners → orchestrator POST /api/v1/building/condo-owners
```
All require `enrichment_scope >= basic`. Rewrite `correlationId` server-side to `${tenant}:${user_sub[:8]}:${requestId}`.
3. **PR3 (gis-api):** Per-tenant rate limit + `gis_meta.api_audit` migration.
When PR1+PR2 are green and deployed, signal back to this plan and proceed with Faza B.
### Faza B — ArchiTools secrets + Authentik client (½ day)
- In Authentik: create OAuth2 client for architools (slug `architools-app`). Configure redirect URI for its NextAuth flow.
- Infisical adds (prod env, path `/`):
- `AUTHENTIK_CLIENT_ID` / `AUTHENTIK_CLIENT_SECRET` / `AUTHENTIK_ISSUER` / `AUTHENTIK_JWKS_URL` for architools
- `GIS_API_URL=https://api.gis.ac`
- `NEXT_PUBLIC_MARTIN_URL=https://tiles.gis.ac`
- `NEXT_PUBLIC_PMTILES_URL=https://pmtiles.gis.ac/overview.pmtiles`
- NextAuth on architools: add Authentik OIDC provider. Store `access_token` in session. ⚠️ DO NOT mint HS256 JWTs server-side anymore — pass the Authentik `access_token` directly as Bearer to gis-api.
### Faza C — Rip-out (1 day, ONE PR, feature-flagged)
`NEXT_PUBLIC_USE_GIS_AC=1` flag controls cutover. With flag on:
- Geoportal map sources flip to `pmtiles.gis.ac` + `tiles.gis.ac`
- All `src/app/api/eterra/*` and `src/app/api/ancpi/*` route handlers replaced by thin wrappers that forward to api.gis.ac with the user's access_token
- All `src/modules/parcel-sync/**` and parcel-sync UI removed
- `src/config/{modules,navigation,flags}.ts` cleanup
With flag off: old behavior retained. **Do not drop Prisma tables (GisFeature, GisUat, GisSyncRun, GisSyncRule, CfExtract) in this PR.** Keep them as dead columns for 2-3 days post-cutover so rollback stays cheap. Drop in a follow-up PR after validation in prod.
Stop `martin` container on satra ONLY after flag-on is verified working in prod. The PMTiles webhook on satra:9876 — **does not exist**; the architools claude's plan had this wrong. The actual PMTiles trigger is the orchestrator cron at 03:00 EEST + admin button in eterra.live, hitting `gis-tippecanoe-builder` on **shop** at port 9876.
### Faza D — Thin client (1-2 days)
- `src/lib/gis-api-client.ts` — fetch wrapper with the Authentik access_token from session
- `gisApi.parcela.get(id)` → `GET /api/v1/parcela/:id`
- `gisApi.search(q, limit)` → `GET /api/v1/search`
- `gisApi.parcel.tech({siruta, cadastralRef, force})` → `POST /api/v1/parcel/tech`
- `gisApi.parcel.unitsFetch(...)` → `POST /api/v1/parcel/units/fetch`
- `gisApi.parcel.immApps(...)` → `POST /api/v1/parcel/imm-apps`
- `gisApi.building.tech(...)` → `POST /api/v1/building/tech`
- `gisApi.building.condoOwners(...)` → `POST /api/v1/building/condo-owners`
- `gisApi.enrichment.cf.list/get/create/patch/uploadPdf/getPdf/getCatalog` (mirror eterra.live's existing usage)
- NO separate `scope-mapper.ts` — gis-api resolves scope from Authentik claims itself.
- NO separate `gis-api-token.ts` — Authentik access_token is the JWT, NextAuth session already has it.
### Faza E — Geoportal rewrite (2 days)
- `map-viewer.tsx`: sources `pmtiles://pmtiles.gis.ac/overview.pmtiles` + Martin `tiles.gis.ac/{view}/{z}/{x}/{y}`. Drop satra Martin entirely.
- `search-bar.tsx` → `gisApi.search()`.
- `feature-info-panel.tsx` → `gisApi.parcela.get()` + (on missing enrichment) `gisApi.parcel.tech()` + `gisApi.parcel.unitsFetch()` for buildings.
- `basemap-switcher.tsx`: same config as eterra.live (liberty/dark/satellite/orto/topo50). Source files are public CSS/tile URLs; literal copy is OK.
- Decision for `boundary-check`, `cf-status`, `export`, `pad`, `piz`: if architools keeps them, rewrite as thin proxies to api.gis.ac equivalents (which may need new endpoints — defer that scoping until each is actually needed).
### Faza F — ePay / CF ordering (½ day)
- Architools UI for "Comandă extras CF" calls `gisApi.enrichment.cf.create({nrCadastral, type, ...})` directly.
- Status + download list: `gisApi.enrichment.cf.list({...})` and `getPdf(id)`.
- The `tenant=architools` claim in the access_token tells gis-api which billing account to attribute the order to (future-work for billing routing, but the field is in place from Faza A).
- Delete `src/app/api/ancpi/*` entirely.
### Faza G — Test E2E + deploy (½ day)
- `/geoportal` in architools renders tiles from gis.ac, click parcel → enrichment via api.gis.ac → live-fetch fallback triggers orchestrator.
- Verify zero references to `architools_postgres.GisFeature` remain in code AND that no DB query goes to the deprecated tables.
- Type-check + lint + build clean.
- Commit + push + Portainer redeploy.
- 24h grace period: old stack (Martin satra, deprecated parcel-sync code) kept around. After validation, follow-up PR drops Prisma tables + stops martin satra container.
### Faza H — Planhub (separate PR, post-architools)
- Same pattern. Different scope mapping: `planhub-free` → `basic`, `planhub-pro` → `full`.
- Already covered by gis-api's tenant claim from Faza A.
---
## Specific corrections to the original plan
1. **`POST /api/internal/enrich` on eterra.live** — REMOVE. Use `api.gis.ac` directly.
2. **Bearer `$ARCHITOOLS_INTERNAL_TOKEN` static token** — REMOVE. Use Authentik OIDC access_token.
3. **`GIS_API_JWT_SECRET (shared)` in Infisical** — REMOVE. With Authentik OIDC, gis-api fetches JWKS from `https://auth.beletage.ro/application/o/gis-api/jwks/` — no shared secrets between apps.
4. **`scope-mapper.ts` in architools** — REMOVE. Scope is in the JWT claims; architools doesn't compute it.
5. **PMTiles webhook satra:9876** — does not exist. The builder is `gis-tippecanoe-builder` on **shop:9876**, fired by orchestrator cron + eterra.live admin button.
6. **Single-PR rip-out** — replace with feature-flagged migration (`NEXT_PUBLIC_USE_GIS_AC=1`), 2-3 day overlap, drop dead code in a separate follow-up PR after prod validation.
7. **eterralive-coordinator** terminology — drop. The pool is "owned" by `gis-sync-orchestrator` (LAN-only on shop:3101). gis-api is the auth/scope/audit gateway in front of it.
---
## Open questions
1. **Authentik OIDC client provisioning** — needs to be done first; until then, no architools migration. Marius or a sys-admin session needs to set up the OAuth2 provider in Authentik UI.
2. **Audit table schema** — does `gis_meta` already have an `api_audit` table? Probably no. Migration needed in same PR as proxy endpoints.
3. **Rate-limit defaults** — what's the cap per tenant? 100 req/min seems safe; configurable per tenant via Infisical JSON env. Confirm with Marius.
4. **Architools' `archi.*` schema writes** — currently architools writes Canvas, Job, RegistryAudit, RgiSearchTemplate directly via its own Prisma. Migration here is OPTIONAL for the cadastre work; could be done in a later sprint. Don't bundle it.
---
## Reference docs
- gis-api project memory: `~/.claude/projects/-home-orchestrator-Code-gis-api/memory/MEMORY.md`
- Plan 001 v2 master: `~/Code/ArchiTools/docs/plans/001-v2-central-gis-db-multi-app.md`
- Tile data sources / cache / gis.ac rule: `~/Code/busc-infra/references/TILE-DATA-SOURCES.md`
- Orchestrator workers + schema: `~/Code/busc-infra/references/SPRINT3.md`
- GIS stack infrastructure: `~/Code/busc-infra/references/GIS-STACK.md`
- Authentik configuration: `~/Code/busc-infra/references/AUTHENTIK.md`
- Sprint 2 (gis-api delivery): `~/Code/busc-infra/references/SPRINT2.md`