docs(platform): add policy configs, runbooks, ops scripts and platform documentation
Config policies (16 files): alert_routing, architecture_pressure, backlog, cost_weights, data_governance, incident_escalation, incident_intelligence, network_allowlist, nodes_registry, observability_sources, rbac_tools_matrix, release_gate, risk_attribution, risk_policy, slo_policy, tool_limits, tools_rollout Ops (22 files): Caddyfile, calendar compose, grafana voice dashboard, deployments/incidents logs, runbooks for alerts/audit/backlog/incidents/sofiia/voice, cron jobs, scripts (alert_triage, audit_cleanup, migrate_*, governance, schedule), task_registry, voice alerts/ha/latency/policy Docs (30+ files): HUMANIZED_STEPAN v2.7-v3 changelogs and runbooks, NODA1/NODA2 status and setup, audit index and traces, backlog, incident, supervisor, tools, voice, opencode, release, risk, aistalk, spacebot Made-with: Cursor
This commit is contained in:
114
ops/scripts/migrate_audit_postgres.py
Normal file
114
ops/scripts/migrate_audit_postgres.py
Normal file
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Idempotent DDL migration for Postgres audit backend.
|
||||
|
||||
Creates the `tool_audit_events` table and its indexes if they don't already exist.
|
||||
|
||||
Usage:
|
||||
python3 ops/scripts/migrate_audit_postgres.py
|
||||
DATABASE_URL=postgresql://user:pass@host/db python3 ops/scripts/migrate_audit_postgres.py --dry-run
|
||||
|
||||
Environment variables:
|
||||
DATABASE_URL — PostgreSQL DSN (required).
|
||||
|
||||
Exit codes:
|
||||
0 — success / already up-to-date
|
||||
1 — error
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
import textwrap
|
||||
|
||||
# ─── DDL ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
DDL = textwrap.dedent("""\
|
||||
-- Audit events table (idempotent)
|
||||
CREATE TABLE IF NOT EXISTS tool_audit_events (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
ts TIMESTAMPTZ NOT NULL,
|
||||
req_id TEXT NOT NULL,
|
||||
workspace_id TEXT NOT NULL,
|
||||
user_id TEXT NOT NULL,
|
||||
agent_id TEXT NOT NULL,
|
||||
tool TEXT NOT NULL,
|
||||
action TEXT NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
duration_ms INT NOT NULL DEFAULT 0,
|
||||
in_size INT NOT NULL DEFAULT 0,
|
||||
out_size INT NOT NULL DEFAULT 0,
|
||||
input_hash TEXT NOT NULL DEFAULT '',
|
||||
graph_run_id TEXT,
|
||||
graph_node TEXT,
|
||||
job_id TEXT
|
||||
);
|
||||
|
||||
-- Indexes (idempotent)
|
||||
CREATE INDEX IF NOT EXISTS idx_tool_audit_ts
|
||||
ON tool_audit_events (ts);
|
||||
CREATE INDEX IF NOT EXISTS idx_tool_audit_ws_ts
|
||||
ON tool_audit_events (workspace_id, ts);
|
||||
CREATE INDEX IF NOT EXISTS idx_tool_audit_tool_ts
|
||||
ON tool_audit_events (tool, ts);
|
||||
CREATE INDEX IF NOT EXISTS idx_tool_audit_agent_ts
|
||||
ON tool_audit_events (agent_id, ts);
|
||||
""")
|
||||
|
||||
|
||||
# ─── Runner ───────────────────────────────────────────────────────────────────
|
||||
|
||||
def run(dsn: str, dry_run: bool) -> int:
|
||||
"""Execute migration against Postgres. Returns 0 on success, 1 on error."""
|
||||
try:
|
||||
import psycopg2 # type: ignore
|
||||
except ImportError:
|
||||
try:
|
||||
import subprocess
|
||||
subprocess.check_call([sys.executable, "-m", "pip", "install", "--quiet", "psycopg2-binary"])
|
||||
import psycopg2 # type: ignore # noqa: F811
|
||||
except Exception as pip_err:
|
||||
print(f"[ERROR] psycopg2 not available and could not be installed: {pip_err}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
print(f"[migrate] Connecting to: {dsn[:40]}…")
|
||||
if dry_run:
|
||||
print("[migrate] DRY-RUN — printing DDL only, not executing:\n")
|
||||
print(DDL)
|
||||
return 0
|
||||
|
||||
try:
|
||||
conn = psycopg2.connect(dsn)
|
||||
conn.autocommit = False
|
||||
cur = conn.cursor()
|
||||
cur.execute(DDL)
|
||||
conn.commit()
|
||||
cur.close()
|
||||
conn.close()
|
||||
print("[migrate] ✅ Migration applied successfully.")
|
||||
return 0
|
||||
except Exception as exc:
|
||||
print(f"[migrate] ❌ Migration failed: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Idempotent Postgres audit DDL migration")
|
||||
parser.add_argument("--dry-run", action="store_true", help="Print DDL without executing")
|
||||
parser.add_argument(
|
||||
"--dsn",
|
||||
default=os.getenv("DATABASE_URL") or os.getenv("POSTGRES_DSN", ""),
|
||||
help="PostgreSQL DSN (default: $DATABASE_URL)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.dsn:
|
||||
print("[migrate] ERROR: DATABASE_URL not set. Provide --dsn or set DATABASE_URL.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
sys.exit(run(args.dsn, args.dry_run))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user