Files
ss-tools/backend/alembic/versions/n1o2p3q4r5s6_add_load_testing_tables.py
busya 9de74baa08 feat: dashboard testing suite — scenario UI, load testing, dataset lineage
- 039-dashboard-scenario-ui: agent workspace (WorkspaceModel, scenario
  views, parameters/baselines, artifact preview, HITL save/approval,
  evidence/VLM review, pipeline verification views) + contextVersion=2
  scenario intent + prototype.
- 040-dashboard-load-testing: capacity/matrix/profile, runner pool,
  timing, circuit breaker, PROD gate, cache/bounded/consistency,
  comparison/schedule, API + frontend + prototype.
- 041-dataset-lineage-blast-radius: usage index, severity/schema-diff,
  propagation, deprecation, fanout, API + frontend + prototype (R9
  labels/metrics/recreate).
- 036/037 amendments: dataset_updated trigger + fanout_plan_id, lineage
  deprecation gate, 040 read-model traceability.
- Includes pre-existing 042-rls-management-workspace and
  043-idm-account-integration work.
2026-08-05 18:15:30 +07:00

224 lines
12 KiB
Python

# #region Alembic.AddLoadTestingTables [C:3] [TYPE Module] [SEMANTICS alembic,load-testing,profile,variation,run,execution,aggregate,finding]
# @ingroup Alembic
# @BRIEF Add 040 dashboard-load-testing tables: load_profiles, load_variations, load_runs,
# load_executions, load_run_aggregates, consistency_findings.
# @LAYER Database
# @RELATION DEPENDS_ON -> [Models.LoadTesting]
# @INVARIANT load_variations composite PK (run_id, variation_id) matches the ORM — a variation is
# immutable and bound to exactly one run.
# @INVARIANT load_run_aggregates unique (run_id, chart_id, variation_id) matches the ORM
# UniqueConstraint — one aggregate per chart/variation cell.
# @INVARIANT load_runs.profile_id FK is nullable with ON DELETE SET NULL — a run survives profile
# deletion for audit retention (mirrors verification_runs.repository_id semantics).
# @INVARIANT execution/cascade tables use ON DELETE CASCADE — partial results are queryable only
# while the run exists; the run row itself is never cascade-deleted by these tables.
# @RATIONALE JSON columns (variation_axes, circuit_breaker, blast_radius_report, sample_rows,
# cache_counts, execution_ids) hold bounded structures read whole — matching the 041
# lineage JSON convention and R6 persistence decisions. Statuses are plain String
# columns so terminal-immutability is enforced in the ORM layer, not by a DB enum.
# @REJECTED A DB enum for run status was rejected — the ORM set_status guard owns immutability and
# additive future statuses must not require a migration. Storing full response blobs was
# rejected (R6) — the schema keeps digest/count/sample only.
"""add load testing tables
Revision ID: n1o2p3q4r5s6
Revises: m7n8o9p1q2r3
Create Date: 2026-08-04 22:30:00.000000
"""
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision: str = "n1o2p3q4r5s6"
down_revision: str | Sequence[str] | None = "m7n8o9p1q2r3"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
"""Create the load-testing tables in dependency order."""
op.create_table(
"load_profiles",
sa.Column("id", sa.String(), nullable=False),
sa.Column("dashboard_id", sa.Integer(), nullable=False),
sa.Column("environment_id", sa.String(), nullable=False),
sa.Column("revision", sa.Integer(), nullable=False),
sa.Column("concurrency_requested", sa.Integer(), nullable=False),
sa.Column("execution_mode", sa.String(length=16), nullable=False),
sa.Column("iterations", sa.Integer(), nullable=True),
sa.Column("duration_seconds", sa.Integer(), nullable=True),
sa.Column("ramp_steps", sa.JSON(), nullable=False),
sa.Column("variation_axes", sa.JSON(), nullable=False),
sa.Column("circuit_breaker", sa.JSON(), nullable=False),
sa.Column("matrix_seed", sa.Integer(), nullable=False),
sa.Column("max_variations", sa.Integer(), nullable=False),
sa.Column("enabled", sa.Boolean(), nullable=False),
sa.Column("created_by", sa.String(), nullable=False),
sa.Column("created_at", sa.DateTime(), nullable=False),
sa.Column("updated_at", sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint("id"),
)
op.create_index("ix_load_profiles_dashboard", "load_profiles", ["dashboard_id"])
op.create_index("ix_load_profiles_environment", "load_profiles", ["environment_id"])
op.create_table(
"load_runs",
sa.Column("id", sa.String(), nullable=False),
sa.Column(
"profile_id",
sa.String(),
sa.ForeignKey("load_profiles.id", ondelete="SET NULL"),
nullable=True,
),
sa.Column("profile_revision", sa.Integer(), nullable=False),
sa.Column("environment_id", sa.String(), nullable=False),
sa.Column("dashboard_id", sa.Integer(), nullable=False),
sa.Column("status", sa.String(length=24), nullable=False),
sa.Column("phase", sa.String(length=16), nullable=False),
sa.Column("effective_concurrency", sa.Integer(), nullable=False),
sa.Column("matrix_seed", sa.Integer(), nullable=False),
sa.Column("theoretical_variations", sa.Integer(), nullable=False),
sa.Column("selected_variations", sa.Integer(), nullable=False),
sa.Column("total_executions", sa.Integer(), nullable=False),
sa.Column("blast_radius_fingerprint", sa.String(length=64), nullable=True),
sa.Column("blast_radius_report", sa.JSON(), nullable=True),
sa.Column("approval_gate_id", sa.String(), nullable=True),
sa.Column("task_id", sa.String(), nullable=True),
sa.Column("circuit_state", sa.String(length=16), nullable=False),
sa.Column("started_at", sa.DateTime(), nullable=True),
sa.Column("finished_at", sa.DateTime(), nullable=True),
sa.Column("stop_reason", sa.String(), nullable=True),
sa.Column("created_at", sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint("id"),
)
op.create_index("ix_load_runs_profile", "load_runs", ["profile_id"])
op.create_index("ix_load_runs_environment", "load_runs", ["environment_id"])
op.create_index("ix_load_runs_task", "load_runs", ["task_id"])
op.create_index("ix_load_runs_created", "load_runs", ["created_at"])
op.create_table(
"load_variations",
sa.Column("run_id", sa.String(), nullable=False),
sa.Column("variation_id", sa.String(length=64), nullable=False),
sa.Column("filters", sa.JSON(), nullable=False),
sa.Column("viewport", sa.JSON(), nullable=False),
sa.Column("role", sa.String(length=32), nullable=False),
sa.Column("time_range", sa.JSON(), nullable=True),
sa.Column("created_at", sa.DateTime(), nullable=False),
sa.ForeignKeyConstraint(["run_id"], ["load_runs.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("run_id", "variation_id"),
)
op.create_index("ix_load_variations_run", "load_variations", ["run_id"])
op.create_table(
"load_executions",
sa.Column("id", sa.String(), nullable=False),
sa.Column("run_id", sa.String(), nullable=False),
sa.Column("variation_id", sa.String(length=64), nullable=False),
sa.Column("chart_id", sa.Integer(), nullable=False),
sa.Column("filters_hash", sa.String(length=64), nullable=False),
sa.Column("outcome", sa.String(length=16), nullable=False),
sa.Column("error_taxonomy", sa.String(length=64), nullable=True),
sa.Column("queue_wait_ms", sa.Integer(), nullable=False),
sa.Column("resource_wait_ms", sa.Integer(), nullable=False),
sa.Column("upstream_latency_ms", sa.Integer(), nullable=False),
sa.Column("end_to_end_ms", sa.Integer(), nullable=False),
sa.Column("response_sha256", sa.String(length=64), nullable=True),
sa.Column("row_count", sa.Integer(), nullable=True),
sa.Column("sample_rows", sa.JSON(), nullable=True),
sa.Column("cache_state", sa.String(length=16), nullable=False),
sa.Column("is_cached", sa.Boolean(), nullable=True),
sa.Column("cache_key", sa.String(), nullable=True),
sa.Column("cached_dttm", sa.DateTime(), nullable=True),
sa.Column("queried_dttm", sa.DateTime(), nullable=True),
sa.Column("cache_timeout", sa.Integer(), nullable=True),
sa.Column("cache_state_source", sa.String(length=32), nullable=False),
sa.Column("worker_id", sa.String(length=64), nullable=False),
sa.Column("started_at", sa.DateTime(), nullable=False),
sa.Column("finished_at", sa.DateTime(), nullable=False),
sa.ForeignKeyConstraint(["run_id"], ["load_runs.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("id"),
)
op.create_index("ix_load_executions_run", "load_executions", ["run_id"])
op.create_index(
"ix_load_executions_run_chart_var",
"load_executions",
["run_id", "chart_id", "variation_id"],
)
op.create_table(
"load_run_aggregates",
sa.Column("id", sa.String(), nullable=False),
sa.Column("run_id", sa.String(), nullable=False),
sa.Column("chart_id", sa.Integer(), nullable=False),
sa.Column("variation_id", sa.String(length=64), nullable=False),
sa.Column("success_count", sa.Integer(), nullable=False),
sa.Column("error_count", sa.Integer(), nullable=False),
sa.Column("p50_upstream_ms", sa.Integer(), nullable=False),
sa.Column("p90_upstream_ms", sa.Integer(), nullable=False),
sa.Column("p95_upstream_ms", sa.Integer(), nullable=False),
sa.Column("p99_upstream_ms", sa.Integer(), nullable=False),
sa.Column("p95_queue_wait_ms", sa.Integer(), nullable=False),
sa.Column("p95_resource_wait_ms", sa.Integer(), nullable=False),
sa.Column("throughput_per_second", sa.Float(), nullable=False),
sa.Column("cache_counts", sa.JSON(), nullable=False),
sa.ForeignKeyConstraint(["run_id"], ["load_runs.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint(
"run_id",
"chart_id",
"variation_id",
name="uq_load_run_aggregate_run_chart_var",
),
)
op.create_table(
"consistency_findings",
sa.Column("id", sa.String(), nullable=False),
sa.Column("run_id", sa.String(), nullable=False),
sa.Column("chart_id", sa.Integer(), nullable=False),
sa.Column("coordinate_key", sa.String(length=64), nullable=False),
sa.Column("first_response_sha256", sa.String(length=64), nullable=False),
sa.Column("divergent_response_sha256", sa.String(length=64), nullable=False),
sa.Column("execution_ids", sa.JSON(), nullable=False),
sa.Column("severity", sa.String(length=16), nullable=False),
sa.Column("classification", sa.String(length=16), nullable=False),
sa.Column("created_at", sa.DateTime(), nullable=False),
sa.ForeignKeyConstraint(["run_id"], ["load_runs.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("id"),
)
op.create_index("ix_consistency_findings_run", "consistency_findings", ["run_id"])
op.create_index("ix_consistency_findings_chart", "consistency_findings", ["chart_id"])
def downgrade() -> None:
"""Drop the load-testing tables in reverse dependency order."""
op.drop_index("ix_consistency_findings_chart", table_name="consistency_findings")
op.drop_index("ix_consistency_findings_run", table_name="consistency_findings")
op.drop_table("consistency_findings")
op.drop_table("load_run_aggregates")
op.drop_index("ix_load_executions_run_chart_var", table_name="load_executions")
op.drop_index("ix_load_executions_run", table_name="load_executions")
op.drop_table("load_executions")
op.drop_index("ix_load_variations_run", table_name="load_variations")
op.drop_table("load_variations")
op.drop_index("ix_load_runs_created", table_name="load_runs")
op.drop_index("ix_load_runs_task", table_name="load_runs")
op.drop_index("ix_load_runs_environment", table_name="load_runs")
op.drop_index("ix_load_runs_profile", table_name="load_runs")
op.drop_table("load_runs")
op.drop_index("ix_load_profiles_environment", table_name="load_profiles")
op.drop_index("ix_load_profiles_dashboard", table_name="load_profiles")
op.drop_table("load_profiles")
# #endregion Alembic.AddLoadTestingTables