docs(plans): session handoff + audit + Faza H runbook
End of 2026-05-19 cutover-debug session. Saves the full state + 2 outstanding bugs + Faza G/H plan into docs/plans/ so the next session can resume without re-investigating. - prompt-handoff-2026-05-19.md: short prompt for the next session to amend + resume. - audit-2026-05-19.md: auditor-agent output (~30 findings). - 004-faza-h-runbook.md: pg_dump + REVOKE + DROP runbook with prereqs (eterra.live shares DB, unidentified writer, CfExtract schema drift). Memory entries also written: - feedback/authentik-token-endpoint-shared - project/architools-cutover-state-2026-05-19 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,518 @@
|
||||
# Plan 004 — Faza H runbook: 30-day read-only freeze + final DROP of legacy Gis*/CfExtract on architools_postgres
|
||||
|
||||
**Date:** 2026-05-19
|
||||
**Author:** ArchiTools session (Claude, `/home/orchestrator/Code/ArchiTools`)
|
||||
**Companion to:** `003-architools-cutover-execution-2026-05-17.md` §Faza H (see 003 line 108: "Do not drop Prisma tables … keep as dead columns 2-3 days. Drop in follow-up PR after prod validation.")
|
||||
|
||||
This runbook formalizes the 30-day decommission of:
|
||||
`public."GisFeature"`, `public."GisUat"`, `public."GisSyncRun"`, `public."GisSyncRule"`, `public."CfExtract"`
|
||||
on `architools_postgres` (satra:5432).
|
||||
|
||||
**Operator: Marius. This runbook is for review only — no commands here run themselves. Each step lists the command, a verify command, and a rollback if applicable.**
|
||||
|
||||
---
|
||||
|
||||
## Pre-flight summary (measured 2026-05-19)
|
||||
|
||||
| Table | Rows on `architools_postgres` | Rows on `postgres-gis` (paritate) | Notes |
|
||||
|--------------------|------------------------------:|---------------------------------:|-------|
|
||||
| `GisFeature` | 9,794,057 | 24,561,481 | gis-api ahead — full Romania sync in progress on shop |
|
||||
| `GisUat` | 3,186 | 3,186 | identical count |
|
||||
| `GisSyncRun` | 7,651 | n/a | local-only audit; lives or dies with satra |
|
||||
| `GisSyncRule` | 9 | n/a | local-only schedule; lives or dies with satra |
|
||||
| `CfExtract` | 8 | 47 (`gis_enrichment`) | **100% of architools 8 IDs already present** in gis_enrichment — see §H.3 |
|
||||
|
||||
Disk: `architools_db` = **35 GB total**, of which `GisFeature` = **15 GB**, `GisUat` = **159 MB**, rest negligible. After DROP CASCADE the DB should shrink to ~20 GB (other heavy tables: KeyValueStore 16 MB, RegistryAudit 120 KB — and procurement/acquisition tables not listed but visible in pg_stat_activity).
|
||||
|
||||
Last `GisFeature.updatedAt` = `2026-05-18 08:01:50` (4 rows touched in last 72 h, all `TERENURI_ACTIVE` on siruta 147358). Scheduler is officially disabled per memory `project_gis_db_overhaul.md`, so these writes are most likely from the enrichment path or a ghost cron — diagnose before §H.4. See **Risks §3**.
|
||||
|
||||
References:
|
||||
- `prisma/schema.prisma` lines 24–195: 5 models in scope.
|
||||
- 003 plan TL;DR + Faza C (do-not-drop directive, line 108).
|
||||
- Memory `feedback_gis_meta_default_acl.md` (gis-api): explicit REVOKE > rely on defaults.
|
||||
- Memory `project_eterra_live_saas.md`: confirms eterra.live still reads the SAME satra Gis* tables — see Risks §2.
|
||||
|
||||
---
|
||||
|
||||
## H.1 — Pre-flight inventory (read-only)
|
||||
|
||||
### H.1.1 Row counts on architools_postgres
|
||||
```bash
|
||||
ssh satra 'docker exec architools_postgres psql -U architools_user -d architools_db -c "
|
||||
SELECT '\''GisFeature'\'' AS table_name, count(*) FROM \"GisFeature\"
|
||||
UNION ALL SELECT '\''GisUat'\'', count(*) FROM \"GisUat\"
|
||||
UNION ALL SELECT '\''GisSyncRun'\'', count(*) FROM \"GisSyncRun\"
|
||||
UNION ALL SELECT '\''GisSyncRule'\'', count(*) FROM \"GisSyncRule\"
|
||||
UNION ALL SELECT '\''CfExtract'\'', count(*) FROM \"CfExtract\";"'
|
||||
```
|
||||
Expected (snapshot 2026-05-19): 9,794,057 / 3,186 / 7,651 / 9 / 8.
|
||||
|
||||
### H.1.2 Disk size
|
||||
```bash
|
||||
ssh satra 'docker exec architools_postgres psql -U architools_user -d architools_db -c "
|
||||
SELECT pg_size_pretty(pg_database_size('\''architools_db'\'')) AS db_size;
|
||||
SELECT relname, pg_size_pretty(pg_total_relation_size(quote_ident(schemaname)||'\''.\"'\''||relname||'\''\"'\'')) AS size
|
||||
FROM pg_stat_user_tables
|
||||
WHERE schemaname='\''public'\''
|
||||
ORDER BY pg_total_relation_size(quote_ident(schemaname)||'\''.\"'\''||relname||'\''\"'\'') DESC LIMIT 10;"'
|
||||
```
|
||||
Expected: db_size = 35 GB; GisFeature dominates at 15 GB.
|
||||
|
||||
### H.1.3 Paritate on postgres-gis (shop) — defense vs. orphan data
|
||||
```bash
|
||||
ssh shop 'docker exec postgres-gis psql -U gis_superuser -d gis -c "
|
||||
SELECT '\''gis_core.GisFeature'\'' AS t, count(*) FROM gis_core.\"GisFeature\"
|
||||
UNION ALL SELECT '\''gis_core.GisUat'\'', count(*) FROM gis_core.\"GisUat\"
|
||||
UNION ALL SELECT '\''gis_enrichment.CfExtract'\'', count(*) FROM gis_enrichment.\"CfExtract\";"'
|
||||
```
|
||||
**Gate**: postgres-gis `GisUat` must equal architools_postgres `GisUat` (= 3,186). If not, halt — the central DB is missing UATs that architools knows about, and parcel-sync hand-off is incomplete (Faza C/D not done).
|
||||
|
||||
---
|
||||
|
||||
## H.2 — pg_dump → NAS
|
||||
|
||||
**Goal:** one self-contained `-F c` archive of `architools_db`, separate from the rolling daily `architools-db.sql.gz`. Two reasons we need our own: (a) the daily is plain SQL gzipped → `pg_restore --list` won't work, can't selectively restore one table; (b) we want a pin file that survives daily rotation.
|
||||
|
||||
NAS access (from satra): `ssh -i ~/.ssh/satra_nas_backup satra-backup@10.10.10.10`. Backups land in `/HDD/satrabkp/` (per existing daily). For Faza H we'll use a dedicated subdir `/HDD/satrabkp/faza-h-pin/`.
|
||||
|
||||
### H.2.1 Take the archive
|
||||
|
||||
```bash
|
||||
# On satra. Streams pg_dump → ssh → NAS file, no intermediate disk on satra.
|
||||
ssh satra '
|
||||
DUMP_NAME="architools_db-pre-faza-h-$(date -u +%Y%m%dT%H%M%SZ).dump"
|
||||
docker exec architools_postgres pg_dump \
|
||||
-U architools_user -d architools_db \
|
||||
-F c -Z 6 --no-owner --no-privileges \
|
||||
--verbose 2>/tmp/pg_dump.faza-h.log \
|
||||
| ssh -i ~/.ssh/satra_nas_backup satra-backup@10.10.10.10 \
|
||||
"mkdir -p ~/faza-h-pin && cat > ~/faza-h-pin/${DUMP_NAME}"
|
||||
echo "DUMP_NAME=${DUMP_NAME}"
|
||||
tail -5 /tmp/pg_dump.faza-h.log'
|
||||
```
|
||||
- `-F c` = custom format (single binary file, supports `pg_restore --list` + selective `-t`).
|
||||
- `-Z 6` = mid-tier compression. Estimated output ~5–7 GB (the existing daily plain-SQL gzip is 5.2 GB and `-F c -Z 6` is similar density on GIS GeoJSON blobs).
|
||||
- Skipping parallel `-j` because we're streaming through one ssh pipe; parallel only helps when writing to disk on a multi-disk file system.
|
||||
|
||||
**Verify upload arrived intact (run after pg_dump finishes):**
|
||||
```bash
|
||||
ssh satra 'ssh -i ~/.ssh/satra_nas_backup satra-backup@10.10.10.10 "
|
||||
ls -lh ~/faza-h-pin/
|
||||
echo --- header check ---
|
||||
head -c 5 ~/faza-h-pin/architools_db-pre-faza-h-*.dump | xxd"'
|
||||
# Expected: first 5 bytes = "PGDMP" (custom-format magic).
|
||||
```
|
||||
|
||||
### H.2.2 Verify dump contents with `pg_restore --list`
|
||||
|
||||
```bash
|
||||
# Stream the dump from NAS back through satra to do pg_restore --list,
|
||||
# without copying to satra disk.
|
||||
ssh satra '
|
||||
DUMP=$(ssh -i ~/.ssh/satra_nas_backup satra-backup@10.10.10.10 "ls ~/faza-h-pin/architools_db-pre-faza-h-*.dump | tail -1")
|
||||
ssh -i ~/.ssh/satra_nas_backup satra-backup@10.10.10.10 "cat ~/faza-h-pin/$(basename "$DUMP")" \
|
||||
| docker exec -i architools_postgres pg_restore --list 2>/dev/null \
|
||||
| grep -E "TABLE DATA public (Gis|CfExtract)" '
|
||||
```
|
||||
Expected output: 5 TABLE DATA lines for `GisFeature`, `GisUat`, `GisSyncRun`, `GisSyncRule`, `CfExtract` (plus their indexes/constraints in other sections).
|
||||
|
||||
### H.2.3 Disk + lifecycle
|
||||
|
||||
NAS `/HDD` has 40 TB free (df 2026-05-19). 5–7 GB dump is rounding error. Pin folder `~/faza-h-pin/` is outside the daily-rotation tree — survives until manually deleted. **Keep this dump until at least 90 days post-DROP** — only restore path if Risk §1 materializes.
|
||||
|
||||
---
|
||||
|
||||
## H.3 — CfExtract overlap check
|
||||
|
||||
**Goal:** confirm gis_enrichment.CfExtract holds a superset of architools_postgres.CfExtract. Per memory `project_eterra_live_saas.md` + the migration history captured in gis-api memory `project_state_2026_05_17.md`, gis_enrichment.CfExtract was bootstrapped FROM architools_postgres. So overlap should be exact.
|
||||
|
||||
Already verified manually 2026-05-19 — all 8 IDs match:
|
||||
|
||||
```bash
|
||||
# Architools side (8 rows):
|
||||
ssh satra 'docker exec architools_postgres psql -U architools_user -d architools_db -c "
|
||||
SELECT id, \"nrCadastral\", \"userId\", \"createdAt\", status
|
||||
FROM \"CfExtract\" ORDER BY \"createdAt\";"' > /tmp/arc-cf.txt
|
||||
|
||||
# Gis-enrichment side — filter by the IDs from arc-cf.txt:
|
||||
ssh shop 'docker exec postgres-gis psql -U gis_superuser -d gis -c "
|
||||
SELECT id, \"nrCadastral\", \"userId\", \"createdAt\", status
|
||||
FROM gis_enrichment.\"CfExtract\"
|
||||
WHERE id IN (
|
||||
'\''c2ad0f24-f5bb-4586-947c-937254d8ed02'\'',
|
||||
'\''48908c41-49de-459e-adef-6f0f8fcb8022'\'',
|
||||
'\''3b4517a0-2d36-49ba-8da8-f3240c2fdf3f'\'',
|
||||
'\''22aabf8b-610a-4525-a352-21dccf7da5fe'\'',
|
||||
'\''676f0b3a-2148-414f-ab3a-69ef250509da'\'',
|
||||
'\''9e3c9d61-d3c4-43dd-bc2c-f92dd9972bab'\'',
|
||||
'\''8c6045cc-2f53-4c70-b4cb-a459e1fb94e1'\'',
|
||||
'\''13abdd21-d60b-4b7e-a31f-8187e5840987'\''
|
||||
) ORDER BY \"createdAt\";"' > /tmp/gis-cf.txt
|
||||
|
||||
# Compare (must report 0 diff rows except whitespace):
|
||||
diff <(sort /tmp/arc-cf.txt) <(sort /tmp/gis-cf.txt)
|
||||
```
|
||||
|
||||
**Decision: SKIP migration.** All 8 architools rows exist on gis_enrichment with identical (id, nrCadastral, userId, createdAt, status). No divergent writes since architools-side CfExtract has been **read-only via app code since the cutover** (Faza C feature-flag flip).
|
||||
|
||||
**Final safety check** before §H.4 — guard against new writes between now and freeze:
|
||||
```bash
|
||||
ssh satra 'docker exec architools_postgres psql -U architools_user -d architools_db -c "
|
||||
SELECT count(*) AS post_clone_rows
|
||||
FROM \"CfExtract\"
|
||||
WHERE \"createdAt\" > '\''2026-04-20 00:00:00'\''::timestamp;"'
|
||||
```
|
||||
**Expected: 0** (gis_enrichment was cloned from architools on or around 2026-04-20 per gis-api memory `project_state_2026_05_17.md`). If > 0, halt: those rows are post-clone and must be exported + INSERTed into gis_enrichment with RLS-correct `userId` before freeze.
|
||||
|
||||
---
|
||||
|
||||
## H.4 — REVOKE INSERT/UPDATE/DELETE + observe (30 days)
|
||||
|
||||
**Goal:** make the 5 tables read-only at the database privilege level. Reads still work for any stale code path; writes raise `permission denied`, which we log and grep for.
|
||||
|
||||
### H.4.1 Apply REVOKE
|
||||
|
||||
```sql
|
||||
-- Connect as superuser (the postgres role inside the container, not architools_user):
|
||||
-- ssh satra 'docker exec -it architools_postgres psql -U postgres -d architools_db'
|
||||
|
||||
BEGIN;
|
||||
|
||||
REVOKE INSERT, UPDATE, DELETE, TRUNCATE
|
||||
ON TABLE public."GisFeature",
|
||||
public."GisUat",
|
||||
public."GisSyncRun",
|
||||
public."GisSyncRule",
|
||||
public."CfExtract"
|
||||
FROM architools_user;
|
||||
|
||||
-- Reads must still succeed. Re-grant explicitly in case prior GRANTs are wider
|
||||
-- than expected — this is idempotent.
|
||||
GRANT SELECT
|
||||
ON TABLE public."GisFeature",
|
||||
public."GisUat",
|
||||
public."GisSyncRun",
|
||||
public."GisSyncRule",
|
||||
public."CfExtract"
|
||||
TO architools_user;
|
||||
|
||||
-- Sanity print:
|
||||
SELECT grantee, table_name, privilege_type
|
||||
FROM information_schema.table_privileges
|
||||
WHERE table_schema='public'
|
||||
AND table_name IN ('GisFeature','GisUat','GisSyncRun','GisSyncRule','CfExtract')
|
||||
AND grantee='architools_user'
|
||||
ORDER BY table_name, privilege_type;
|
||||
|
||||
COMMIT;
|
||||
```
|
||||
Expected post-COMMIT: only `SELECT` rows for `architools_user` × 5 tables.
|
||||
|
||||
### H.4.2 Verify write rejection
|
||||
|
||||
```bash
|
||||
# As architools_user — should fail with: ERROR: permission denied for table GisFeature
|
||||
ssh satra 'docker exec architools_postgres psql -U architools_user -d architools_db -c "
|
||||
INSERT INTO \"GisFeature\"(id, \"layerId\", siruta, \"objectId\", attributes, \"createdAt\", \"updatedAt\")
|
||||
VALUES (gen_random_uuid()::text, '\''TEST'\'', '\''999999'\'', -1, '\''{}'\''::jsonb, NOW(), NOW());"'
|
||||
# Expected: ERROR: permission denied for table GisFeature
|
||||
```
|
||||
|
||||
```bash
|
||||
# Read must still succeed:
|
||||
ssh satra 'docker exec architools_postgres psql -U architools_user -d architools_db -c "
|
||||
SELECT count(*) FROM \"GisFeature\";"'
|
||||
# Expected: 9794057 (or whatever the live count is at freeze time).
|
||||
```
|
||||
|
||||
### H.4.3 Rollback if observability breaks immediately
|
||||
|
||||
If app errors flood logs within 30 min of REVOKE (active write path missed during code audit):
|
||||
|
||||
```sql
|
||||
BEGIN;
|
||||
GRANT INSERT, UPDATE, DELETE, TRUNCATE
|
||||
ON TABLE public."GisFeature",
|
||||
public."GisUat",
|
||||
public."GisSyncRun",
|
||||
public."GisSyncRule",
|
||||
public."CfExtract"
|
||||
TO architools_user;
|
||||
COMMIT;
|
||||
```
|
||||
Then triage the offending code path, fix it (route to gis-api), redeploy, and re-attempt REVOKE.
|
||||
|
||||
### H.4.4 Monitoring during 30-day window
|
||||
|
||||
**Pattern to grep** in docker logs (architools container):
|
||||
```
|
||||
permission denied for table (GisFeature|GisUat|GisSyncRun|GisSyncRule|CfExtract)
|
||||
```
|
||||
Also catch ORM-side mirror errors:
|
||||
```
|
||||
relation "(Gis|CfExtract)
|
||||
```
|
||||
|
||||
**Cron snippet** (do NOT install yet — for review):
|
||||
```bash
|
||||
# /etc/cron.d/faza-h-freeze-watch (run as root on satra, every 15 min)
|
||||
*/15 * * * * root \
|
||||
COUNT=$(docker logs --since 15m architools 2>&1 \
|
||||
| grep -cE 'permission denied for table (Gis|CfExtract)|relation "(Gis|CfExtract)') ; \
|
||||
if [ "$COUNT" -gt 0 ]; then \
|
||||
curl -fsS -X POST -H "Content-Type: application/json" \
|
||||
-d "{\"text\":\"Faza H: $COUNT write attempts hit revoked Gis/CfExtract tables in last 15min on architools\"}" \
|
||||
https://n8n.beletage.ro/webhook/<faza-h-alert>; \
|
||||
fi
|
||||
```
|
||||
The n8n webhook URL is a placeholder — Marius creates the workflow when ready. Alternative: pipe to `mail -s` to ops alias or set up an Uptime Kuma push monitor.
|
||||
|
||||
**Manual probe** anytime during the window:
|
||||
```bash
|
||||
ssh satra 'docker logs --since 24h architools 2>&1 \
|
||||
| grep -E "permission denied for table (Gis|CfExtract)|relation \"(Gis|CfExtract)" \
|
||||
| tail -20'
|
||||
```
|
||||
|
||||
**Exit criteria for §H.5**: 30 consecutive days with **zero** matches in the grep.
|
||||
|
||||
---
|
||||
|
||||
## H.5 — Final DROP after 30 silent days
|
||||
|
||||
### H.5.1 Final pre-flight (re-confirm gates from §H.1)
|
||||
|
||||
```bash
|
||||
# 1. NAS dump still exists:
|
||||
ssh satra 'ssh -i ~/.ssh/satra_nas_backup satra-backup@10.10.10.10 \
|
||||
"ls -lh ~/faza-h-pin/architools_db-pre-faza-h-*.dump"'
|
||||
|
||||
# 2. Re-take a fresh dump on the day of DROP (in case there's been
|
||||
# enrichment-field drift we want a clean rollback target):
|
||||
# (repeat §H.2.1 with a new timestamped filename → ~/faza-h-pin/)
|
||||
|
||||
# 3. postgres-gis still at parity or ahead:
|
||||
ssh shop 'docker exec postgres-gis psql -U gis_superuser -d gis -c "
|
||||
SELECT count(*) FROM gis_core.\"GisUat\";"' # must be >= 3186
|
||||
|
||||
# 4. No code references left in src/ (sanity, should already be 0 from Faza C/D):
|
||||
cd /home/orchestrator/Code/ArchiTools \
|
||||
&& grep -rln "GisFeature\\|GisUat\\|GisSyncRun\\|GisSyncRule\\|CfExtract" src/ \
|
||||
| grep -v "\\.next\\|node_modules"
|
||||
# Expected: empty.
|
||||
```
|
||||
|
||||
### H.5.2 DROP TABLE CASCADE
|
||||
|
||||
```sql
|
||||
-- Connect as superuser:
|
||||
-- ssh satra 'docker exec -it architools_postgres psql -U postgres -d architools_db'
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- CASCADE because GisFeature has FK to GisSyncRun (onDelete: SetNull → safe but
|
||||
-- DROP order otherwise matters). Order chosen to drop dependents first.
|
||||
DROP TABLE IF EXISTS public."CfExtract" CASCADE;
|
||||
DROP TABLE IF EXISTS public."GisFeature" CASCADE;
|
||||
DROP TABLE IF EXISTS public."GisSyncRun" CASCADE;
|
||||
DROP TABLE IF EXISTS public."GisUat" CASCADE;
|
||||
DROP TABLE IF EXISTS public."GisSyncRule" CASCADE;
|
||||
|
||||
-- Optional: drop the PostGIS trigger + 'geom' column infrastructure if it's
|
||||
-- only used by GisFeature. See prisma/postgis-setup.sql — if all triggers ref
|
||||
-- GisFeature, drop them now. If PostGIS itself is used by other tables, keep
|
||||
-- the extension.
|
||||
SELECT extname FROM pg_extension WHERE extname IN ('postgis','postgis_topology');
|
||||
-- If unused: DROP EXTENSION postgis CASCADE; -- only after verifying!
|
||||
|
||||
COMMIT;
|
||||
|
||||
-- Reclaim disk:
|
||||
VACUUM FULL; -- alternative: pg_repack if downtime not acceptable
|
||||
```
|
||||
|
||||
**Verify post-DROP:**
|
||||
```bash
|
||||
ssh satra 'docker exec architools_postgres psql -U architools_user -d architools_db -c "
|
||||
SELECT tablename FROM pg_tables
|
||||
WHERE schemaname='\''public'\''
|
||||
AND tablename IN ('\''GisFeature'\'','\''GisUat'\'','\''GisSyncRun'\'','\''GisSyncRule'\'','\''CfExtract'\'');"'
|
||||
# Expected: 0 rows.
|
||||
|
||||
ssh satra 'docker exec architools_postgres psql -U architools_user -d architools_db -c "
|
||||
SELECT pg_size_pretty(pg_database_size('\''architools_db'\'')) AS db_size;"'
|
||||
# Expected: ~20 GB (down from 35 GB).
|
||||
```
|
||||
|
||||
### H.5.3 Prisma schema cleanup
|
||||
|
||||
Remove from `prisma/schema.prisma` (lines 24–195):
|
||||
- `model GisSyncRule`
|
||||
- `model GisFeature` (and its `syncRun` relation field)
|
||||
- `model GisSyncRun` (and its `features GisFeature[]` back-relation)
|
||||
- `model GisUat`
|
||||
- `model CfExtract`
|
||||
|
||||
Also check `prisma/postgis-setup.sql` — if it exists and only configures the dropped tables, delete the file or reduce it to a no-op header.
|
||||
|
||||
```bash
|
||||
cd /home/orchestrator/Code/ArchiTools
|
||||
|
||||
# Edit prisma/schema.prisma (remove the 5 models)
|
||||
$EDITOR prisma/schema.prisma
|
||||
|
||||
# Reformat + regenerate:
|
||||
npx prisma format
|
||||
npx prisma generate
|
||||
|
||||
# Build clean:
|
||||
npx next build
|
||||
# Expected: zero errors. If any TS error references Gis*/CfExtract, that's
|
||||
# left-over Faza C/D dead code — fix or delete the file.
|
||||
|
||||
# Commit:
|
||||
git add prisma/schema.prisma prisma/postgis-setup.sql
|
||||
git commit -m "chore(prisma): drop legacy Gis*/CfExtract models — Faza H final"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
### H.5.4 Container redeploy
|
||||
|
||||
```bash
|
||||
# Portainer API unreachable from Orchi (memory `feedback_portainer_api_unreachable.md`)
|
||||
# → use Portainer UI (stack 8 → Editor → Update + Pull and redeploy)
|
||||
# OR SSH on satra:
|
||||
ssh satra 'cd /data/compose/8 && docker compose pull architools && docker compose up -d architools'
|
||||
|
||||
# Verify:
|
||||
ssh satra 'docker logs --tail 50 architools 2>&1 | grep -iE "error|prisma|gis"'
|
||||
# Expected: no Prisma errors. Tile/map still works (sourced from tiles.gis.ac).
|
||||
```
|
||||
|
||||
### H.5.5 Rollback (if a write attempt is observed during the 30-day window)
|
||||
|
||||
If grep finds a "permission denied" hit:
|
||||
1. Capture trace: `docker logs --since <timestamp> architools 2>&1 | grep -B2 -A20 "permission denied"`.
|
||||
2. Identify route from stack, cross-check Faza C/D diff.
|
||||
3. Either (a) delete dead Gis* code (proper fix) or (b) fix the ORM call to gis-api proxy.
|
||||
4. **Do NOT GRANT writes back** unless fix takes > 24h and route is user-facing. If you must `GRANT INSERT, UPDATE, DELETE ON public."<table>" TO architools_user;` — then **reset the 30-day clock**.
|
||||
|
||||
### H.5.6 Hard rollback (if DROP turns out to break something — e.g. archi.* schema FK we missed)
|
||||
|
||||
```bash
|
||||
# 1. Restore the 5 tables from the pin dump (selective restore, no full DB):
|
||||
ssh satra '
|
||||
DUMP=$(ssh -i ~/.ssh/satra_nas_backup satra-backup@10.10.10.10 \
|
||||
"ls ~/faza-h-pin/architools_db-pre-faza-h-*.dump | tail -1")
|
||||
ssh -i ~/.ssh/satra_nas_backup satra-backup@10.10.10.10 \
|
||||
"cat ~/faza-h-pin/$(basename "$DUMP")" \
|
||||
| docker exec -i architools_postgres pg_restore \
|
||||
-U postgres -d architools_db \
|
||||
-t GisFeature -t GisUat -t GisSyncRun -t GisSyncRule -t CfExtract \
|
||||
--no-owner --no-privileges --verbose'
|
||||
|
||||
# 2. Re-apply REVOKE (per §H.4.1) so we're back in read-only freeze, not write-active.
|
||||
# 3. Restore the deleted Prisma models from git history:
|
||||
cd /home/orchestrator/Code/ArchiTools
|
||||
git revert <commit-from-H.5.3>
|
||||
npx prisma generate && npx next build
|
||||
git push origin main
|
||||
# 4. Redeploy (per §H.5.4).
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Risks + open questions
|
||||
|
||||
### Risk 1 (HIGH): eterra.live still reads architools_postgres Gis* tables
|
||||
|
||||
Per memory `project_eterra_live_saas.md`:
|
||||
|
||||
> "Shares same PostgreSQL+PostGIS geometry DB as ArchiTools (GisFeature, GisUat tables)"
|
||||
|
||||
eterra.live is a separate Next.js app, separate repo (`gitadmin/eterra-live`), but its Prisma client connects to the same `architools_postgres`. If we DROP the Gis* tables before eterra.live is also cut over to api.gis.ac, eterra.live's `/harta` + `/rgi-search` routes break in prod.
|
||||
|
||||
**Mitigation — must happen before §H.5 DROP** (not before §H.4 REVOKE, since REVOKE breaks eterra.live writes too, but eterra.live shouldn't be writing Gis* — verify):
|
||||
- Audit `eterra-live` repo for `prisma.gisFeature.*`, `prisma.gisUat.*`, `prisma.cfExtract.*`.
|
||||
- If write paths exist, those will fail at §H.4 REVOKE — schedule cutover of eterra.live FIRST.
|
||||
- If read-only, §H.4 is safe, but §H.5 DROP still needs eterra.live migrated to api.gis.ac.
|
||||
|
||||
**Check (run before §H.4):**
|
||||
```bash
|
||||
# On Orchi or wherever eterra-live is checked out:
|
||||
cd /home/orchestrator/Code/eterra-live 2>/dev/null \
|
||||
&& grep -rn "gisFeature\\|gisUat\\|cfExtract\\|GisFeature\\|GisUat\\|CfExtract" src/ prisma/ \
|
||||
| grep -v "\\.next\\|node_modules"
|
||||
# If repo not local: ssh git.beletage.ro and clone, or check via Gitea web UI.
|
||||
```
|
||||
|
||||
### Risk 2 (MEDIUM): unidentified writer touching GisFeature in last 72h
|
||||
|
||||
Snapshot 2026-05-18: 4 GisFeature rows had `updatedAt` within 72h, all on `siruta=147358 (TERENURI_ACTIVE)`, all linked to `syncRunId=4fe614d6-...` from 2026-03-21. The scheduler is officially disabled (memory `project_gis_db_overhaul.md`), yet writes are happening. Candidates:
|
||||
- A geoportal route that fills `enrichment` JSON on read-then-write — check `/api/geoportal/enrich/route.ts` (found earlier in src/).
|
||||
- A leftover cron in n8n that hits `/api/eterra/*`.
|
||||
- eterra.live writing enrichment to the shared DB (would also trip Risk 1).
|
||||
|
||||
**Action before §H.4:** during a 24-h quiet window, monitor:
|
||||
```bash
|
||||
ssh satra 'docker exec architools_postgres psql -U architools_user -d architools_db -c "
|
||||
SELECT \"updatedAt\", \"layerId\", siruta, \"objectId\"
|
||||
FROM \"GisFeature\"
|
||||
WHERE \"updatedAt\" > NOW() - INTERVAL '\''24 hours'\''
|
||||
ORDER BY \"updatedAt\" DESC LIMIT 20;"'
|
||||
```
|
||||
If non-empty after Faza C/D ship, find the writer (correlation with `pg_stat_activity` snapshots) before flipping REVOKE.
|
||||
|
||||
### Risk 3 (LOW): pg_dump streaming may time out
|
||||
|
||||
5.2 GB daily dump → expect 5–7 GB pin dump. NAS on 10G agg switch (`sw-agg-10g-01`) → 1–5 min. If stream times out: dump locally first (needs ~10 GB free at `/var/lib/docker/volumes/`), then `rsync` to NAS.
|
||||
|
||||
### Risk 4 (LOW): orphaned PostGIS extension
|
||||
|
||||
`GisFeature.geometry` is JSONB backed by PostGIS `geom` via trigger (see `prisma/postgis-setup.sql`). After DROP, if no other table uses PostGIS, the extension is orphaned but harmless (~7 MB `spatial_ref_sys`). Drop in §H.5.2 is optional — Marius's call.
|
||||
|
||||
### Open question 1: external consumers of architools_postgres Gis*?
|
||||
|
||||
Checked `pg_stat_activity` 2026-05-19 — only `architools_user` active. Point-in-time only. Re-run during freeze 3–4 times at different hours of day:
|
||||
```bash
|
||||
ssh satra 'docker exec architools_postgres psql -U architools_user -d architools_db -c "
|
||||
SELECT DISTINCT usename, application_name, client_addr FROM pg_stat_activity WHERE datname='\''architools_db'\'';"'
|
||||
```
|
||||
Anything other than `architools_user` → investigate before §H.5.
|
||||
|
||||
### Open question 2: archi.* schema FK to Gis*?
|
||||
|
||||
Plan 003 §"Open questions" item 4 mentions `archi.*` schema (Canvas, Job, etc.) is DEFERRED. We don't know yet whether `archi.Canvas` or `archi.Job` carries an FK to `GisFeature.id`. **Check before §H.5:**
|
||||
```sql
|
||||
ssh satra 'docker exec architools_postgres psql -U architools_user -d architools_db -c "
|
||||
SELECT conrelid::regclass AS from_table,
|
||||
confrelid::regclass AS to_table,
|
||||
conname
|
||||
FROM pg_constraint
|
||||
WHERE contype='\''f'\''
|
||||
AND confrelid::regclass::text IN
|
||||
('\''public.\"GisFeature\"'\'','\''public.\"GisUat\"'\'',
|
||||
'\''public.\"GisSyncRun\"'\'','\''public.\"GisSyncRule\"'\'',
|
||||
'\''public.\"CfExtract\"'\'');"'
|
||||
```
|
||||
Empty result = safe. Non-empty = either drop the FK in a prior commit OR move the related data to gis-api before §H.5.
|
||||
|
||||
### Random suspicious thing — schema drift
|
||||
|
||||
Live `CfExtract` on satra has columns not in `prisma/schema.prisma` (`userId`, `pdfData bytea`, `type`, `adminOrderedBy`, `minioPath`). Same class of error as the 2026-05-17 gis RLS leak (memory `project_rls_leak_2026_05_17.md`). Before §H.5.3 audit other models with `npx prisma db pull --print` for deltas. **Never run `prisma db push`** (memory `feedback_prisma_migrate_drift.md`).
|
||||
|
||||
---
|
||||
|
||||
## Citation map back to plan 003
|
||||
|
||||
| Section | Plan 003 reference |
|
||||
|---|---|
|
||||
| H.1 / H.2 | 003 §Faza C, line 108 ("Do not drop Prisma tables … keep as dead columns 2-3 days") — we extend "2-3 days" to "30 days" for safety. |
|
||||
| H.3 (overlap) | 003 §Faza F + memory `project_eterra_live_saas.md` (gis_enrichment cloned from architools). |
|
||||
| H.4 (REVOKE) | New for this runbook — not specified in 003. Justification: defense-in-depth, mirrors gis-api's "explicit REVOKE > rely on defaults" pattern (memory `feedback_gis_meta_default_acl.md`). |
|
||||
| H.5 (DROP) | 003 §Faza G item 10 ("Follow-up PR drops dead Prisma tables") + §Faza C line 108. |
|
||||
| Risks §1 (eterra.live) | 003 §Faza H header ("Planhub deferred … separate PR") — eterra.live is in the same "downstream consumer" bucket but not called out. Flagged here as a HARD prerequisite. |
|
||||
|
||||
---
|
||||
|
||||
End of runbook. Total: 5 phases × ~1 hour of operator time spread across 30 days, +1 month wall-clock for the silent observation window.
|
||||
Reference in New Issue
Block a user