"""Minimal guarded Firebird write executor for the MVP. Scope is intentionally narrow: a single allowlisted table/field (C000025.IPI), a single authorized database file, and an optimistic compare-before-write. This is not a generic ORM/executor framework. Per CLAUDE.md's "Firebird write policy": writes are only ever permitted against the disposable test database TESTESOFICIAIS.FDB. Any other path, or any table/field outside the allowlist, fails closed before touching the connection. """ from __future__ import annotations import os from dataclasses import dataclass from pathlib import Path from typing import Any AUTHORIZED_WRITE_DATABASE_PATH = Path(r"D:\PROJETOS\GTISISTEMAS\Projeto\TESTESOFICIAIS.FDB") # table -> allowed writable fields. Anything not listed here is refused. _FIELD_ALLOWLIST: dict[str, set[str]] = { # C000025.ALIQ_IPI is VARCHAR(1) and unused (0/521 rows) — confirmed via # the vendor's own DDL scripts and live schema inspection to NOT be the # IPI rate field. C000025.IPI NUMERIC(15,2) is. See Mission notes. "C000025": {"IPI"}, } # Local completion mission: every field with WRITE_ALLOWED=true in docs/FISCAL_FIELD_CONTRACT_V1.json (kept equal to # central.app.field_contract.WRITE_FIELDS by a test). Still C000025 only; still exact-path authorized. _LOCAL_COMPLETION_FIELDS = {"CLASSIFICACAO_FISCAL", "IPI", "EX_TIPI", "CEST", "SITUACAO_TRIBUTARIA", "CFOP", "CFOP_ENTRADA", "CST", "CST_ENTRADA", "CSOSN", "ALIQUOTA", "COD_ORIGEM_MERCADORIA", "CST_IPI", "COD_ENTRADA_PIS", "COD_SAIDA_PIS", "ALIQ_PIS", "COD_ENTRADA_COFINS", "COD_SAIDA_COFINS", "ALIQ_COFINS", "INCIDENCIA_PISCOFINS", "PISCOFINS", "IAT", "IPPT"} _LOCAL_ALLOWLIST_FILE = Path(__file__).resolve().parents[1] / "docs" / "LOCAL_WRITE_ALLOWLIST_V1.json" _KEY_FIELD = "CODIGO" class WriteRefused(Exception): """Raised whenever a guard rejects the write before it reaches the database.""" def _normalized_path(path: os.PathLike[str] | str) -> str: return os.path.normcase(os.path.normpath(str(Path(path).resolve(strict=False)))) def assert_authorized_write_path( path: os.PathLike[str] | str, authorized_path: os.PathLike[str] | str | None = None, ) -> Path: """Fail closed unless `path` is exactly the authorized write target. `authorized_path` defaults to the development disposable test database (`AUTHORIZED_WRITE_DATABASE_PATH`) for backward compatibility with the dev environment. A pilot/client installation MUST pass its own `authorized_path` explicitly (from that installation's own config, e.g. `AUTHORIZED_WRITE_DATABASE_PATH` env var) — authorization is never inferred from the filename or extension alone, and never defaults to "any .FDB is fine". If a caller sets FIREBIRD_WRITES=1 in a pilot environment without also setting an explicit authorized_path, this still falls back to the dev-only path and therefore fails closed for any real client database, which is the safe behavior. """ candidate = Path(path) authorized = Path(authorized_path) if authorized_path else AUTHORIZED_WRITE_DATABASE_PATH if _normalized_path(candidate) != _normalized_path(authorized): raise WriteRefused(f"path is not the authorized write target: {candidate}") return authorized def assert_schema_fingerprint_authorized(observed_fingerprint: str | None, expected_fingerprint: str | None) -> None: """Fail closed unless the live schema fingerprint matches what this installation was registered/configured to expect. Prevents writing against a database whose schema silently changed or was never verified.""" if not expected_fingerprint: raise WriteRefused("no expected schema fingerprint configured for this installation") if observed_fingerprint != expected_fingerprint: raise WriteRefused(f"schema fingerprint mismatch: expected {expected_fingerprint!r}, observed {observed_fingerprint!r}") def assert_allowed_field(table: str, field: str) -> None: allowed = _FIELD_ALLOWLIST.get(table.upper()) if not allowed or field.upper() not in allowed: raise WriteRefused(f"{table}.{field} is not in the write allowlist") def assert_write_eligible(proposal: dict[str, Any]) -> None: if not proposal.get("write_eligible"): raise WriteRefused( f"proposal for {proposal.get('product_code')}/{proposal.get('field')} " f"is not write_eligible (status={proposal.get('status')})" ) def assert_schema_ok(cursor: Any, table: str, field: str, proposed_value: Any = None) -> None: """Confirm the target column exists and, if it is a fixed-length text field, that the proposed value actually fits without truncation. Never coerce or truncate a value to make it fit.""" cursor.execute( "SELECT f.RDB$FIELD_TYPE, f.RDB$FIELD_LENGTH FROM RDB$RELATION_FIELDS rf " "JOIN RDB$FIELDS f ON f.RDB$FIELD_NAME = rf.RDB$FIELD_SOURCE " "WHERE rf.RDB$RELATION_NAME = ? AND rf.RDB$FIELD_NAME = ?", (table, field), ) row = cursor.fetchone() if row is None: raise WriteRefused(f"schema guard failed: {table}.{field} not found in live schema") field_type, field_length = row # Firebird character types (CHAR=14, VARCHAR=37): reject a value that would truncate. if proposed_value is not None and field_type in (14, 37) and field_length is not None: if len(str(proposed_value)) > field_length: raise WriteRefused( f"schema guard failed: {table}.{field} is {field_length} char(s) wide, " f"proposed value {proposed_value!r} ({len(str(proposed_value))} chars) would truncate" ) @dataclass(frozen=True) class WriteAuditRecord: product_code: str field: str before_value: Any proposed_value: Any after_value: Any rowcount: int source_dataset: str | None source_version: str | None timestamp: str def as_dict(self) -> dict[str, Any]: return { "product_code": self.product_code, "field": self.field, "before_value": self.before_value, "proposed_value": self.proposed_value, "after_value": self.after_value, "rowcount": self.rowcount, "source_dataset": self.source_dataset, "source_version": self.source_version, "timestamp": self.timestamp, } def execute_guarded_update( connection: Any, *, database_path: os.PathLike[str] | str, proposal: dict[str, Any], table: str = "C000025", key_field: str = _KEY_FIELD, now: str, authorized_path: os.PathLike[str] | str | None = None, ) -> WriteAuditRecord: """Perform one allowlisted, compare-before-write UPDATE and verify it. Never commits if any guard fails. Returns an audit record with no secrets. `authorized_path` defaults to the dev disposable test database; a pilot installation must pass its own registered path explicitly. """ assert_authorized_write_path(database_path, authorized_path=authorized_path) assert_write_eligible(proposal) field = proposal["field"] assert_allowed_field(table, field) product_code = proposal["product_code"] expected_before = proposal["current_value"] proposed_value = proposal["proposed_value"] if proposed_value is None: raise WriteRefused("refuse to write a null proposed_value") cursor = connection.cursor() try: assert_schema_ok(cursor, table, field, proposed_value) cursor.execute( f"SELECT {key_field}, {field} FROM {table} WHERE {key_field} = ?", (product_code,), ) row = cursor.fetchone() if row is None: raise WriteRefused(f"product {product_code} not found in {table}") live_before = row[1] if live_before != expected_before: raise WriteRefused( f"drift detected for {product_code}.{field}: expected {expected_before!r}, " f"found {live_before!r}" ) if expected_before is None: sql = f"UPDATE {table} SET {field} = ? WHERE {key_field} = ? AND {field} IS NULL" params = (proposed_value, product_code) else: sql = f"UPDATE {table} SET {field} = ? WHERE {key_field} = ? AND {field} = ?" params = (proposed_value, product_code, expected_before) cursor.execute(sql, params) rowcount = cursor.rowcount if rowcount != 1: connection.rollback() raise WriteRefused(f"expected rowcount 1, got {rowcount}; rolled back") connection.commit() cursor.execute(f"SELECT {field} FROM {table} WHERE {key_field} = ?", (product_code,)) after_row = cursor.fetchone() after_value = after_row[0] if after_row else None if after_value != proposed_value: raise WriteRefused( f"post-write verification failed for {product_code}.{field}: " f"expected {proposed_value!r}, found {after_value!r}" ) return WriteAuditRecord( product_code=product_code, field=field, before_value=live_before, proposed_value=proposed_value, after_value=after_value, rowcount=rowcount, source_dataset=proposal.get("source_dataset"), source_version=proposal.get("source_version"), timestamp=now, ) except Exception: try: connection.rollback() except Exception: pass raise finally: cursor.close() def execute_guarded_restore( connection: Any, *, database_path: os.PathLike[str] | str, table: str, field: str, key_field: str, product_code: str, written_value: Any, restore_value: Any, now: str, ) -> WriteAuditRecord: """Restore a field to `restore_value`, guarded the same way as the forward write.""" assert_authorized_write_path(database_path) assert_allowed_field(table, field) cursor = connection.cursor() try: assert_schema_ok(cursor, table, field) cursor.execute(f"SELECT {field} FROM {table} WHERE {key_field} = ?", (product_code,)) row = cursor.fetchone() if row is None: raise WriteRefused(f"product {product_code} not found in {table}") if row[0] != written_value: raise WriteRefused(f"drift before restore: expected {written_value!r}, found {row[0]!r}") if restore_value is None: sql = f"UPDATE {table} SET {field} = NULL WHERE {key_field} = ? AND {field} = ?" params = (product_code, written_value) else: sql = f"UPDATE {table} SET {field} = ? WHERE {key_field} = ? AND {field} = ?" params = (restore_value, product_code, written_value) cursor.execute(sql, params) rowcount = cursor.rowcount if rowcount != 1: connection.rollback() raise WriteRefused(f"expected rowcount 1 on restore, got {rowcount}; rolled back") connection.commit() cursor.execute(f"SELECT {field} FROM {table} WHERE {key_field} = ?", (product_code,)) after_row = cursor.fetchone() after_value = after_row[0] if after_row else None if after_value != restore_value: raise WriteRefused( f"post-restore verification failed: expected {restore_value!r}, found {after_value!r}" ) return WriteAuditRecord( product_code=product_code, field=field, before_value=written_value, proposed_value=restore_value, after_value=after_value, rowcount=rowcount, source_dataset=None, source_version=None, timestamp=now, ) except Exception: try: connection.rollback() except Exception: pass raise finally: cursor.close() def load_local_allowlist(path=None): """Exact canonical paths authorized for the local completion mission (never by directory/extension).""" import json doc = json.loads(Path(path or _LOCAL_ALLOWLIST_FILE).read_text(encoding="utf-8")) return {os.path.normcase(os.path.abspath(d["path"])) for d in doc["databases"]} def assert_local_completion_target(database_path, allowlist=None) -> None: allow = allowlist if allowlist is not None else load_local_allowlist() if os.path.normcase(os.path.abspath(str(database_path))) not in allow: raise WriteRefused(f"{database_path} is not in the exact local-completion write allowlist") def execute_guarded_product_update(connection, *, database_path, product_code, changes: dict, now: str, allowlist=None, table: str = "C000025", key_field: str = _KEY_FIELD, restore_before: bool = False): """One transaction per product: every field is compare-before-write, ONE UPDATE, rowcount must be 1, then an independent re-read. changes: {field: (expected_before, proposed)}. restore_before=True (rollback of an audited write to its recorded BEFORE) additionally allows a NULL target. Refuses any field outside the local-completion field set, any null proposal, any path outside the exact allowlist, and any value that would truncate. Returns an audit dict (no secrets).""" assert_local_completion_target(database_path, allowlist) if table != "C000025": raise WriteRefused("only C000025 may be written") if not changes: raise WriteRefused("no changes") for field, (_, proposed) in changes.items(): if field not in _LOCAL_COMPLETION_FIELDS: raise WriteRefused(f"{table}.{field} is not a contract write target") if proposed is None and not restore_before: raise WriteRefused(f"refuse to write a null proposed_value for {field}") cursor = connection.cursor() try: for field, (_, proposed) in changes.items(): if proposed is not None: assert_schema_ok(cursor, table, field, proposed) fields = list(changes) cursor.execute(f"SELECT {', '.join(fields)} FROM {table} WHERE {key_field} = ?", (product_code,)) row = cursor.fetchone() if row is None: raise WriteRefused(f"product {product_code} not found in {table}") for field, live in zip(fields, row): expected = changes[field][0] if _norm(live) != _norm(expected): raise WriteRefused(f"drift detected for {product_code}.{field}: expected {expected!r}, found {live!r}") sets = ", ".join(f"{f} = ?" for f in fields) cursor.execute(f"UPDATE {table} SET {sets} WHERE {key_field} = ?", tuple(changes[f][1] for f in fields) + (product_code,)) if cursor.rowcount != 1: connection.rollback() raise WriteRefused(f"expected rowcount 1, got {cursor.rowcount}; rolled back") connection.commit() cursor.execute(f"SELECT {', '.join(fields)} FROM {table} WHERE {key_field} = ?", (product_code,)) after = cursor.fetchone() bad = [f for f, a in zip(fields, after) if _norm(a) != _norm(changes[f][1])] if bad: raise WriteRefused(f"post-write verification failed for {product_code}: {bad}") return {"product_code": product_code, "rowcount": 1, "fields": {f: {"before": changes[f][0], "proposed": changes[f][1], "after": a} for f, a in zip(fields, after)}, "timestamp": now} except Exception: try: connection.rollback() except Exception: pass raise finally: cursor.close() def _norm(v): """Comparable form: text trimmed, numbers as float (Decimal/int/float from the driver), None stays None.""" if v is None: return None if isinstance(v, (int, float)) or v.__class__.__name__ == "Decimal": return round(float(v), 6) return str(v).strip()