fix: allow guarded production Flow v2 shadow rebuild

This commit is contained in:
plx
2026-08-16 00:39:23 +00:00
parent 32e7773957
commit e3b8750ebb
4 changed files with 205 additions and 9 deletions

View File

@@ -19,6 +19,7 @@ from app.config import settings
FLOW_VERSION = "blif-flow-v2-shadow-3"
WRITE_DATABASE_ALLOWLIST = frozenset({"clientflow_codex_test"})
PROJECTION_WRITE_TABLES = frozenset({"opportunity_flow_state_v2", "opportunity_flow_transitions"})
def _jsonable(value: Any) -> Any:
@@ -78,16 +79,24 @@ def _projection_value(row: dict[str, Any], derived_at: datetime) -> dict[str, An
return stable | {"source_fingerprint": fingerprint, "derived_at": derived_at}
def _derive_all() -> list[dict[str, Any]]:
def _derive_all(
*,
expected_database: str = "clientflow_codex_test",
expected_user: str | None = "clientflow_codex_test",
expected_opportunity_count: int | None = 328,
require_opportunities: bool = False,
) -> list[dict[str, Any]]:
# Reuse the validated shadow evidence adapter without making it authoritative.
from scripts.simulate_blif_flow_v2 import collect
report = collect(
expected_database="clientflow_codex_test",
expected_user="clientflow_codex_test",
expected_database=expected_database,
expected_user=expected_user,
# collect() still opens its factual read phase with BEGIN READ ONLY;
# the session default may be read-write in the isolated test database.
require_read_only=False,
expected_opportunity_count=expected_opportunity_count,
require_opportunities=require_opportunities,
)
return list(report["opportunities"])
@@ -99,6 +108,10 @@ def rebuild_blif_flow_v2_projection(
allowed_databases: frozenset[str] = WRITE_DATABASE_ALLOWLIST,
target_schema: str = "public",
connection: Any | None = None,
derive_expected_database: str = "clientflow_codex_test",
derive_expected_user: str | None = "clientflow_codex_test",
expected_opportunity_count: int | None = 328,
require_opportunities: bool = False,
) -> dict[str, Any]:
"""Idempotently rebuild projection rows and state-change transitions.
@@ -114,7 +127,14 @@ def rebuild_blif_flow_v2_projection(
if not re.fullmatch(r"[a-z_][a-z0-9_]*", target_schema):
raise ValueError("invalid target_schema")
rows = list(derived_rows) if derived_rows is not None else _derive_all()
rows = list(derived_rows) if derived_rows is not None else _derive_all(
expected_database=derive_expected_database,
expected_user=derive_expected_user,
expected_opportunity_count=expected_opportunity_count,
require_opportunities=require_opportunities,
)
if require_opportunities and not rows:
raise RuntimeError("Flow v2 projection requires at least one opportunity")
derived_at = datetime.now(timezone.utc)
values = [_projection_value(row, derived_at) for row in rows]
if len({value["opportunity_id"] for value in values}) != len(values):