feat(faza-c.2): gate legacy GisFeature writes under USE_GIS_AC

Adds gateLegacyGisWrite() helper that returns 410 when the caller is on
the api.gis.ac path (global USE_GIS_AC=1 or per-user GIS_AC_PILOT_USERS).
Wired into 13 routes covering every entry point that touches Gis*
tables on architools_postgres — directly or via parcel-sync services.

Why: yesterday 4 GisFeature rows were updated on architools_postgres
even though the scheduler is officially disabled. Root cause: pilot
user opened the legacy /geoportal UI in a stale tab and clicked
parcels; POST /api/geoportal/enrich wrote directly to the local DB.
Without a write gate, Faza H (pg_dump + REVOKE + DROP) is unsafe —
any stale tab in any user's browser can still trip writes between
freeze and DROP.

Gated routes (writes only — reads stay open for rollback ergonomics):
- /api/geoportal/enrich (POST) — the writer of the 4 rows
- /api/eterra/sync-rules (POST), /api/eterra/sync-rules/[id] (PATCH+DELETE)
- /api/eterra/sync-rules/bulk (POST)
- /api/eterra/uats (POST+PATCH)
- /api/eterra/sync (POST), /api/eterra/sync-county (POST)
- /api/eterra/sync-background (POST), /api/eterra/sync-all-counties (POST)
- /api/eterra/auto-refresh (POST), /api/eterra/refresh-all (POST)
- /api/eterra/export-layer-gpkg (POST), /api/eterra/export-bundle (POST)
  (last two trigger syncLayer write-first-then-export)

Read-only routes intentionally NOT gated: sync-status, no-geom-scan
(scanNoGeometryParcels is read-only), export-local, db-summary,
counties, search, features (GET), stats, uat-dashboard, sync-rules
(GET), sync-rules/scheduler.

Operations: after redeploy, flip USE_GIS_AC=1 in Infisical /architools
prod env and restart container. Then monitor docker logs for ~30 min:
grep "deprecated" + "/api/geoportal/enrich|/api/eterra/sync*" lines
indicate stale-tab clients that need a refresh. pg_stat_user_tables
write count on GisFeature should hit 0 within one hour.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude VM
2026-05-19 11:00:16 +03:00
parent 9847b4a070
commit b957de77b9
14 changed files with 91 additions and 0 deletions
+5
View File
@@ -4,6 +4,7 @@ import { readFile } from "fs/promises";
import { join } from "path";
import { EterraClient } from "@/modules/parcel-sync/services/eterra-client";
import { getSessionCredentials } from "@/modules/parcel-sync/services/session-store";
import { gateLegacyGisWrite } from "@/core/feature-flags/gis-write-gate";
export const runtime = "nodejs";
export const dynamic = "force-dynamic";
@@ -212,6 +213,8 @@ export async function GET() {
/* ------------------------------------------------------------------ */
export async function POST() {
const gate = await gateLegacyGisWrite("/api/eterra/uats");
if (gate) return gate;
try {
// Read uat.json from public/ directory
@@ -288,6 +291,8 @@ export async function POST() {
/* ------------------------------------------------------------------ */
export async function PATCH() {
const gate = await gateLegacyGisWrite("/api/eterra/uats");
if (gate) return gate;
try {
// 1. Get eTerra credentials from session
const session = getSessionCredentials();