HumboldtJoker commited on
Commit
376d61e
·
verified ·
1 Parent(s): 9994a58

Clean up nested duplicate: v2/v2/skills/migration_safety.py

Browse files
Files changed (1) hide show
  1. v2/v2/skills/migration_safety.py +0 -154
v2/v2/skills/migration_safety.py DELETED
@@ -1,154 +0,0 @@
1
- """Migration safety chip — the shared-database constraint, enforced.
2
-
3
- Staging and prod share one PostgreSQL instance (belief
4
- `belief_constraint_shared_db`, confidence 1.0). This chip classifies
5
- every piece of SQL in the request as additive or destructive, checks
6
- named tables against the actual schema (live or snapshot), and produces
7
- the migration_report the discipline gate blocks on.
8
-
9
- Classification is code, not model judgment. The model can be talked out
10
- of a rule; a regex cannot.
11
- """
12
-
13
- import re
14
-
15
- from kintsugi_core import (
16
- BaseSkillChip,
17
- EFEWeights,
18
- SkillCapability,
19
- SkillContext,
20
- SkillDomain,
21
- SkillRequest,
22
- SkillResponse,
23
- )
24
-
25
- DESTRUCTIVE_SQL = [
26
- (r"\bDROP\s+(TABLE|COLUMN|INDEX|CONSTRAINT|SCHEMA|DATABASE)\b",
27
- "DROP statement"),
28
- (r"\bALTER\s+TABLE\s+[\w\".]+\s+DROP\b", "ALTER TABLE ... DROP"),
29
- (r"\bALTER\s+TABLE\s+[\w\".]+\s+(ALTER|MODIFY)\s+COLUMN\s+\w+\s+(SET\s+DATA\s+)?TYPE\b",
30
- "in-place column type change (needs multi-step expand/contract)"),
31
- (r"\bALTER\s+(TABLE\s+[\w\".]+\s+)?RENAME\b",
32
- "RENAME (old code breaks against the shared DB)"),
33
- (r"\bTRUNCATE\b", "TRUNCATE"),
34
- (r"\bDELETE\s+FROM\s+[\w\".]+\s*(;|$)", "unqualified DELETE"),
35
- (r"\bALTER\s+TABLE\s+[\w\".]+\s+ADD\s+(COLUMN\s+)?\w+[^;]*\bNOT\s+NULL\b(?![^;]*DEFAULT)",
36
- "NOT NULL column without DEFAULT (breaks old code's inserts)"),
37
- ]
38
-
39
- ADDITIVE_SQL = [
40
- r"\bCREATE\s+TABLE\b",
41
- r"\bCREATE\s+(UNIQUE\s+)?INDEX\s+CONCURRENTLY\b",
42
- r"\bALTER\s+TABLE\s+[\w\".]+\s+ADD\s+(COLUMN\s+)?",
43
- r"\bCREATE\s+(OR\s+REPLACE\s+)?(FUNCTION|VIEW)\b",
44
- ]
45
-
46
- SQL_HINT = re.compile(
47
- r"\b(CREATE|ALTER|DROP|TRUNCATE|DELETE|INSERT|UPDATE|SELECT)\b", re.I,
48
- )
49
- TABLE_REF = re.compile(
50
- r"\b(?:TABLE|FROM|INTO|UPDATE)\s+(?:IF\s+(?:NOT\s+)?EXISTS\s+)?"
51
- r"(?:public\.)?([a-z_][\w]*)", re.I,
52
- )
53
-
54
-
55
- def extract_sql(text: str) -> list:
56
- """SQL from fenced code blocks, plus bare statements outside them.
57
-
58
- Prose that *describes* destructive SQL should not block; copy-pastable
59
- SQL should. Fenced blocks are always inspected; outside fences only
60
- lines that parse as statements count.
61
- """
62
- chunks = []
63
- fenced = re.findall(r"```(?:\w*)\n(.*?)```", text, re.DOTALL)
64
- for block in fenced:
65
- if SQL_HINT.search(block):
66
- chunks.append(block)
67
- remainder = re.sub(r"```(?:\w*)\n.*?```", "", text, flags=re.DOTALL)
68
- for line in remainder.splitlines():
69
- stripped = line.strip()
70
- if re.match(
71
- r"^(CREATE|ALTER|DROP|TRUNCATE|DELETE|INSERT|UPDATE)\s", stripped, re.I
72
- ) and (stripped.endswith(";") or len(stripped.split()) >= 3):
73
- chunks.append(stripped)
74
- return chunks
75
-
76
-
77
- def classify_sql(sql: str) -> dict:
78
- violations = []
79
- for pattern, label in DESTRUCTIVE_SQL:
80
- if re.search(pattern, sql, re.IGNORECASE):
81
- violations.append(label)
82
- additive = any(re.search(p, sql, re.IGNORECASE) for p in ADDITIVE_SQL)
83
- return {
84
- "sql": sql[:500],
85
- "destructive": bool(violations),
86
- "violations": violations,
87
- "additive": additive and not violations,
88
- "tables": list(dict.fromkeys(
89
- t.lower() for t in TABLE_REF.findall(sql)
90
- )),
91
- }
92
-
93
-
94
- class MigrationSafetyChip(BaseSkillChip):
95
- name = "migration_safety"
96
- description = "Classify SQL against the shared-db constraint and schema"
97
- version = "2.0.0"
98
- domain = SkillDomain.OPERATIONS
99
- efe_weights = EFEWeights(
100
- mission_alignment=0.15, stakeholder_benefit=0.35,
101
- resource_efficiency=0.10, transparency=0.25, equity=0.15,
102
- )
103
- capabilities = [SkillCapability.READ_DATA]
104
- consensus_actions = ["destructive_migration"]
105
-
106
- def __init__(self, schema_tools=None):
107
- super().__init__()
108
- self.schema_tools = schema_tools
109
-
110
- async def handle(self, request: SkillRequest,
111
- context: SkillContext) -> SkillResponse:
112
- question = context.metadata.get("question", request.raw_input)
113
- session = context.metadata.get("session")
114
-
115
- statements = [classify_sql(s) for s in extract_sql(question)]
116
- destructive = [s for s in statements if s["destructive"]]
117
-
118
- schema_check = {"source": "none", "known_tables": [],
119
- "unknown_tables": [], "migration_status": ""}
120
- if self.schema_tools is not None:
121
- info = self.schema_tools.inspect()
122
- schema_check["source"] = info.source
123
- schema_check["migration_status"] = info.migration_status
124
- if info.tables:
125
- mentioned = {t for s in statements for t in s["tables"]}
126
- known = set(info.tables)
127
- schema_check["known_tables"] = sorted(mentioned & known)
128
- schema_check["unknown_tables"] = sorted(mentioned - known)
129
- if session and info.source != "none":
130
- session.record_evidence(
131
- "schema", f"{info.source}:{info.table_count} tables",
132
- self.name,
133
- )
134
-
135
- report = {
136
- "sql_found": bool(statements),
137
- "statements": statements,
138
- "destructive_count": len(destructive),
139
- "schema_check": schema_check,
140
- "shared_db_rule": (
141
- "Staging and prod share one PostgreSQL instance. Migrations "
142
- "must be additive, backward-compatible, and reversible."
143
- ),
144
- }
145
- summary = (
146
- f"{len(statements)} SQL statement(s), "
147
- f"{len(destructive)} destructive; schema source: "
148
- f"{schema_check['source']}"
149
- )
150
- return SkillResponse(
151
- content=summary, success=True, data=report,
152
- requires_consensus=bool(destructive),
153
- consensus_action="destructive_migration" if destructive else None,
154
- )