Files
ss-tools/scripts/dr_evidence_scope.py

165 lines
8.4 KiB
Python

# #region Tooling.DrEvidenceCheck.TypedScope [C:4] [TYPE Module] [SEMANTICS dr,evidence,scope]
# @BRIEF Extract only schema-validated catalog, authority and active retained artifact references.
import json
import os
# #region Tooling.DrEvidenceCheck.TypedScope.Scopecontractrefused [C:3] [TYPE Class] [SEMANTICS dr,recovery]
class ScopeContractRefused(Exception):
"""Typed refusal raised when the reviewed scope contract cannot be read exactly."""
# #region Tooling.DrEvidenceCheck.TypedScope.Scopecontractrefused.Init [C:3] [TYPE Function] [SEMANTICS dr,recovery]
def __init__(self, code: str):
self.code = code
super().__init__(code)
# #endregion Tooling.DrEvidenceCheck.TypedScope.Scopecontractrefused.Init
# #endregion Tooling.DrEvidenceCheck.TypedScope.Scopecontractrefused
# #region Tooling.DrEvidenceCheck.TypedScope.EntryEvidenceRefs [C:3] [TYPE Function] [SEMANTICS dr,recovery]
def _entry_evidence_refs(entry) -> list[dict]:
"""Extract DraftRef evidence fields from one typed catalog entry revision."""
refs = []
native = getattr(entry.entry, "native_result", None)
if native is not None:
record = native.authority_record
refs.append({"ref": record.capture_content_ref, "sha256": record.source_response_hash,
"source": "catalog_capture_content_ref"})
descriptor = record.descriptor.native_request
refs.append({"ref": descriptor.content_ref, "sha256": descriptor.sha256,
"source": "catalog_descriptor_content_ref"})
expected_image = getattr(entry.entry, "expected_image_content_ref", None)
if expected_image:
refs.append({"ref": expected_image, "sha256": expected_image.rsplit(":", 1)[-1],
"source": "catalog_expected_image_content_ref"})
return refs
# #endregion Tooling.DrEvidenceCheck.TypedScope.EntryEvidenceRefs
# #region Tooling.DrEvidenceCheck.TypedScope.AuthorityEvidenceRefs [C:3] [TYPE Function] [SEMANTICS dr,recovery]
def _authority_evidence_refs(authority) -> list[dict]:
"""Extract protected evidence from the typed entry authority, including nested security evidence."""
refs = []
protected = getattr(authority, "protected_evidence_ref", None)
if protected is not None:
# The authoring envelope has no separate digest column: the DraftRef itself carries the digest.
refs.append({"ref": protected, "sha256": None,
"source": "entry_authority_protected_evidence"})
security = getattr(authority, "security_authority", None)
if security is not None:
refs.append({"ref": security.protected_evidence_ref,
"sha256": security.protected_evidence_sha256,
"source": "entry_authority_security_evidence"})
return refs
# #endregion Tooling.DrEvidenceCheck.TypedScope.AuthorityEvidenceRefs
# #region Tooling.DrEvidenceCheck.TypedScope.MergeScope [C:3] [TYPE Function] [SEMANTICS dr,recovery]
def _merge_scope(rows: list[dict]) -> list[dict]:
"""One row per ref; conflicting digests for one ref are a typed refusal, never a silent pick."""
merged: dict[str, dict] = {}
for row in rows:
ref, digest = row["ref"], row.get("sha256")
existing = merged.get(ref)
if existing is None:
merged[ref] = row
continue
existing_digest = existing.get("sha256")
if existing_digest is None:
existing["sha256"] = digest
elif digest is not None and existing_digest != digest:
raise ScopeContractRefused("EVIDENCE_SCOPE_CONFLICT")
return list(merged.values())
# #endregion Tooling.DrEvidenceCheck.TypedScope.MergeScope
# #region Tooling.DrEvidenceCheck.TypedScope.DocumentScopeRows [C:3] [TYPE Function] [SEMANTICS dr,recovery]
def _document_scope_rows(payload, CatalogRevisionDocument, authority_kinds) -> list[dict]:
"""Validate one stored document through the typed contract, then extract its evidence fields."""
try:
document = CatalogRevisionDocument.model_validate_json(
json.dumps(payload), context={"entry_authority_kinds": authority_kinds})
except Exception: # noqa: BLE001 — the typed contract must validate, no fallback
raise ScopeContractRefused("EVIDENCE_CATALOG_PAYLOAD_INVALID") from None
rows: list[dict] = []
for entry in document.entry_revisions + document.observatory_entries:
rows.extend(_entry_evidence_refs(entry))
return rows
# #endregion Tooling.DrEvidenceCheck.TypedScope.DocumentScopeRows
# #region Tooling.DrEvidenceCheck.TypedScope.AuthorityScopeRows [C:3] [TYPE Function] [SEMANTICS dr,recovery]
def _authority_scope_rows(payload, validate_authority) -> list[dict]:
"""Validate one stored authority envelope through the typed contract, then extract evidence."""
try:
authority = validate_authority(payload)
except Exception: # noqa: BLE001
raise ScopeContractRefused("EVIDENCE_ENTRY_AUTHORITY_INVALID") from None
return _authority_evidence_refs(authority)
# #endregion Tooling.DrEvidenceCheck.TypedScope.AuthorityScopeRows
# #region Tooling.DrEvidenceCheck.TypedScope.CatalogScopeRows [C:3] [TYPE Function] [SEMANTICS dr,recovery]
def _catalog_scope_rows(engine, CatalogRevisionDocument, validate_authority) -> list[dict]:
from sqlalchemy import text
rows: list[dict] = []
with engine.connect() as connection:
payloads = connection.execute(text(
"SELECT payload FROM baseline_catalog_revisions")).scalars().all()
authorities = connection.execute(text(
"SELECT catalog_revision_id, baseline_revision_id, authority_kind, authority "
"FROM baseline_catalog_entry_authorities")).mappings().all()
kinds_by_revision: dict[str, dict[str, str]] = {}
for item in authorities:
kinds_by_revision.setdefault(item["catalog_revision_id"], {})[
item["baseline_revision_id"]] = item["authority_kind"]
for payload in payloads:
revision_id = payload.get("catalog_revision_id") if isinstance(payload, dict) else None
rows.extend(_document_scope_rows(payload, CatalogRevisionDocument,
kinds_by_revision.get(revision_id, {})))
for item in authorities:
rows.extend(_authority_scope_rows(item["authority"], validate_authority))
return rows
# #endregion Tooling.DrEvidenceCheck.TypedScope.CatalogScopeRows
# #region Tooling.DrEvidenceCheck.TypedScope.ScopeFromDatabase [C:3] [TYPE Function] [SEMANTICS dr,recovery]
def _scope_from_database(CatalogRevisionDocument, validate_authority) -> tuple[list[dict], int]:
"""Return in-scope rows and the out-of-scope draft count; read-only queries only."""
from sqlalchemy import create_engine, text
database_url = os.environ.get("DATABASE_URL", "")
if not database_url:
raise ScopeContractRefused("DR_TARGET_UNRESOLVED")
schema = (os.environ.get("DB_SCHEMA") or "public").strip() or "public"
connect_args = {} if schema == "public" else {"options": f"-csearch_path={schema}"}
engine = create_engine(database_url, connect_args=connect_args)
try:
rows = _catalog_scope_rows(engine, CatalogRevisionDocument, validate_authority)
with engine.connect() as connection:
scenario_rows = connection.execute(text(
"SELECT a.content_ref AS ref, a.sha256 AS sha256 FROM scenario_artifacts a "
"LEFT JOIN scenario_retention_deletions d "
" ON d.target_type = 'artifact' AND d.target_id = a.id "
" AND d.state IN ('deleted', 'tombstoned') "
"WHERE a.is_active AND d.id IS NULL")).mappings().all()
known = {row["ref"] for row in rows}
for row in scenario_rows:
rows.append({"ref": row["ref"], "sha256": row["sha256"],
"source": "active_scenario_artifact"})
known.add(row["ref"])
out_of_scope = 0
for row in connection.execute(text(
"SELECT content_ref AS ref, sha256 AS sha256 FROM draft_artifacts")).mappings().all():
if row["ref"] in known:
rows.append({"ref": row["ref"], "sha256": row["sha256"],
"source": "referenced_draft_artifact"})
else:
out_of_scope += 1
finally:
engine.dispose()
return _merge_scope(rows), out_of_scope
# #endregion Tooling.DrEvidenceCheck.TypedScope.ScopeFromDatabase
# #endregion Tooling.DrEvidenceCheck.TypedScope