chore: establish project baseline

This commit is contained in:
OpenCode
2026-09-21 15:51:47 -03:00
commit 9dcc263217
459 changed files with 447890 additions and 0 deletions
+22
View File
@@ -0,0 +1,22 @@
"""Read-only Firebird discovery utilities.
The lexical SQL validator is intentionally not imported here. It is an
offline-only diagnostic utility in ``gti_discovery.read_only`` and has no
runtime security authority.
"""
from .firebird_connection import (
AUTHORIZED_DATABASE_PATH,
connect_authorized_read_only,
get_database_identity,
validate_authorized_database_identity,
validate_authorized_database_path,
)
__all__ = [
"AUTHORIZED_DATABASE_PATH",
"connect_authorized_read_only",
"get_database_identity",
"validate_authorized_database_identity",
"validate_authorized_database_path",
]
+287
View File
@@ -0,0 +1,287 @@
"""The closed, read-only Firebird catalog API (Mission 00D redesign).
This module is the ONLY place that ever builds SQL text and sends it to
the Firebird driver for real. It exposes no ``execute(sql)``, ``query(sql)``
or equivalent to callers -- ``FirebirdCatalog`` only offers narrow,
pre-defined operations (``get_database_info``, ``list_relations``,
``get_relation_columns``, ...), so a caller can never supply SQL, a FROM
clause, a WHERE clause, a procedure name to execute, or any other
arbitrary expression.
Two kinds of SQL exist in this module:
1. Fixed, module-owned catalog queries (``_QUERIES`` below) against
``RDB$...``/``MON$...`` system tables. These never contain caller
input -- they are plain string literals written by this project --
so a lexical "is this SQL safe" check is unnecessary and is
deliberately NOT applied to them (see ``gti_discovery.read_only``'s
module docstring for why that check is not trustworthy as a
boundary anyway). They may freely use CAST, COUNT, SUM, JOIN, or any
other legitimate Firebird SQL.
2. Exactly one dynamic query, built by ``sample_relation_rows``. Its
only variable parts are a relation name and column names, and both
are required to have been produced by ``list_relations()`` /
``get_relation_columns()`` of THIS SAME catalog instance in THIS
SAME session first (see ``_KnownCatalog`` below) -- never taken from
raw caller text. Firebird identifier quoting is applied, and the row
limit is a validated, hard-capped integer embedded as a literal
(ints cannot carry SQL syntax). Nothing about this query depends on
``gti_discovery.read_only``.
The exact adapter type must be present in the private approved-adapter
registry before construction and must declare database-level read-only
enforcement. The handle is then held privately; ``__getattribute__``
refuses to hand it back through normal attribute access, so a caller
holding a ``FirebirdCatalog`` has no path to raw ``execute``.
"""
from __future__ import annotations
from collections.abc import Mapping
from copy import deepcopy
from types import MappingProxyType
from typing import Any
from .collector import ReadOnlyCatalogReader, _fetch_rows, _safe_sample_rows
from . import firebird_queries as _firebird_queries
from .firebird_queries import _SamplingCapability, _execute_catalog
from .identifiers import quote_firebird_identifier
MAX_SAMPLE_ROWS = 50
_quote_identifier = quote_firebird_identifier
def _freeze_snapshot(value: Any) -> Any:
if isinstance(value, Mapping):
return MappingProxyType({deepcopy(key): _freeze_snapshot(item) for key, item in value.items()})
if isinstance(value, (list, tuple)):
return tuple(_freeze_snapshot(item) for item in value)
return deepcopy(value)
def _freeze_catalog_rows(rows: list[Any]) -> tuple[Any, ...]:
return tuple(_freeze_snapshot(row) for row in rows)
class StructuredFirebirdReader(ReadOnlyCatalogReader):
"""The only read-only surface handed to collectors/consumers.
Constructed only by ``gti_discovery.firebird_connection`` from a
driver adapter that has already *proven* database-level read-only
enforcement (``adapter.read_only is True``) and is present in the
private exact-type approval registry. No public constructor path accepts
an unapproved or unproven adapter.
"""
__slots__ = ("_handle", "_relations_cache", "_fields_cache")
def __init__(self, handle: Any):
if type(handle) not in _firebird_queries._APPROVED_ADAPTER_TYPES:
raise TypeError("adapter is not an approved implementation")
if callable(handle) or not callable(getattr(handle, "execute", None)):
raise TypeError("adapter must expose an execute method")
if getattr(handle, "read_only", None) is not True:
raise ValueError("handle must declare database-level read-only enforcement")
object.__setattr__(self, "_handle", handle)
object.__setattr__(self, "_relations_cache", None)
object.__setattr__(self, "_fields_cache", None)
def __getattribute__(self, name: str) -> Any:
if name in ("_handle",):
raise AttributeError(name)
return object.__getattribute__(self, name)
def __setattr__(self, name: str, value: Any) -> None:
raise AttributeError("FirebirdCatalog is immutable outside __init__")
@property
def read_only(self) -> bool:
return True
def close(self) -> None:
handle = object.__getattribute__(self, "_handle")
close = getattr(handle, "close", None)
if callable(close):
close()
def _run_fixed_catalog(self, operation_key: str) -> list[Any]:
if operation_key not in ("database", "relations", "fields", "keys", "indexes", "foreign_keys", "triggers", "procedures", "procedure_parameters", "packages", "generators", "dependencies", "monitoring"):
raise ValueError("operation is not a fixed catalog operation")
handle = object.__getattribute__(self, "_handle")
return _fetch_rows(_execute_catalog(handle, operation_key))
# -- bulk catalog reads (fixed, project-owned queries) --------------
def get_database_info(self):
return self._run_fixed_catalog("database")
def list_relations(self):
rows = _freeze_catalog_rows(self._run_fixed_catalog("relations"))
object.__setattr__(self, "_relations_cache", rows)
return rows
def list_relation_fields(self):
rows = _freeze_catalog_rows(self._run_fixed_catalog("fields"))
object.__setattr__(self, "_fields_cache", rows)
return rows
def list_keys(self):
return self._run_fixed_catalog("keys")
def list_indexes(self):
return self._run_fixed_catalog("indexes")
def list_foreign_keys(self):
return self._run_fixed_catalog("foreign_keys")
def list_triggers(self):
return self._run_fixed_catalog("triggers")
def list_procedures(self):
return self._run_fixed_catalog("procedures")
def list_procedure_parameters(self):
return self._run_fixed_catalog("procedure_parameters")
def list_packages(self):
return self._run_fixed_catalog("packages")
def list_generators(self):
return self._run_fixed_catalog("generators")
def list_dependencies(self):
return self._run_fixed_catalog("dependencies")
def list_monitoring(self):
return self._run_fixed_catalog("monitoring")
# -- relation-scoped convenience reads (pure Python filters over ----
# -- the same bulk, project-owned queries above -- no new SQL) ------
def _known_relation_names(self) -> set[str]:
cached = object.__getattribute__(self, "_relations_cache")
rows = cached if cached is not None else self.list_relations()
return {str(row.get("RELATION_NAME", "")).strip().upper() for row in rows}
def _require_known_relation(self, relation: str) -> str:
"""Phase 4: a relation name is only usable if it was itself
returned by RDB$RELATIONS first -- never trusted as free text.
This structurally excludes procedures/UDFs/expressions, since
RDB$RELATIONS only lists real tables and views."""
if not isinstance(relation, str) or not relation.strip():
raise ValueError("relation must be a non-empty string")
candidate = relation.strip().upper()
if candidate not in self._known_relation_names():
raise ValueError(f"unknown relation: {relation!r} was not returned by list_relations()")
rows = object.__getattribute__(self, "_relations_cache")
for row in rows or ():
issued = str(row.get("RELATION_NAME", "")).strip()
if issued.upper() == candidate:
return issued
raise ValueError(f"unknown relation: {relation!r} was not returned by list_relations()")
def get_relation_columns(self, relation: str) -> tuple[dict, ...]:
known = self._require_known_relation(relation)
cached = object.__getattribute__(self, "_fields_cache")
rows = cached if cached is not None else self.list_relation_fields()
return tuple(row for row in rows if str(row.get("RELATION_NAME", "")).strip().upper() == known.upper())
def get_primary_keys(self, relation: str) -> list[dict]:
known = self._require_known_relation(relation)
return [
row for row in self.list_keys()
if str(row.get("RELATION_NAME", "")).strip().upper() == known
and "PRIMARY" in str(row.get("CONSTRAINT_TYPE", "")).upper()
]
def get_foreign_keys(self, relation: str) -> list[dict]:
known = self._require_known_relation(relation)
return [
row for row in self.list_foreign_keys()
if str(row.get("CHILD_RELATION_NAME", "")).strip().upper() == known
or str(row.get("REFERENCED_RELATION_NAME", "")).strip().upper() == known
]
def get_indices(self, relation: str) -> list[dict]:
known = self._require_known_relation(relation)
return [row for row in self.list_indexes() if str(row.get("RELATION_NAME", "")).strip().upper() == known]
def get_triggers(self, relation: str | None = None) -> list[dict]:
rows = self.list_triggers()
if relation is None:
return rows
known = self._require_known_relation(relation)
return [row for row in rows if str(row.get("RELATION_NAME", "")).strip().upper() == known]
def get_procedures(self) -> list[dict]:
"""Metadata only -- procedures are inventoried, never executed
(Mission 00D Phase 5). There is no operation anywhere in this
class that runs a procedure or a selectable stored procedure."""
return self.list_procedures()
def get_procedure_parameters(self) -> list[dict]:
return self.list_procedure_parameters()
def get_packages(self) -> list[dict]:
return self.list_packages()
def get_generators(self) -> list[dict]:
return self.list_generators()
def get_dependencies(self) -> list[dict]:
return self.list_dependencies()
def get_monitoring(self) -> list[dict]:
return self.list_monitoring()
# -- the one dynamic operation: bounded, catalog-validated sampling -
def sample_relation_rows(self, relation: str, columns: list[str], limit: int) -> list[dict]:
"""Sample rows from a real table/view, never a procedure/UDF/expression.
Every safety property below is structural, not textual:
- ``relation`` must already appear in ``list_relations()`` (so it is
a real table/view -- RDB$RELATIONS never lists procedures/UDFs).
- each of ``columns`` must already appear in
``get_relation_columns(relation)`` for that exact relation.
- ``limit`` must be a plain, non-bool ``int`` within
``1..MAX_SAMPLE_ROWS`` and is embedded only as a literal integer
(an int value cannot carry SQL syntax).
- identifiers are Firebird double-quoted with embedded quotes
doubled before being placed in the SQL text.
"""
known_relation = self._require_known_relation(relation)
if not isinstance(columns, (list, tuple)) or not columns:
raise ValueError("columns must be a non-empty list of column names")
available = {
str(row.get("FIELD_NAME", "")).strip().upper(): str(row.get("FIELD_NAME", "")).strip()
for row in self.get_relation_columns(known_relation)
}
validated_columns: list[str] = []
for column in columns:
if not isinstance(column, str) or not column.strip():
raise ValueError("column names must be non-empty strings")
candidate = column.strip().upper()
if candidate not in available:
raise ValueError(f"unknown column {column!r} for relation {relation!r}")
validated_columns.append(available[candidate])
if type(limit) is not int:
raise ValueError("limit must be an int")
if not (1 <= limit <= MAX_SAMPLE_ROWS):
raise ValueError(f"limit must be between 1 and {MAX_SAMPLE_ROWS}")
return _safe_sample_rows(
_fetch_rows(_execute_catalog(
object.__getattribute__(self, "_handle"),
"sample_relation",
(known_relation, tuple(validated_columns), limit),
capability=_SamplingCapability(
object.__getattribute__(self, "_relations_cache"),
object.__getattribute__(self, "_fields_cache"),
),
))
)
FirebirdCatalog = StructuredFirebirdReader
+333
View File
@@ -0,0 +1,333 @@
"""Predefined, read-only Firebird catalog metadata collection.
Mission 00D: the concrete reader consumed here is
``gti_discovery.catalog.FirebirdCatalog`` -- a closed API with no
``execute(sql)``-shaped method at all. ``collect_metadata`` only ever
calls the narrow, named operations declared on ``ReadOnlyCatalogReader``
below; it has no path to send arbitrary SQL even if a caller wanted it
to. The private immutable catalog operation map contains fixed,
project-owned strings that ``FirebirdCatalog`` alone executes -- they never depend on the offline
lexical checker in ``gti_discovery.read_only``, which is no longer part
of the runtime path (see that module's docstring).
"""
from collections.abc import Mapping
import json
from types import MappingProxyType
from typing import Any
_CATALOG_QUERIES = MappingProxyType({
"database": "SELECT MON$DATABASE_NAME, MON$ODS_MAJOR, MON$ODS_MINOR FROM MON$DATABASE",
"relations": "SELECT RDB$RELATION_NAME AS RELATION_NAME, RDB$RELATION_ID AS RELATION_ID, RDB$SYSTEM_FLAG AS SYSTEM_FLAG FROM RDB$RELATIONS",
"fields": "SELECT RF.RDB$RELATION_NAME AS RELATION_NAME, RF.RDB$FIELD_NAME AS FIELD_NAME, RF.RDB$FIELD_POSITION AS FIELD_POSITION, RF.RDB$NULL_FLAG AS NULL_FLAG, F.RDB$FIELD_TYPE AS FIELD_TYPE, F.RDB$FIELD_SUB_TYPE AS FIELD_SUB_TYPE, F.RDB$FIELD_LENGTH AS FIELD_LENGTH, F.RDB$FIELD_PRECISION AS FIELD_PRECISION, F.RDB$FIELD_SCALE AS FIELD_SCALE, T.RDB$TYPE_NAME AS TYPE_NAME FROM RDB$RELATION_FIELDS RF JOIN RDB$FIELDS F ON F.RDB$FIELD_NAME = RF.RDB$FIELD_SOURCE LEFT JOIN RDB$TYPES T ON T.RDB$TYPE = F.RDB$FIELD_TYPE AND T.RDB$FIELD_NAME = 'RDB$FIELD_TYPE'",
"keys": "SELECT RC.RDB$CONSTRAINT_NAME AS CONSTRAINT_NAME, RC.RDB$RELATION_NAME AS RELATION_NAME, RC.RDB$CONSTRAINT_TYPE AS CONSTRAINT_TYPE, RC.RDB$INDEX_NAME AS INDEX_NAME, S.RDB$FIELD_NAME AS FIELD_NAME, S.RDB$FIELD_POSITION AS FIELD_POSITION FROM RDB$RELATION_CONSTRAINTS RC LEFT JOIN RDB$INDEX_SEGMENTS S ON S.RDB$INDEX_NAME = RC.RDB$INDEX_NAME",
"indexes": "SELECT I.RDB$INDEX_NAME AS INDEX_NAME, I.RDB$RELATION_NAME AS RELATION_NAME, I.RDB$UNIQUE_FLAG AS UNIQUE_FLAG, I.RDB$INDEX_TYPE AS INDEX_TYPE, S.RDB$FIELD_NAME AS FIELD_NAME, S.RDB$FIELD_POSITION AS FIELD_POSITION FROM RDB$INDICES I LEFT JOIN RDB$INDEX_SEGMENTS S ON S.RDB$INDEX_NAME = I.RDB$INDEX_NAME",
"foreign_keys": "SELECT C.RDB$CONSTRAINT_NAME AS CONSTRAINT_NAME, C.RDB$RELATION_NAME AS CHILD_RELATION_NAME, C.RDB$INDEX_NAME AS CHILD_INDEX_NAME, CS.RDB$FIELD_NAME AS CHILD_FIELD_NAME, CS.RDB$FIELD_POSITION AS CHILD_FIELD_POSITION, U.RDB$RELATION_NAME AS REFERENCED_RELATION_NAME, U.RDB$INDEX_NAME AS REFERENCED_INDEX_NAME, US.RDB$FIELD_NAME AS REFERENCED_FIELD_NAME, US.RDB$FIELD_POSITION AS REFERENCED_FIELD_POSITION, R.RDB$UPDATE_RULE AS UPDATE_RULE, R.RDB$DELETE_RULE AS DELETE_RULE FROM RDB$REF_CONSTRAINTS R JOIN RDB$RELATION_CONSTRAINTS C ON C.RDB$CONSTRAINT_NAME = R.RDB$CONSTRAINT_NAME JOIN RDB$RELATION_CONSTRAINTS U ON U.RDB$CONSTRAINT_NAME = R.RDB$CONST_NAME_UQ LEFT JOIN RDB$INDEX_SEGMENTS CS ON CS.RDB$INDEX_NAME = C.RDB$INDEX_NAME LEFT JOIN RDB$INDEX_SEGMENTS US ON US.RDB$INDEX_NAME = U.RDB$INDEX_NAME AND US.RDB$FIELD_POSITION = CS.RDB$FIELD_POSITION",
"triggers": "SELECT RDB$TRIGGER_NAME AS TRIGGER_NAME, RDB$RELATION_NAME AS RELATION_NAME, RDB$TRIGGER_TYPE AS TRIGGER_TYPE, RDB$TRIGGER_SEQUENCE AS TRIGGER_SEQUENCE, RDB$TRIGGER_INACTIVE AS TRIGGER_INACTIVE FROM RDB$TRIGGERS",
"procedures": "SELECT RDB$PROCEDURE_NAME AS PROCEDURE_NAME, RDB$PROCEDURE_ID AS PROCEDURE_ID FROM RDB$PROCEDURES",
"procedure_parameters": "SELECT RDB$PROCEDURE_NAME AS PROCEDURE_NAME, RDB$PARAMETER_NAME AS PARAMETER_NAME, RDB$PARAMETER_NUMBER AS PARAMETER_NUMBER, RDB$PARAMETER_TYPE AS PARAMETER_TYPE, RDB$FIELD_TYPE AS FIELD_TYPE, RDB$FIELD_SUB_TYPE AS FIELD_SUB_TYPE, RDB$FIELD_LENGTH AS FIELD_LENGTH, RDB$FIELD_PRECISION AS FIELD_PRECISION, RDB$FIELD_SCALE AS FIELD_SCALE FROM RDB$PROCEDURE_PARAMETERS",
"packages": "SELECT RDB$PACKAGE_NAME AS PACKAGE_NAME, RDB$PACKAGE_ID AS PACKAGE_ID FROM RDB$PACKAGES",
"generators": "SELECT RDB$GENERATOR_NAME AS GENERATOR_NAME, RDB$GENERATOR_ID AS GENERATOR_ID, RDB$SYSTEM_FLAG AS SYSTEM_FLAG FROM RDB$GENERATORS",
"dependencies": "SELECT RDB$DEPENDENT_NAME AS DEPENDENT_NAME, RDB$DEPENDED_ON_NAME AS DEPENDED_ON_NAME, RDB$DEPENDENT_TYPE AS DEPENDENT_TYPE, RDB$DEPENDED_ON_TYPE AS DEPENDED_ON_TYPE, RDB$FIELD_NAME AS FIELD_NAME FROM RDB$DEPENDENCIES",
"monitoring": "SELECT MON$ATTACHMENT_ID AS ATTACHMENT_ID, MON$SERVER_PID AS SERVER_PID, MON$STATE AS STATE, MON$TIMESTAMP AS TIMESTAMP FROM MON$ATTACHMENTS",
})
_SENSITIVE_WORDS = (
"PASSWORD",
"SECRET",
"TOKEN",
"CREDENTIAL",
"AUTH",
"BLOB",
"SOURCE",
"DEFAULT",
"BODY",
)
_MAX_SAFE_STRING = 1024
_SAMPLE_SENSITIVE_WORDS = _SENSITIVE_WORDS + (
"CUSTOMER", "CLIENT", "PRICE", "AMOUNT", "COST", "EMAIL", "PHONE",
"ADDRESS", "TAX", "CPF", "CNPJ",
)
_FIXTURE_REGISTRATION_TOKEN = object()
_APPROVED_FIXTURE_TYPES = MappingProxyType({})
def _approved_fixture_reader(reader_type: type | None = None, *, token: object) -> type:
"""Register one exact fixture type through the private test capability."""
if token is not _FIXTURE_REGISTRATION_TOKEN:
raise TypeError("fixture registration is private")
def register(candidate: type) -> type:
global _APPROVED_FIXTURE_TYPES
if not isinstance(candidate, type):
raise TypeError("fixture registration requires a type")
_APPROVED_FIXTURE_TYPES = MappingProxyType(
{**_APPROVED_FIXTURE_TYPES, candidate: True}
)
return candidate
return register(reader_type) if reader_type is not None else register
class ReadOnlyCatalogReader:
"""Approved structured reader contract used by the collector."""
__slots__ = ()
def get_database_info(self):
return self.list_database()
def list_database(self):
raise NotImplementedError
def list_relations(self):
raise NotImplementedError
def get_relation_columns(self, relation):
return [row for row in self.list_relation_fields() if row.get("RELATION_NAME", "").strip().upper() == str(relation).strip().upper()]
def get_primary_keys(self, relation):
return [row for row in self.list_keys() if row.get("RELATION_NAME", "").strip().upper() == str(relation).strip().upper()]
def get_foreign_keys(self, relation):
known = str(relation).strip().upper()
return [row for row in self.list_foreign_keys() if known in {
str(row.get("CHILD_RELATION_NAME", "")).strip().upper(),
str(row.get("REFERENCED_RELATION_NAME", "")).strip().upper(),
}]
def get_indices(self, relation):
return [row for row in self.list_indexes() if row.get("RELATION_NAME", "").strip().upper() == str(relation).strip().upper()]
def get_triggers(self):
return self.list_triggers()
def get_procedures(self):
return self.list_procedures()
def get_procedure_parameters(self):
return self.list_procedure_parameters()
def get_packages(self):
return self.list_packages()
def get_generators(self):
return self.list_generators()
def get_dependencies(self):
return self.list_dependencies()
def get_monitoring(self):
return self.list_monitoring()
def _safe_value(value: Any) -> Any:
if isinstance(value, (bytes, bytearray, memoryview)):
return "<redacted>"
if isinstance(value, Mapping):
return {
str(key): _safe_value(item)
for key, item in sorted(value.items(), key=lambda pair: str(pair[0]))
if not any(word in str(key).upper() for word in _SENSITIVE_WORDS)
}
if isinstance(value, (list, tuple)):
return [_safe_value(item) for item in value]
if isinstance(value, str):
upper_value = value.upper()
if any(word in upper_value for word in _SENSITIVE_WORDS):
return "<redacted>"
return value if len(value) <= _MAX_SAFE_STRING else "<redacted>"
if isinstance(value, (int, float, bool)) or value is None:
return value
return "<redacted>"
def _safe_sample_rows(rows: Any) -> list[dict[str, Any]]:
"""Keep sampled evidence flat, scalar, bounded, and mapping-relevant."""
sanitized = []
for row in rows if isinstance(rows, list) else []:
if not isinstance(row, Mapping):
continue
safe_row = {}
for key, value in row.items():
name = str(key)
upper_name = name.upper()
if "BLOB" in upper_name:
safe_row[name] = "<redacted>"
continue
if any(word in upper_name for word in _SAMPLE_SENSITIVE_WORDS):
continue
if isinstance(value, (str, int, float, bool)) or value is None:
safe_row[name] = _safe_value(value)
sanitized.append(safe_row)
return sanitized
def _row_sort_key(row: Any) -> str:
return json.dumps(_safe_value(row), ensure_ascii=True, sort_keys=True, separators=(",", ":"))
def _sort_rows(rows: Any) -> Any:
if isinstance(rows, list):
return sorted(rows, key=_row_sort_key)
return rows
def _is_optional_unsupported(name: str, error: Exception) -> bool:
if isinstance(error, PermissionError):
return False
if name not in {"packages", "procedure_parameters", "generators", "dependencies", "monitoring"}:
return False
if isinstance(error, AttributeError):
return True
message = str(error).lower()
if any(
marker in message
for marker in (
"permission",
"privilege",
"access denied",
"not authorized",
"unauthorized",
"authorization",
)
):
return False
if isinstance(error, NotImplementedError):
return True
return any(
marker in message
for marker in (
"table unknown",
"column unknown",
"does not exist",
"not found",
"unsupported",
"not supported",
"not implemented",
"capability unavailable",
)
)
def _fetch_rows(result: Any) -> list[Any]:
description = getattr(result, "description", None)
if hasattr(result, "fetchall"):
result = result.fetchall()
rows = list(result) if result is not None else []
if description:
names = [str(column[0]) for column in description]
return [
dict(zip(names, row)) if isinstance(row, tuple) else row
for row in rows
]
return rows
def _optional_operation(reader: ReadOnlyCatalogReader, method_name: str):
try:
operation = getattr(reader, method_name)
except AttributeError:
return None
if method_name.startswith("get_") and getattr(type(reader), method_name, None) is getattr(ReadOnlyCatalogReader, method_name, None):
try:
getattr(reader, "list_" + method_name.removeprefix("get_"))
except AttributeError:
return None
return operation
def collect_metadata(reader: ReadOnlyCatalogReader) -> dict:
"""Collect catalog rows through the approved structured reader only."""
from .catalog import StructuredFirebirdReader
if not isinstance(reader, ReadOnlyCatalogReader):
raise TypeError("reader must be ReadOnlyCatalogReader")
if any(callable(getattr(reader, name, None)) for name in ("execute", "query", "raw_execute", "run_sql")):
raise TypeError("reader must not expose raw execution")
is_approved_fixture = type(reader) in _APPROVED_FIXTURE_TYPES
structured_methods = (
"get_database_info", "get_relation_columns", "get_primary_keys",
"get_foreign_keys", "get_indices", "get_triggers", "get_procedures",
"get_procedure_parameters", "get_packages", "get_generators", "get_dependencies",
"get_monitoring",
)
approved_implementations = {
getattr(ReadOnlyCatalogReader, name)
for name in structured_methods
} | {
getattr(StructuredFirebirdReader, name)
for name in structured_methods
}
if not is_approved_fixture and (
type(reader) is not StructuredFirebirdReader
or any(getattr(type(reader), name, None) not in approved_implementations for name in structured_methods)
):
raise TypeError("reader must use the sealed structured operations")
metadata = {}
errors = []
unavailable = ["events"]
relations = []
def run(name, operation, *, optional=False):
try:
return operation()
except Exception as error:
kind = "optional_unsupported" if optional and _is_optional_unsupported(name, error) else "query_error"
errors.append({"query": name, "kind": kind, "error": type(error).__name__})
if kind == "optional_unsupported":
unavailable.append(name)
return []
metadata["database"] = run("database", reader.get_database_info)
relations = run("relations", reader.list_relations)
metadata["relations"] = relations
metadata["fields"] = []
def extend_unique(target, rows):
for row in rows:
if row not in target:
target.append(row)
for relation_row in relations:
if not isinstance(relation_row, Mapping):
continue
relation = relation_row.get("RELATION_NAME", relation_row.get("relation_name"))
if relation is None:
continue
extend_unique(metadata["fields"], run("fields", lambda relation=relation: reader.get_relation_columns(relation)))
metadata["keys"] = []
metadata["foreign_keys"] = []
metadata["indexes"] = []
for relation_row in relations:
if not isinstance(relation_row, Mapping):
continue
relation = relation_row.get("RELATION_NAME", relation_row.get("relation_name"))
if relation is None:
continue
extend_unique(metadata["keys"], run("keys", lambda relation=relation: reader.get_primary_keys(relation)))
extend_unique(metadata["foreign_keys"], run("foreign_keys", lambda relation=relation: reader.get_foreign_keys(relation)))
extend_unique(metadata["indexes"], run("indexes", lambda relation=relation: reader.get_indices(relation)))
operations = {
"triggers": (reader.get_triggers, False),
"procedures": (reader.get_procedures, False),
"procedure_parameters": (_optional_operation(reader, "get_procedure_parameters"), True),
"packages": (_optional_operation(reader, "get_packages"), True),
"generators": (_optional_operation(reader, "get_generators"), True),
"dependencies": (_optional_operation(reader, "get_dependencies"), True),
"monitoring": (_optional_operation(reader, "get_monitoring"), True),
}
for name, (operation, optional) in operations.items():
if operation is None or not callable(operation):
metadata[name] = []
unavailable.append(name)
continue
metadata[name] = run(name, operation, optional=optional)
for name, rows in list(metadata.items()):
metadata[name] = _safe_value(_sort_rows(rows))
metadata["events"] = []
metadata["unavailable"] = sorted(set(unavailable))
metadata["errors"] = sorted((_safe_value(error) for error in errors), key=_row_sort_key)
metadata["incomplete"] = bool(errors or metadata["unavailable"])
return metadata
+16
View File
@@ -0,0 +1,16 @@
"""Narrow, sanitized company context discovery."""
def collect_company_context(reader):
"""Read only the catalog-approved company UF/regime projection; never CNPJ."""
method = getattr(reader, "company_context_rows", None)
if not callable(method):
raise TypeError("reader must expose company_context_rows")
rows = method()
if not isinstance(rows, list) or not rows or any(not isinstance(row, dict) for row in rows):
raise ValueError("company context is unavailable")
ufs = {str(row.get("UF", "")).strip().upper() for row in rows}
regimes = {str(row.get("REGIME", "")).strip() for row in rows}
if len(ufs) != 1 or len(regimes) != 1 or not next(iter(ufs)) or not next(iter(regimes)):
raise ValueError("company context is ambiguous")
return {"uf": next(iter(ufs)), "regime": next(iter(regimes)), "cnpj": "REDACTED", "record_count": len(rows)}
+59
View File
@@ -0,0 +1,59 @@
"""Canonical schema fingerprints and conservative classification."""
import hashlib
import json
import re
from collections.abc import Mapping
from typing import Any
SCHEMA_SUPPORTED = "SCHEMA_SUPPORTED"
SCHEMA_CHANGED = "SCHEMA_CHANGED"
SCHEMA_UNKNOWN = "SCHEMA_UNKNOWN"
_SHA256 = re.compile(r"\A[0-9a-f]{64}\Z")
_REDACTED_WORDS = ("PASSWORD", "SECRET", "TOKEN", "CREDENTIAL", "AUTH", "BLOB", "SOURCE", "DEFAULT", "BODY")
_UNORDERED_COLLECTIONS = {"tables", "relations", "fields", "keys", "indexes", "foreign_keys", "triggers", "procedures", "procedure_parameters", "packages", "generators", "dependencies", "monitoring", "events"}
_POSITIONAL_COLLECTIONS = {"columns", "segments", "field_order", "positions"}
def _canonical(value: Any, key: str | None = None) -> Any:
if isinstance(value, Mapping):
return {
str(name): _canonical(item, str(name).lower())
for name, item in sorted(value.items(), key=lambda pair: str(pair[0]))
if not any(word in str(name).upper() for word in _REDACTED_WORDS)
}
if isinstance(value, (list, tuple, set, frozenset)):
items = [_canonical(item) for item in value]
if key in _UNORDERED_COLLECTIONS and key not in _POSITIONAL_COLLECTIONS:
return sorted(items, key=lambda item: json.dumps(item, ensure_ascii=True, sort_keys=True, separators=(",", ":")))
return items
if isinstance(value, (bytes, bytearray, memoryview)):
return "<redacted>"
if isinstance(value, (str, int, float, bool)) or value is None:
return value if not isinstance(value, str) or len(value) <= 1024 else "<redacted>"
return "<redacted>"
def build_fingerprint(metadata: dict) -> str:
"""Return SHA-256 of recursively canonicalized relevant metadata."""
payload = json.dumps(_canonical(metadata), ensure_ascii=True, sort_keys=True, separators=(",", ":"))
return hashlib.sha256(payload.encode("utf-8")).hexdigest()
def classify_schema(metadata: dict, expected_fingerprints, *, mapping_ambiguous: bool = False) -> str:
"""Classify metadata against a validated allowlist of expected fingerprints."""
if mapping_ambiguous:
return SCHEMA_UNKNOWN
if metadata.get("incomplete") or metadata.get("errors") or metadata.get("unavailable"):
return SCHEMA_UNKNOWN
try:
allowlist = (
{expected_fingerprints}
if isinstance(expected_fingerprints, str)
else set(expected_fingerprints)
)
except Exception:
return SCHEMA_UNKNOWN
if not allowlist or not all(isinstance(item, str) and _SHA256.fullmatch(item) for item in allowlist):
return SCHEMA_UNKNOWN
return SCHEMA_SUPPORTED if build_fingerprint(metadata) in allowlist else SCHEMA_CHANGED
+141
View File
@@ -0,0 +1,141 @@
"""Authorized, fail-closed Firebird connection boundary."""
import os
from pathlib import Path
from types import MappingProxyType
from typing import Any
from .catalog import FirebirdCatalog
from .firebird_queries import _ADAPTER_REGISTRATION_TOKEN, _register_approved_adapter
AUTHORIZED_DATABASE_PATH = Path(
r"D:\PROJETOS\GTISISTEMAS\Projeto\TESTESOFICIAIS.FDB"
)
class _FirebirdAdapter:
"""Sealed adapter implementation used by the authorized connection."""
__slots__ = ("_connection", "_read_only_evidence", "_seal")
def __init__(self, connection: Any, read_only_evidence: dict[str, Any], seal: object):
if seal is not _ADAPTER_CREATION_TOKEN:
raise TypeError("adapter construction is private")
self._connection = connection
self._read_only_evidence = MappingProxyType(dict(read_only_evidence))
self._seal = seal
@property
def read_only(self) -> bool:
return self.read_only_evidence.get("accepted") is True
@property
def read_only_evidence(self):
return self._read_only_evidence
def execute(self, sql: str, params: tuple = ()):
cursor = self._connection.cursor()
cursor.execute(sql, params)
return cursor
def close(self):
self._connection.close()
_register_approved_adapter(_FirebirdAdapter, _ADAPTER_REGISTRATION_TOKEN)
_ADAPTER_CREATION_TOKEN = object()
def _create_firebird_adapter(connection: Any, read_only_evidence: dict[str, Any], seal: object) -> _FirebirdAdapter:
if seal is not _ADAPTER_CREATION_TOKEN:
raise TypeError("adapter construction is private")
return _FirebirdAdapter(connection, read_only_evidence, seal)
def _normalized_path(path: Path) -> str:
return os.path.normcase(os.path.normpath(str(path.resolve(strict=False))))
def validate_authorized_database_path(path: os.PathLike[str] | str) -> Path:
"""Return the resolved authorized target or reject it before driver use."""
candidate = Path(path).resolve(strict=False)
authorized = AUTHORIZED_DATABASE_PATH.resolve(strict=False)
if _normalized_path(candidate) != _normalized_path(authorized):
raise ValueError("database path is not the authorized target")
if not authorized.is_file() or not candidate.is_file():
raise ValueError("authorized database path must be a regular file")
return authorized
def validate_authorized_database_identity(
identity: Any, path: os.PathLike[str] | str = AUTHORIZED_DATABASE_PATH
) -> str:
"""Reject an engine identity that does not identify the authorized target."""
reported = str(identity or "").strip().strip('"')
if not reported:
raise ValueError("database identity is unavailable")
authorized = Path(path).resolve(strict=False)
if any(separator in reported for separator in ("/", "\\")) or ":" in reported:
if _normalized_path(Path(reported)) != _normalized_path(authorized):
raise ValueError("database identity does not match authorized target")
elif os.path.normcase(reported) != os.path.normcase(authorized.name):
raise ValueError("database identity does not match authorized target")
return reported
def connect_authorized_read_only(
path: os.PathLike[str] | str,
driver: Any,
client_library: str | None = None,
) -> FirebirdCatalog:
"""Connect only to the authorized file with proven driver read-only mode.
Returns a ``FirebirdCatalog`` -- the closed, structured read-only API
(Mission 00D). No object with a public ``execute(sql)`` is ever handed
back to the caller; there is no arbitrary-SQL path from here on.
"""
database = validate_authorized_database_path(path)
options = {"database": str(database), "read_only": True}
if client_library is not None:
options["client_library"] = client_library
raw_connection = driver.connect(**options)
try:
if not isinstance(raw_connection, _FirebirdAdapter) or raw_connection._seal is not _ADAPTER_CREATION_TOKEN:
raise ValueError("driver did not return a trusted sealed read-only adapter")
evidence = raw_connection.read_only_evidence
if (
raw_connection.read_only is not True
or not isinstance(evidence, MappingProxyType)
or evidence.get("accepted") is not True
or evidence.get("driver_api") != "fdb.Connection.default_tpb"
or evidence.get("configured_tpb") != b"\x03\x08\x06\x0f\x11"
or evidence.get("observed_default_tpb") != evidence.get("configured_tpb")
or getattr(raw_connection._connection, "default_tpb", None) != evidence.get("configured_tpb")
):
raise ValueError("driver did not prove read-only enforcement")
return FirebirdCatalog(raw_connection)
except BaseException:
try:
close = getattr(raw_connection, "close", None)
if callable(close):
close()
except BaseException:
pass
raise
def get_database_identity(catalog: FirebirdCatalog) -> Any:
"""Read the database identity through the closed catalog API."""
if not isinstance(catalog, FirebirdCatalog):
raise TypeError("catalog must be FirebirdCatalog")
rows = catalog.get_database_info()
if not rows:
return None
row = rows[0]
if isinstance(row, dict):
for key, value in row.items():
if str(key).upper() == "MON$DATABASE_NAME":
return value
return None
return row[0] if row else None
+110
View File
@@ -0,0 +1,110 @@
"""Private operation-key boundary for project-owned Firebird catalog reads.
The driver can receive SQL only after this module has selected a query from
the immutable, module-owned operation map. Callers provide an operation key,
never SQL text or SQL fragments.
"""
from typing import Any
from types import MappingProxyType
from .collector import _CATALOG_QUERIES
from .identifiers import quote_firebird_identifier
_SAMPLE_OPERATION = "sample_relation"
_MAX_SAMPLE_ROWS = 50
_ADAPTER_REGISTRATION_TOKEN = object()
_APPROVED_ADAPTER_TYPES = MappingProxyType({})
_INTERNAL_QUERIES = MappingProxyType({
"expression_probe": (
"SELECT CAST(COUNT(*) AS INTEGER), SUM(MON$ODS_MAJOR), MIN(MON$ODS_MAJOR), "
"MAX(MON$ODS_MAJOR), COALESCE(MON$ODS_MINOR, 0) "
"FROM MON$DATABASE GROUP BY MON$ODS_MINOR"
),
})
def _register_approved_adapter(adapter_type: type, token: object) -> None:
"""Register an exact adapter type using the private module capability."""
global _APPROVED_ADAPTER_TYPES
if token is not _ADAPTER_REGISTRATION_TOKEN or not isinstance(adapter_type, type):
raise TypeError("adapter registration is private")
_APPROVED_ADAPTER_TYPES = MappingProxyType(
{**_APPROVED_ADAPTER_TYPES, adapter_type: True}
)
class _SamplingCapability:
"""Immutable proof that identifiers came from one reader snapshot."""
__slots__ = ("_relations", "_columns")
def __init__(self, relations, fields):
relation_names = {
str(row.get("RELATION_NAME", "")).strip().upper()
for row in relations
}
columns = {}
for row in fields:
relation = str(row.get("RELATION_NAME", "")).strip().upper()
field = str(row.get("FIELD_NAME", "")).strip()
columns.setdefault(relation, set()).add(field)
object.__setattr__(self, "_relations", frozenset(relation_names))
object.__setattr__(self, "_columns", {
relation: frozenset(names) for relation, names in columns.items()
})
def __setattr__(self, name, value):
raise AttributeError("sampling capability is immutable")
def allows(self, relation: str, columns: tuple[str, ...]) -> bool:
relation_key = relation.strip().upper()
return (
relation_key in self._relations
and set(columns).issubset(self._columns.get(relation_key, frozenset()))
)
def _execute_catalog(
adapter: Any,
operation_key: str,
params: tuple = (),
*,
capability: _SamplingCapability | None = None,
) -> Any:
"""Execute one approved catalog operation through the private adapter."""
if type(operation_key) is not str:
raise ValueError("operation key must be an exact string")
if operation_key == _SAMPLE_OPERATION:
if (
not isinstance(params, tuple)
or len(params) != 3
or type(params[0]) is not str
or not isinstance(params[1], tuple)
or not params[1]
or not all(type(column) is str for column in params[1])
or any(not column or column != column.strip() for column in params[1])
or type(params[2]) is not int
or not (1 <= params[2] <= _MAX_SAMPLE_ROWS)
or capability is None
or not capability.allows(params[0], params[1])
):
raise ValueError("invalid sample operation parameters")
sql = "SELECT FIRST {} {} FROM {}".format(
params[2],
", ".join(quote_firebird_identifier(column) for column in params[1]),
quote_firebird_identifier(params[0]),
)
elif operation_key in _CATALOG_QUERIES:
sql = _CATALOG_QUERIES[operation_key]
elif operation_key in _INTERNAL_QUERIES:
sql = _INTERNAL_QUERIES[operation_key]
else:
raise ValueError("unknown catalog operation")
if type(adapter) not in _APPROVED_ADAPTER_TYPES:
raise TypeError("adapter is not an approved implementation")
if callable(adapter) or not callable(getattr(adapter, "execute", None)):
raise TypeError("adapter must expose an execute method")
if getattr(adapter, "read_only", None) is not True:
raise ValueError("adapter must declare database-level read-only enforcement")
return adapter.execute(sql, () if operation_key == _SAMPLE_OPERATION else params)
+14
View File
@@ -0,0 +1,14 @@
"""Validation and quoting for identifiers issued by the Firebird catalog."""
from __future__ import annotations
def quote_firebird_identifier(name: str) -> str:
"""Quote one exact catalog identifier, never an SQL expression."""
if not isinstance(name, str) or not name or name != name.strip():
raise ValueError("identifier must be an exact non-empty string")
if any(ord(character) < 32 or ord(character) == 127 for character in name):
raise ValueError("identifier must not contain control characters")
if any(token in name for token in (".", ";", "--", "/*", "*/", "(", ")", "'", ",")):
raise ValueError("identifier must be a single catalog name")
return '"' + name.replace('"', '""') + '"'
+290
View File
@@ -0,0 +1,290 @@
"""Conservative, fixture-driven schema mapping with no database access."""
from collections import defaultdict
from collections.abc import Mapping
from typing import Any
_SENSITIVE = ("PASSWORD", "SECRET", "TOKEN", "CREDENTIAL", "AUTH", "SOURCE", "DEFAULT", "BODY")
_PRODUCT_WORDS = ("PRODUCT", "PRODUTO", "ITEM", "MATERIAL", "SKU", "ARTICLE")
_FISCAL_WORDS = ("NCM", "CEST", "CST", "CSOSN", "CFOP", "ICMS", "PIS", "COFINS", "IPI", "FCP", "ST", "ORIGIN", "RATE", "REDUCTION", "NATURE", "OPERATION", "COMPANY", "BRANCH", "REGIME", "UF", "IAT", "IPPT", "INCIDENCE", "INCIDENCIA")
_MEDIA_WORDS = ("IMAGE", "IMG", "PHOTO", "FILE", "PATH", "URL", "BLOB", "DOCUMENT", "ATTACH")
_FIELD_GROUPS = {
"id": ("ID", "KEY", "IDENTIFIER"),
"code": ("CODE", "CODIGO", "SKU", "INTERNAL"),
"description": ("DESCRIPTION", "DESCRICAO", "NAME", "NOME"),
"barcode": ("BARCODE", "EAN", "GTIN"),
"reference": ("REFERENCE", "REFERENCIA", "REF"),
"unit": ("UNIT", "UNIDADE"),
"category": ("GROUP", "CATEGORY", "GRUPO", "CATEGORIA"),
"brand": ("BRAND", "MARCA", "MANUFACTURER", "FABRICANTE"),
"status": ("STATUS", "ACTIVE", "ATIVO"),
"timestamp": ("CREATED", "UPDATED", "MODIFIED", "CHANGED", "DATE", "TIMESTAMP"),
}
_PRODUCT_SEMANTIC_WORDS = tuple(
word
for group in ("code", "description", "barcode", "reference", "unit", "category", "brand", "status")
for word in _FIELD_GROUPS[group]
)
_PRODUCT_SEMANTIC_WORDS += ("PRODUTO", "ESTOQUE")
def _clean(value: Any, key: str = "") -> Any:
upper = str(key).upper()
if any(word in upper for word in _SENSITIVE):
return "<redacted>"
if isinstance(value, (bytes, bytearray, memoryview)):
return "<redacted>"
if isinstance(value, Mapping):
return {str(k): _clean(v, str(k)) for k, v in value.items() if not any(word in str(k).upper() for word in _SENSITIVE)}
if isinstance(value, (list, tuple, set, frozenset)):
return [_clean(item) for item in value]
if isinstance(value, str):
if any(word in value.upper() for word in _SENSITIVE):
return "<redacted>"
return value if len(value) <= 256 else "<redacted>"
if isinstance(value, (int, float, bool)) or value is None:
return value
return "<redacted>"
def _rows(metadata: Mapping, name: str) -> list[dict]:
value = metadata.get(name, [])
return [dict(item) for item in value if isinstance(item, Mapping)]
def _value(row: Mapping, *names: str, default=None):
lowered = {str(k).lower(): v for k, v in row.items()}
for name in names:
if name.lower() in lowered:
return lowered[name.lower()]
return default
def _field_name(row: Mapping) -> str:
return str(_value(row, "name", "field", "field_name", "rdb$field_name", default="")).strip()
def _relation_name(row: Mapping) -> str:
return str(_value(row, "relation", "relation_name", "table", "table_name", "name", "rdb$relation_name", default="")).strip()
def _sample_rows(samples: Any, relation: str) -> list[dict]:
if isinstance(samples, Mapping):
value = samples.get(relation, samples.get(relation.upper(), []))
else:
value = [row for row in samples or [] if isinstance(row, Mapping) and _relation_name(row).upper() == relation.upper()]
return [dict(row) for row in value if isinstance(row, Mapping)]
def _matches(name: str, words: tuple[str, ...]) -> bool:
upper = name.upper()
return any(word in upper for word in words)
def _primary_keys(keys: list[dict], relation: str) -> list[str]:
found = []
for key in keys:
if _relation_name(key).upper() != relation.upper():
continue
kind = str(_value(key, "constraint_type", "type", default="")).upper()
if "PRIMARY" not in kind and not _value(key, "primary", "is_primary", default=False):
continue
fields = _value(key, "fields", "columns", default=None)
if fields is None:
fields = [_value(key, "field", "field_name", default="")]
found.extend(str(field).strip() for field in fields if str(field).strip())
return list(dict.fromkeys(found))
def _foreign_key(row: Mapping) -> tuple[str, str, str, str]:
return (
str(_value(row, "child_relation", "child_relation_name", default="")).strip(),
str(_value(row, "child_field", "child_field_name", default="")).strip(),
str(_value(row, "referenced_relation", "referenced_relation_name", "parent_relation", default="")).strip(),
str(_value(row, "referenced_field", "referenced_field_name", "parent_field", default="")).strip(),
)
def _classification(*, named: bool, structural: bool = False, data: bool = False) -> str:
if structural and data:
return "CONFIRMED"
if named or structural:
return "INFERRED"
return "UNKNOWN"
def identify_product_candidates(metadata, samples) -> list[dict]:
"""Return sanitized product relation candidates derived solely from fixtures."""
fields = _rows(metadata, "fields")
by_relation = defaultdict(list)
for field in fields:
relation = _relation_name(field)
if relation:
by_relation[relation.upper()].append(field)
candidates = []
for relation_row in _rows(metadata, "relations"):
relation = _relation_name(relation_row)
if not relation:
continue
relation_fields = by_relation[relation.upper()]
pk = _primary_keys(_rows(metadata, "keys"), relation)
sample = _sample_rows(samples, relation)
data = bool(sample and any(any(value is not None for value in row.values()) for row in sample))
named = _matches(relation, _PRODUCT_WORDS)
structural = bool(pk and relation_fields)
semantic_fields = [
_field_name(field)
for field in relation_fields
if _matches(_field_name(field), _PRODUCT_SEMANTIC_WORDS + _FISCAL_WORDS)
]
semantic_sample_fields = [
name
for name in semantic_fields
if any(_value(row, name, default=None) is not None for row in sample)
]
semantic = bool(semantic_fields and semantic_sample_fields)
evidence = {
"primary_key": pk,
"field_count": len(relation_fields),
"semantic_fields": sorted(semantic_fields),
}
if sample:
evidence["sample_fields"] = sorted({str(key) for row in sample for key in row if not any(word in str(key).upper() for word in _SENSITIVE)})
evidence["semantic_sample_fields"] = sorted(semantic_sample_fields)
count = _value(relation_row, "approximate_count", "count", "row_count", default=None)
if count is not None:
evidence["approximate_count"] = _clean(count)
product_semantic = [field for field in semantic_fields if not _matches(field, _FISCAL_WORDS)]
candidates.append({
"relation": relation,
"classification": _classification(named=named or bool(product_semantic), structural=structural and semantic, data=data and semantic),
"evidence": evidence,
})
return candidates
def _field_candidates(fields: list[dict], samples: list[dict], words: tuple[str, ...], *, include_type=False) -> dict:
result = {}
for field in fields:
name = _field_name(field)
if not name or not _matches(name, words) or any(word in name.upper() for word in _SENSITIVE):
continue
has_data = any(_value(row, name, default=None) is not None for row in samples)
item = {"classification": _classification(named=True, data=has_data), "sample_present": has_data}
if include_type:
item["type"] = _clean(_value(field, "type", "type_name", "field_type", default="UNKNOWN"))
subtype = _value(field, "subtype", "field_sub_type", "field_subtype", default=None)
if subtype is not None:
item["subtype"] = _clean(subtype)
result[name] = item
return result
def _relation_fields(metadata: Mapping) -> dict[str, list[dict]]:
grouped = defaultdict(list)
for field in _rows(metadata, "fields"):
relation = _relation_name(field)
if relation:
grouped[relation.upper()].append(field)
return grouped
def _relation_profile(relation: str, fields: list[dict], samples: list[dict], foreign_keys: list[dict], product_relation: str) -> dict:
relationships = []
for fk in foreign_keys:
child, child_field, parent, parent_field = _foreign_key(fk)
if child.upper() == relation.upper() or parent.upper() == relation.upper():
relationships.append({"child_relation": child, "child_field": child_field, "referenced_relation": parent, "referenced_field": parent_field})
mapped_fields = {}
for field in fields:
name = _field_name(field)
if not name or any(word in name.upper() for word in _SENSITIVE):
continue
item = {
"classification": "INFERRED",
"sample_present": any(_value(row, name, default=None) is not None for row in samples),
"type": _clean(_value(field, "type", "type_name", "field_type", default="UNKNOWN")),
}
subtype = _value(field, "subtype", "field_sub_type", "field_subtype", default=None)
if subtype is not None:
item["subtype"] = _clean(subtype)
if "BLOB" in str(item["type"]).upper():
item["value"] = "<redacted>"
mapped_fields[name] = item
return {"classification": "CONFIRMED" if relationships and samples else ("INFERRED" if fields else "UNKNOWN"), "fields": mapped_fields, "relationships": relationships}
def build_schema_mapping(metadata, samples) -> dict:
"""Build a redacted mapping contract from catalog-shaped fixture data."""
candidates = identify_product_candidates(metadata, samples)
def candidate_score(item: dict) -> tuple[int, int, int, int, int]:
evidence = item["evidence"]
product_fields = [
field for field in evidence.get("semantic_fields", [])
if not _matches(field, _FISCAL_WORDS)
]
return (
len(product_fields),
int(_matches(item["relation"], _PRODUCT_WORDS)),
int(bool(evidence.get("primary_key"))),
len(evidence.get("semantic_sample_fields", [])),
int(item["classification"] == "CONFIRMED"),
)
selected = max(
(item for item in candidates if item["classification"] != "UNKNOWN"),
key=candidate_score,
default=None,
)
confirmed = selected if selected and selected["classification"] == "CONFIRMED" else None
product_relation = selected["relation"] if selected else None
fields_by_relation = _relation_fields(metadata)
product_fields = fields_by_relation.get(str(product_relation).upper(), []) if product_relation else []
product_samples = _sample_rows(samples, product_relation) if product_relation else []
fiscal = _field_candidates(product_fields, product_samples, _FISCAL_WORDS, include_type=True)
media = _field_candidates(product_fields, product_samples, _MEDIA_WORDS, include_type=True)
blob_fields = [field for field in product_fields if "BLOB" in str(_value(field, "type", "type_name", default="")).upper()]
for field in blob_fields:
name = _field_name(field)
media.setdefault(name, {"classification": "INFERRED", "sample_present": False, "type": "BLOB"})["value"] = "<redacted>"
foreign_keys = _rows(metadata, "foreign_keys")
stock_relations = {}
media_relations = {}
fiscal_relations = {}
for relation in _rows(metadata, "relations"):
name = _relation_name(relation)
relation_fields = fields_by_relation.get(name.upper(), [])
relation_samples = _sample_rows(samples, name)
profile = _relation_profile(name, relation_fields, relation_samples, foreign_keys, str(product_relation))
linked = any(child.upper() == name.upper() and parent.upper() == str(product_relation).upper() for child, _, parent, _ in (_foreign_key(fk) for fk in foreign_keys))
if name and (_matches(name, ("STOCK", "INVENTORY", "ESTOQUE")) or linked):
stock_relations[name] = {"classification": "CONFIRMED" if linked else "INFERRED", "evidence": "foreign-key relationship" if linked else "relation name", "fields": profile["fields"], "relationships": profile["relationships"]}
if name and (_matches(name, _MEDIA_WORDS) or any(_matches(_field_name(field), _MEDIA_WORDS) or "BLOB" in str(_value(field, "type", "type_name", default="")).upper() for field in relation_fields)):
media_relations[name] = profile
if name and (_matches(name, ("FISCAL", "TAX", "TRIBUT")) or any(_matches(_field_name(field), _FISCAL_WORDS) for field in relation_fields)):
fiscal_relations[name] = profile
triggers = []
dependencies = defaultdict(list)
for dependency in _rows(metadata, "dependencies"):
dependent = str(_value(dependency, "dependent", "dependent_name", default="")).strip()
depended_on = str(_value(dependency, "depended_on", "depended_on_name", default="")).strip()
if dependent and depended_on:
dependencies[dependent.upper()].append(depended_on)
for trigger in _rows(metadata, "triggers"):
trigger_relation = _relation_name(trigger)
trigger_name = str(_value(trigger, "name", "trigger_name", default="")).strip()
if trigger_relation.upper() == str(product_relation).upper() or _matches(trigger_name, _PRODUCT_WORDS):
triggers.append({"classification": "INFERRED", "relation": trigger_relation, "trigger": trigger_name, "timing": _clean(_value(trigger, "timing", default="UNKNOWN")) if _value(trigger, "timing", default="UNKNOWN") is not None else "UNKNOWN", "event": _clean(_value(trigger, "event", default="UNKNOWN")) if _value(trigger, "event", default="UNKNOWN") is not None else "UNKNOWN", "trigger_type": _clean(_value(trigger, "trigger_type", default="UNKNOWN")) if _value(trigger, "trigger_type", default="UNKNOWN") is not None else "UNKNOWN", "dependencies": sorted(dependencies.get(trigger_name.upper(), [])), "source": "sanitized metadata only"})
incremental = {"classification": "INFERRED" if triggers or _rows(metadata, "generators") or any(_matches(_field_name(f), _FIELD_GROUPS["timestamp"]) for f in product_fields) else "UNKNOWN", "evidence": {"timestamps": [ _field_name(f) for f in product_fields if _matches(_field_name(f), _FIELD_GROUPS["timestamp"])], "triggers": len(triggers), "generators": len(_rows(metadata, "generators"))}}
return {
"product": {"classification": selected["classification"] if selected else "UNKNOWN", "relation": product_relation, "evidence": selected["evidence"] if selected else {}, "candidates": candidates},
"fields": _field_candidates(product_fields, product_samples, tuple(word for words in _FIELD_GROUPS.values() for word in words), include_type=True),
"fiscal": {"classification": "CONFIRMED" if (fiscal or fiscal_relations) and confirmed else ("INFERRED" if fiscal or fiscal_relations else "UNKNOWN"), "fields": fiscal, "relations": fiscal_relations},
"stock": {"classification": "CONFIRMED" if any(item["classification"] == "CONFIRMED" for item in stock_relations.values()) else ("INFERRED" if stock_relations else "UNKNOWN"), "relations": stock_relations},
"media": {"classification": "CONFIRMED" if (media or media_relations) and confirmed else ("INFERRED" if media or media_relations else "UNKNOWN"), "fields": media, "relations": media_relations},
"incremental": incremental,
"triggers": triggers,
}
+186
View File
@@ -0,0 +1,186 @@
"""Credential-free provenance checks for the Firebird Python driver."""
from __future__ import annotations
import importlib.metadata
import importlib.util
import os
import sys
import sysconfig
from pathlib import Path
from typing import Any
PROJECT_ROOT = Path(__file__).resolve().parents[2]
APPROVED_SITE_PACKAGES_ROOT = Path(sysconfig.get_paths()["purelib"]).resolve(strict=False)
class ProvenanceError(RuntimeError):
"""Raised when the driver origin cannot be proven unambiguously."""
def __init__(self, message: str, evidence: dict[str, Any] | None = None):
super().__init__(message)
self.evidence = evidence
def _resolved(value: Any) -> str | None:
if value is None:
return None
try:
return str(Path(value).resolve(strict=False))
except (OSError, TypeError, ValueError):
return None
def _under(path: str | None, directory: str | None) -> bool:
if not path or not directory:
return False
try:
Path(path).relative_to(Path(directory))
return True
except ValueError:
return False
def _metadata_value(metadata: Any, name: str) -> Any:
for key in (name, name.lower(), name.title()):
try:
value = metadata.get(key)
except AttributeError:
value = None
if value is not None:
return value
return None
def _project_shadowing() -> list[str]:
findings: list[str] = []
try:
for path in PROJECT_ROOT.rglob("*"):
if path.name in {"fdb.py", "fdb"} or path.suffix == ".pth":
findings.append(str(path.resolve(strict=False)))
except OSError:
return ["<project-scan-unavailable>"]
return sorted(set(findings))
def _module_candidates() -> list[str]:
candidates = []
for entry in sys.path:
root = Path(entry or Path.cwd())
for candidate in (root / "fdb.py", root / "fdb" / "__init__.py"):
if candidate.is_file():
candidates.append(str(candidate.resolve(strict=False)))
return sorted(set(candidates))
def inspect_fdb_provenance() -> dict[str, Any]:
"""Inspect ``fdb`` origin without importing or executing the module."""
module_file = None
module_package = "fdb"
module_error = None
try:
spec = importlib.util.find_spec("fdb")
module_file = _resolved(getattr(spec, "origin", None)) if spec else None
if spec and getattr(spec, "name", None):
module_package = str(spec.name)
except (ImportError, AttributeError, ValueError, OSError) as error:
module_error = type(error).__name__
distribution_name = "fdb"
distribution_version = None
distribution_location = None
distribution_error = None
try:
distribution = importlib.metadata.distribution("fdb")
distribution_name = str(_metadata_value(distribution.metadata, "Name") or "fdb")
distribution_version = str(getattr(distribution, "version", None) or
_metadata_value(distribution.metadata, "Version") or "") or None
location = getattr(distribution, "locate_file", None)
if callable(location):
distribution_location = _resolved(location(""))
if distribution_location is None:
distribution_location = _resolved(getattr(distribution, "location", None))
except (importlib.metadata.PackageNotFoundError, ImportError, OSError, AttributeError) as error:
distribution_error = type(error).__name__
shadowing = _project_shadowing()
module_candidates = _module_candidates()
ambiguous = len(module_candidates) > 1
module_under_distribution = _under(module_file, distribution_location)
module_under_approved_root = _under(module_file, str(APPROVED_SITE_PACKAGES_ROOT))
distribution_under_approved_root = _under(distribution_location, str(APPROVED_SITE_PACKAGES_ROOT))
status = "VALID"
if (module_error or distribution_error or not module_file or not distribution_version or
not distribution_location or not module_under_distribution or
not module_under_approved_root or not distribution_under_approved_root or
shadowing or ambiguous):
status = "UNKNOWN"
return {
"status": status,
"module_file": module_file,
"module_package": module_package,
"module_version": distribution_version,
"distribution_name": distribution_name,
"distribution_version": distribution_version,
"distribution_location": distribution_location,
"module_under_distribution": module_under_distribution,
"approved_site_packages_root": str(APPROVED_SITE_PACKAGES_ROOT),
"module_under_approved_root": module_under_approved_root,
"distribution_under_approved_root": distribution_under_approved_root,
"module_candidates": module_candidates,
"ambiguous_origin": ambiguous,
"interpreter": _resolved(sys.executable),
"working_directory": _resolved(Path.cwd()),
"sys_path": [_resolved(item) or str(item) for item in sys.path],
"pythonpath": [item for item in os.environ.get("PYTHONPATH", "").split(os.pathsep) if item],
"shadowing": shadowing,
"errors": sorted(item for item in (module_error, distribution_error) if item),
}
def validate_fdb_provenance(expected_version: str = "2.0.4") -> dict[str, Any]:
"""Return proven provenance or raise a sanitized, deterministic error."""
evidence = inspect_fdb_provenance()
if evidence["ambiguous_origin"]:
raise ProvenanceError("fdb provenance has ambiguous origins", evidence)
if evidence["shadowing"]:
raise ProvenanceError("fdb provenance is blocked by project shadowing", evidence)
if not evidence["module_under_approved_root"] or not evidence["distribution_under_approved_root"]:
raise ProvenanceError("fdb provenance is outside the approved environment", evidence)
if evidence["status"] != "VALID":
raise ProvenanceError("fdb provenance is unknown", evidence)
if evidence["distribution_name"].lower() != "fdb":
raise ProvenanceError("fdb distribution name mismatch", evidence)
if evidence["distribution_version"] != expected_version:
raise ProvenanceError("fdb distribution version mismatch", evidence)
evidence["validated_version"] = expected_version
return evidence
def validate_imported_fdb_provenance(
fdb_module: Any, expected_version: str = "2.0.4"
) -> dict[str, Any]:
"""Re-check the concrete imported module against pre-import evidence."""
evidence = inspect_fdb_provenance()
imported_file = _resolved(getattr(fdb_module, "__file__", None))
imported_package = getattr(fdb_module, "__package__", None)
imported_version = getattr(fdb_module, "__version__", None)
evidence["imported_module_file"] = imported_file
evidence["imported_module_package"] = imported_package
evidence["imported_module_version"] = imported_version
if (
imported_file != evidence["module_file"]
or imported_package != "fdb"
or not _under(imported_file, str(APPROVED_SITE_PACKAGES_ROOT))
):
evidence["status"] = "BLOCKED"
evidence["blocked_reason"] = "imported fdb module origin or package mismatch"
raise ProvenanceError("imported fdb provenance is blocked", evidence)
if imported_version != expected_version:
evidence["status"] = "BLOCKED"
evidence["blocked_reason"] = "imported fdb version mismatch"
raise ProvenanceError("imported fdb provenance version is blocked", evidence)
evidence["validated_version"] = expected_version
evidence["status"] = "VALID"
return evidence
+153
View File
@@ -0,0 +1,153 @@
"""Offline, auxiliary SQL sanity checker -- NOT the security boundary.
IMPORTANT (Mission 00D architectural decision): this module is kept only
as a defense-in-depth / offline diagnostic tool. It is deliberately no
longer wired into the live read path -- ``gti_discovery.catalog`` never
calls ``validate_read_only`` and exposes no ``execute(sql)``-shaped API
at all, so arbitrary caller-provided SQL text can never reach the
Firebird driver in the first place. A regex/lexical SQL "blacklist" can
never be a complete or reliable security boundary against a real SQL
grammar (this file's own history proves it -- see git log / Mission 00D
report for the quoted-identifier bypass and the CAST/aggregate false
rejections that motivated the redesign). It is retained here, fixed and
tested, purely so it can still be used as an optional, non-authoritative
sanity check if some future caller-facing tool ever needs one -- it must
never again be trusted as the sole gate before execution.
"""
import re
from typing import Any
_SIDE_EFFECT_SQL = re.compile(
r"\b(?:INSERT|UPDATE|DELETE|CREATE|ALTER|DROP|TRUNCATE|EXECUTE)\b",
re.IGNORECASE,
)
_FIRST_KEYWORD = re.compile(r"\A(SELECT|WITH)\b", re.IGNORECASE)
_SIDE_EFFECTING_SELECT = re.compile(
r"(?:\bFROM\s+(?:[A-Z][\w$]*\s*\.\s*)*[A-Z][\w$]*\s*\(|"
r"\bGEN_ID\s*\(|\bNEXT\s+VALUE\s+FOR\b|\bWITH\s+LOCK\b|"
r"\bFOR\s+UPDATE\b)",
re.IGNORECASE,
)
_CALLABLE = re.compile(
r"\b(?P<qualified>(?:[A-Z][\w$]*\s*\.\s*)*[A-Z][\w$]*)\s*\(",
re.IGNORECASE,
)
# A bare quoted identifier immediately followed by "(" -- e.g.
# "SOME_PROCEDURE"(1,2) -- used to be invisible to _CALLABLE once quoted
# text was masked to spaces by _scan_sql/_normalize_outside_quotes. That
# was the real bypass: masking hid the callable name instead of still
# flagging "there was a callable here". This pattern reads the ORIGINAL
# (unmasked) SQL to catch that shape explicitly.
_QUOTED_CALLABLE = re.compile(r'"(?:[^"]|"")*"\s*\(')
_DETERMINISTIC_CALLABLES = {
"ABS", "AS", "AVG", "CAST", "CEILING", "CHAR_LENGTH", "COALESCE", "COUNT",
"EXTRACT", "FLOOR", "IIF", "LOWER", "MAX", "MIN", "NULLIF", "ROUND",
"SUBSTRING", "SUM", "TRIM", "UPPER",
}
def _scan_sql(sql: str) -> tuple[str, int]:
"""Return SQL with quoted literals/identifiers masked and semicolon count."""
masked: list[str] = []
semicolons = 0
index = 0
while index < len(sql):
char = sql[index]
if char in ("'", '"'):
quote = char
masked.append(" ")
index += 1
while index < len(sql):
masked.append(" ")
if sql[index] == quote:
if index + 1 < len(sql) and sql[index + 1] == quote:
masked.append(" ")
index += 2
continue
index += 1
break
index += 1
else:
raise ValueError("unterminated quoted SQL value")
continue
if sql.startswith("--", index):
raise ValueError("SQL comments are not allowed")
if sql.startswith("/*", index):
raise ValueError("SQL comments are not allowed")
if char == ";":
semicolons += 1
masked.append(char)
index += 1
return "".join(masked), semicolons
def _normalize_outside_quotes(sql: str) -> str:
normalized: list[str] = []
pending_space = False
index = 0
while index < len(sql):
char = sql[index]
if char in ("'", '"'):
if pending_space and normalized:
normalized.append(" ")
pending_space = False
quote = char
normalized.append(char)
index += 1
while index < len(sql):
normalized.append(sql[index])
if sql[index] == quote:
if index + 1 < len(sql) and sql[index + 1] == quote:
normalized.append(sql[index + 1])
index += 2
continue
index += 1
break
index += 1
continue
if char.isspace():
if normalized:
pending_space = True
else:
if pending_space and normalized:
normalized.append(" ")
pending_space = False
normalized.append(char)
index += 1
return "".join(normalized).rstrip()
def validate_read_only(sql: str) -> str:
"""Normalize and validate that *sql* is one safe, read-only query."""
if not isinstance(sql, str):
raise ValueError("SQL must be a string")
candidate = sql.strip()
if candidate.endswith(";"):
candidate = candidate[:-1].rstrip()
if not candidate:
raise ValueError("SQL query cannot be empty")
masked, semicolons = _scan_sql(candidate)
if semicolons:
raise ValueError("multiple SQL statements are not allowed")
normalized = _normalize_outside_quotes(candidate)
normalized_masked = " ".join(masked.split())
if not _FIRST_KEYWORD.match(normalized_masked):
raise ValueError("only SELECT and WITH queries are allowed")
if _SIDE_EFFECT_SQL.search(normalized_masked):
raise ValueError("SQL has a prohibited side effect")
if _SIDE_EFFECTING_SELECT.search(normalized_masked):
raise ValueError("SQL contains a known side-effecting construct")
# A quoted identifier used as a callable (e.g. "SOME_PROC"(1)) is
# invisible to _CALLABLE once masking hides its text -- check the
# unmasked, normalized SQL directly so it can never bypass the
# callable allowlist just by being quoted (the original bypass this
# module was rewritten to close, see module docstring).
if _QUOTED_CALLABLE.search(normalized):
raise ValueError("SQL contains an unapproved quoted callable")
for match in _CALLABLE.finditer(normalized_masked):
name = match.group("qualified").rsplit(".", 1)[-1].strip().upper()
if name not in _DETERMINISTIC_CALLABLES:
raise ValueError("SQL contains an unapproved callable")
return normalized