feat(approval): add safe risk disposition workflow
This commit is contained in:
@@ -28,7 +28,7 @@ from app.models.risk_observation import RiskObservation
|
||||
|
||||
MIGRATION_TEST_DATABASE_URL = os.getenv("MIGRATION_TEST_DATABASE_URL", "").strip()
|
||||
LEGACY_PROBE_TABLE = "legacy_migration_probe_records"
|
||||
HEAD_REVISION = "20260716_0009"
|
||||
HEAD_REVISION = "20260716_0011"
|
||||
SERVER_DIR = Path(__file__).resolve().parents[1]
|
||||
ALEMBIC_INI_PATH = SERVER_DIR / "alembic.ini"
|
||||
|
||||
@@ -357,12 +357,150 @@ def _assert_head_schema(engine: Engine) -> None:
|
||||
"uq_risk_observations_tenant_key",
|
||||
("tenant_id", "observation_key"),
|
||||
)
|
||||
_assert_unique_constraint(
|
||||
engine,
|
||||
"risk_observations",
|
||||
"uq_risk_observations_tenant_id",
|
||||
("tenant_id", "id"),
|
||||
)
|
||||
_assert_unique_constraint(
|
||||
engine,
|
||||
"few_shot_samples",
|
||||
"uq_few_shot_samples_tenant_key",
|
||||
("tenant_id", "sample_key"),
|
||||
)
|
||||
_assert_unique_constraint(
|
||||
engine,
|
||||
"approval_action_ledgers",
|
||||
"uq_approval_action_ledger_request",
|
||||
("tenant_id", "actor_id", "request_id"),
|
||||
)
|
||||
_assert_indexes(
|
||||
engine,
|
||||
"approval_action_ledgers",
|
||||
{
|
||||
"ix_approval_action_ledger_claim_action": (
|
||||
"tenant_id",
|
||||
"claim_id",
|
||||
"action",
|
||||
)
|
||||
},
|
||||
)
|
||||
_assert_check_constraint(
|
||||
engine,
|
||||
"approval_action_ledgers",
|
||||
"ck_approval_action_ledger_action",
|
||||
)
|
||||
_assert_check_constraint(
|
||||
engine,
|
||||
"approval_action_ledgers",
|
||||
"ck_approval_action_ledger_completion",
|
||||
)
|
||||
_assert_unique_constraint(
|
||||
engine,
|
||||
"risk_dispositions",
|
||||
"uq_risk_dispositions_tenant_observation",
|
||||
("tenant_id", "observation_id"),
|
||||
)
|
||||
_assert_unique_constraint(
|
||||
engine,
|
||||
"risk_dispositions",
|
||||
"uq_risk_dispositions_tenant_id",
|
||||
("tenant_id", "id"),
|
||||
)
|
||||
_assert_composite_foreign_key(
|
||||
engine,
|
||||
"risk_dispositions",
|
||||
("tenant_id", "observation_id"),
|
||||
"risk_observations",
|
||||
)
|
||||
_assert_indexes(
|
||||
engine,
|
||||
"risk_dispositions",
|
||||
{
|
||||
"ix_risk_dispositions_tenant_lifecycle_due": (
|
||||
"tenant_id",
|
||||
"lifecycle_status",
|
||||
"due_at",
|
||||
),
|
||||
"ix_risk_dispositions_assignee": ("tenant_id", "assignee"),
|
||||
},
|
||||
)
|
||||
_assert_check_constraint(
|
||||
engine,
|
||||
"risk_dispositions",
|
||||
"ck_risk_dispositions_adjudication",
|
||||
)
|
||||
_assert_check_constraint(
|
||||
engine,
|
||||
"risk_dispositions",
|
||||
"ck_risk_dispositions_lifecycle",
|
||||
)
|
||||
_assert_check_constraint(
|
||||
engine,
|
||||
"risk_dispositions",
|
||||
"ck_risk_dispositions_version",
|
||||
)
|
||||
_assert_unique_constraint(
|
||||
engine,
|
||||
"risk_disposition_events",
|
||||
"uq_risk_disposition_events_tenant_request",
|
||||
("tenant_id", "request_id"),
|
||||
)
|
||||
_assert_unique_constraint(
|
||||
engine,
|
||||
"risk_disposition_events",
|
||||
"uq_risk_disposition_events_version",
|
||||
("disposition_id", "version"),
|
||||
)
|
||||
_assert_indexes(
|
||||
engine,
|
||||
"risk_disposition_events",
|
||||
{
|
||||
"ix_risk_disposition_events_disposition_id": ("disposition_id",),
|
||||
"ix_risk_disposition_events_tenant_observation_time": (
|
||||
"tenant_id",
|
||||
"observation_id",
|
||||
"created_at",
|
||||
),
|
||||
},
|
||||
)
|
||||
_assert_check_constraint(
|
||||
engine,
|
||||
"risk_disposition_events",
|
||||
"ck_risk_disposition_events_action",
|
||||
)
|
||||
_assert_check_constraint(
|
||||
engine,
|
||||
"risk_disposition_events",
|
||||
"ck_risk_disposition_events_version",
|
||||
)
|
||||
_assert_composite_foreign_key(
|
||||
engine,
|
||||
"risk_disposition_events",
|
||||
("tenant_id", "disposition_id"),
|
||||
"risk_dispositions",
|
||||
)
|
||||
_assert_composite_foreign_key(
|
||||
engine,
|
||||
"risk_disposition_events",
|
||||
("tenant_id", "observation_id"),
|
||||
"risk_observations",
|
||||
)
|
||||
with engine.connect() as connection:
|
||||
append_only_trigger_count = int(
|
||||
connection.scalar(
|
||||
text(
|
||||
"SELECT COUNT(*) FROM pg_trigger trigger "
|
||||
"JOIN pg_class relation ON relation.oid = trigger.tgrelid "
|
||||
"WHERE relation.relname = 'risk_disposition_events' "
|
||||
"AND trigger.tgname = 'trg_risk_disposition_events_append_only' "
|
||||
"AND NOT trigger.tgisinternal"
|
||||
)
|
||||
)
|
||||
or 0
|
||||
)
|
||||
assert append_only_trigger_count == 1
|
||||
_assert_check_constraint(
|
||||
engine,
|
||||
"memory_entries",
|
||||
@@ -972,12 +1110,15 @@ def _create_hierarchical_memory_downgrade_probe(engine: Engine) -> None:
|
||||
"""
|
||||
)
|
||||
)
|
||||
assert connection.scalar(
|
||||
text(
|
||||
"SELECT COUNT(*) FROM memory_entries "
|
||||
"WHERE id = 'hierarchical-memory-downgrade-probe'"
|
||||
assert (
|
||||
connection.scalar(
|
||||
text(
|
||||
"SELECT COUNT(*) FROM memory_entries "
|
||||
"WHERE id = 'hierarchical-memory-downgrade-probe'"
|
||||
)
|
||||
)
|
||||
) == 1
|
||||
== 1
|
||||
)
|
||||
|
||||
|
||||
def _create_duplicate_active_organization_memory_probe(engine: Engine) -> None:
|
||||
@@ -1056,10 +1197,7 @@ def _create_enriched_few_shot_downgrade_probe(engine: Engine) -> None:
|
||||
def _delete_enriched_few_shot_downgrade_probe(engine: Engine) -> None:
|
||||
with engine.begin() as connection:
|
||||
connection.execute(
|
||||
text(
|
||||
"DELETE FROM few_shot_samples "
|
||||
"WHERE id = 'enriched-few-shot-downgrade-probe'"
|
||||
)
|
||||
text("DELETE FROM few_shot_samples WHERE id = 'enriched-few-shot-downgrade-probe'")
|
||||
)
|
||||
|
||||
|
||||
@@ -1116,12 +1254,8 @@ def _create_historical_case_downgrade_probe(engine: Engine) -> None:
|
||||
|
||||
def _assert_historical_case_downgrade_probe(engine: Engine) -> None:
|
||||
inspector = inspect(engine)
|
||||
risk_columns = {
|
||||
str(item["name"]) for item in inspector.get_columns("risk_observations")
|
||||
}
|
||||
sample_columns = {
|
||||
str(item["name"]) for item in inspector.get_columns("few_shot_samples")
|
||||
}
|
||||
risk_columns = {str(item["name"]) for item in inspector.get_columns("risk_observations")}
|
||||
sample_columns = {str(item["name"]) for item in inspector.get_columns("few_shot_samples")}
|
||||
assert "tenant_id" not in risk_columns
|
||||
assert {"tenant_id", "policy_ref", "rule_version"}.isdisjoint(sample_columns)
|
||||
assert not any(
|
||||
@@ -1131,24 +1265,32 @@ def _assert_historical_case_downgrade_probe(engine: Engine) -> None:
|
||||
for item in inspector.get_foreign_keys("risk_observations")
|
||||
)
|
||||
with engine.connect() as connection:
|
||||
assert connection.scalar(
|
||||
text(
|
||||
"SELECT COUNT(*) FROM risk_observations "
|
||||
"WHERE id = 'historical-downgrade-observation'"
|
||||
assert (
|
||||
connection.scalar(
|
||||
text(
|
||||
"SELECT COUNT(*) FROM risk_observations "
|
||||
"WHERE id = 'historical-downgrade-observation'"
|
||||
)
|
||||
)
|
||||
) == 1
|
||||
assert connection.scalar(
|
||||
text(
|
||||
"SELECT COUNT(*) FROM risk_observation_feedback "
|
||||
"WHERE id = 'historical-downgrade-feedback'"
|
||||
== 1
|
||||
)
|
||||
assert (
|
||||
connection.scalar(
|
||||
text(
|
||||
"SELECT COUNT(*) FROM risk_observation_feedback "
|
||||
"WHERE id = 'historical-downgrade-feedback'"
|
||||
)
|
||||
)
|
||||
) == 1
|
||||
assert connection.scalar(
|
||||
text(
|
||||
"SELECT COUNT(*) FROM few_shot_samples "
|
||||
"WHERE id = 'historical-downgrade-sample'"
|
||||
== 1
|
||||
)
|
||||
assert (
|
||||
connection.scalar(
|
||||
text(
|
||||
"SELECT COUNT(*) FROM few_shot_samples WHERE id = 'historical-downgrade-sample'"
|
||||
)
|
||||
)
|
||||
) == 1
|
||||
== 1
|
||||
)
|
||||
|
||||
|
||||
def _assert_legacy_sentinel(engine: Engine) -> None:
|
||||
@@ -1179,6 +1321,10 @@ def _assert_base_schema(engine: Engine) -> None:
|
||||
("20260716_0008_tenant_safe_historical_cases.py", "downgrade"),
|
||||
("20260716_0009_organization_memory_idempotency.py", "upgrade"),
|
||||
("20260716_0009_organization_memory_idempotency.py", "downgrade"),
|
||||
("20260716_0010_approval_action_protocol.py", "upgrade"),
|
||||
("20260716_0010_approval_action_protocol.py", "downgrade"),
|
||||
("20260716_0011_risk_disposition.py", "upgrade"),
|
||||
("20260716_0011_risk_disposition.py", "downgrade"),
|
||||
],
|
||||
)
|
||||
def test_postgresql_only_migrations_reject_other_dialects_before_mutation(
|
||||
|
||||
Reference in New Issue
Block a user