rohitsar567 commited on
Commit
12f33ae
Β·
1 Parent(s): 216a816

feat(audit): Tier 2 code-soundness checks + selftest fixtures

Browse files
Files changed (2) hide show
  1. audit/selftest_fixtures.py +122 -0
  2. audit/tier2_code.py +104 -1
audit/selftest_fixtures.py CHANGED
@@ -88,10 +88,132 @@ def _f_t1_5():
88
  d.rmdir()
89
 
90
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  FIXTURES.update({
92
  "T1.1": _f_t1_1,
93
  "T1.2": _f_t1_2,
94
  "T1.3": _f_t1_3,
95
  "T1.4": _f_t1_4,
96
  "T1.5": _f_t1_5,
 
 
 
 
 
 
97
  })
 
88
  d.rmdir()
89
 
90
 
91
+ @contextlib.contextmanager
92
+ def _f_t2_1():
93
+ """Track a .py with a syntax error so T2.1 FAILs."""
94
+ f = REPO / "_audit_selftest_syntax.py"
95
+ f.write_text("def broken(:\n pass\n", encoding="utf-8")
96
+ sh(["git", "add", "-f", "_audit_selftest_syntax.py"])
97
+ try:
98
+ yield
99
+ finally:
100
+ sh(["git", "rm", "--cached", "-q", "_audit_selftest_syntax.py"])
101
+ if f.exists():
102
+ f.unlink()
103
+
104
+
105
+ @contextlib.contextmanager
106
+ def _f_t2_2():
107
+ """Track backend/_audit_selftest_mod.py that raises NameError at import."""
108
+ f = REPO / "backend" / "_audit_selftest_mod.py"
109
+ f.write_text("x = undefined_thing\n", encoding="utf-8")
110
+ sh(["git", "add", "-f", "backend/_audit_selftest_mod.py"])
111
+ try:
112
+ yield
113
+ finally:
114
+ sh(["git", "rm", "--cached", "-q", "backend/_audit_selftest_mod.py"])
115
+ if f.exists():
116
+ f.unlink()
117
+
118
+
119
+ @contextlib.contextmanager
120
+ def _f_t2_3():
121
+ """Track backend/_audit_selftest_dead.py with a code ref to a deleted module."""
122
+ f = REPO / "backend" / "_audit_selftest_dead.py"
123
+ f.write_text("from backend.orchestrator import handle_turn\n", encoding="utf-8")
124
+ sh(["git", "add", "-f", "backend/_audit_selftest_dead.py"])
125
+ try:
126
+ yield
127
+ finally:
128
+ sh(["git", "rm", "--cached", "-q", "backend/_audit_selftest_dead.py"])
129
+ if f.exists():
130
+ f.unlink()
131
+
132
+
133
+ @contextlib.contextmanager
134
+ def _f_t2_4():
135
+ """Track a .css file with a nested */ inside a block comment so T2.4 FAILs."""
136
+ f = REPO / "_audit_selftest.css"
137
+ f.write_text("/* a */ b */\n.x { color: red; }\n", encoding="utf-8")
138
+ sh(["git", "add", "-f", "_audit_selftest.css"])
139
+ try:
140
+ yield
141
+ finally:
142
+ sh(["git", "rm", "--cached", "-q", "_audit_selftest.css"])
143
+ if f.exists():
144
+ f.unlink()
145
+
146
+
147
+ @contextlib.contextmanager
148
+ def _f_t2_5():
149
+ """Track backend/_audit_selftest_path.py with a hardcoded 40-data path."""
150
+ f = REPO / "backend" / "_audit_selftest_path.py"
151
+ f.write_text('x = something / "40-data" / "y"\n', encoding="utf-8")
152
+ sh(["git", "add", "-f", "backend/_audit_selftest_path.py"])
153
+ try:
154
+ yield
155
+ finally:
156
+ sh(["git", "rm", "--cached", "-q", "backend/_audit_selftest_path.py"])
157
+ if f.exists():
158
+ f.unlink()
159
+
160
+
161
+ def _ruff_available() -> bool:
162
+ return (REPO / ".venv" / "bin" / "ruff").exists()
163
+
164
+
165
+ def _tsc_available() -> bool:
166
+ return (REPO / "frontend" / "node_modules" / ".bin" / "tsc").exists()
167
+
168
+
169
+ @contextlib.contextmanager
170
+ def _f_t2_6():
171
+ """Force a lint/type failure so T2.6 FAILs.
172
+
173
+ Prefer ruff (a flagrant unused-import + bare-except .py under audit/). If
174
+ ruff is unavailable in this env T2.6 would SKIP (not FAIL) on a ruff-only
175
+ fixture, so fall back to forcing a tsc error via a broken .ts under
176
+ frontend/src/. If NEITHER ruff nor tsc is available this single fixture
177
+ cannot legitimately force a FAIL β€” raise a clear RuntimeError so selftest
178
+ surfaces it rather than reporting a false pass.
179
+ """
180
+ have_ruff = _ruff_available()
181
+ have_tsc = _tsc_available()
182
+ if not have_ruff and not have_tsc:
183
+ raise RuntimeError("T2.6 selftest needs ruff or tsc")
184
+
185
+ created = []
186
+ try:
187
+ if have_ruff:
188
+ f = REPO / "audit" / "_audit_selftest_lint.py"
189
+ f.write_text("import os\ntry:\n pass\nexcept:\n pass\n",
190
+ encoding="utf-8")
191
+ sh(["git", "add", "-f", "audit/_audit_selftest_lint.py"])
192
+ created.append("audit/_audit_selftest_lint.py")
193
+ else:
194
+ f = REPO / "frontend" / "src" / "_audit_selftest_bad.ts"
195
+ f.write_text("export const x: number = ;\n", encoding="utf-8")
196
+ sh(["git", "add", "-f", "frontend/src/_audit_selftest_bad.ts"])
197
+ created.append("frontend/src/_audit_selftest_bad.ts")
198
+ yield
199
+ finally:
200
+ for rel in created:
201
+ sh(["git", "rm", "--cached", "-q", rel])
202
+ fp = REPO / rel
203
+ if fp.exists():
204
+ fp.unlink()
205
+
206
+
207
  FIXTURES.update({
208
  "T1.1": _f_t1_1,
209
  "T1.2": _f_t1_2,
210
  "T1.3": _f_t1_3,
211
  "T1.4": _f_t1_4,
212
  "T1.5": _f_t1_5,
213
+ "T2.1": _f_t2_1,
214
+ "T2.2": _f_t2_2,
215
+ "T2.3": _f_t2_3,
216
+ "T2.4": _f_t2_4,
217
+ "T2.5": _f_t2_5,
218
+ "T2.6": _f_t2_6,
219
  })
audit/tier2_code.py CHANGED
@@ -1 +1,104 @@
1
- # filled in a later task
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Tier 2 β€” code soundness (pre-commit)."""
2
+ from __future__ import annotations
3
+ import ast, re
4
+ from audit.core import register, Result, Status, REPO, git, sh
5
+
6
+ PY = [p for p in git("ls-files").splitlines() if p.endswith(".py")]
7
+ DEAD = ("backend.orchestrator", "import sales_brain", "qa_brain", "faithfulness",
8
+ "backend.translator", "profile_extractor", "get_judge_llm", "get_fast_brain_llm")
9
+
10
+
11
+ @register("T2.1", "static", "all .py parse (AST)")
12
+ def t2_1() -> Result:
13
+ bad = []
14
+ for p in PY:
15
+ try:
16
+ ast.parse((REPO / p).read_text(encoding="utf-8", errors="replace"), p)
17
+ except SyntaxError as e:
18
+ bad.append(f"{p}: {e}")
19
+ return (Result("T2.1", Status.FAIL, "; ".join(bad[:5]), "fix the syntax error")
20
+ if bad else Result("T2.1", Status.PASS, f"{len(PY)} files parse"))
21
+
22
+
23
+ @register("T2.2", "static", "runtime-import every backend/rag module")
24
+ def t2_2() -> Result:
25
+ mods = []
26
+ for p in PY:
27
+ if (p.startswith("backend/") or p.startswith("rag/")) and not p.endswith("__init__.py") \
28
+ and "_smoke_test" not in p and "/tests/" not in p:
29
+ mods.append(p[:-3].replace("/", "."))
30
+ code = "import importlib,sys\nbad=[]\n" + \
31
+ "".join(f"try:\n importlib.import_module({m!r})\nexcept Exception as e:\n bad.append(({m!r},repr(e)))\n"
32
+ for m in mods) + "print(bad)\nsys.exit(1 if bad else 0)"
33
+ r = sh([".venv/bin/python", "-c", code], timeout=300)
34
+ if r.returncode != 0:
35
+ return Result("T2.2", Status.FAIL, r.stdout.strip()[:600],
36
+ "fix the import (often: import wrongly placed inside a docstring)")
37
+ return Result("T2.2", Status.PASS, f"{len(mods)} modules import clean")
38
+
39
+
40
+ @register("T2.3", "static", "no refs to deleted modules/symbols")
41
+ def t2_3() -> Result:
42
+ code_hits, doc_hits = [], []
43
+ for p in PY:
44
+ for i, ln in enumerate(open(REPO / p, encoding="utf-8", errors="replace"), 1):
45
+ for d in DEAD:
46
+ if d in ln:
47
+ s = ln.strip()
48
+ (doc_hits if s.startswith("#") or s.startswith(('"', "'", "*")) else code_hits
49
+ ).append(f"{p}:{i} {d}")
50
+ if code_hits:
51
+ return Result("T2.3", Status.FAIL, "; ".join(code_hits[:6]),
52
+ "remove/replace the dead reference (e.g. get_judge_llm -> get_brain_llm)")
53
+ if doc_hits:
54
+ return Result("T2.3", Status.WARN, f"{len(doc_hits)} stale comment refs e.g. {doc_hits[:3]}",
55
+ "tidy the stale comment")
56
+ return Result("T2.3", Status.PASS, "no dead-symbol references")
57
+
58
+
59
+ @register("T2.4", "static", "no */ inside CSS/JS block-comment body")
60
+ def t2_4() -> Result:
61
+ bad = []
62
+ for p in git("ls-files").splitlines():
63
+ if not p.endswith((".css", ".scss")):
64
+ continue
65
+ txt = (REPO / p).read_text(encoding="utf-8", errors="replace")
66
+ for m in re.finditer(r"/\*.*?\*/", txt, re.S):
67
+ body = m.group(0)[2:-2]
68
+ if "*/" in body:
69
+ bad.append(f"{p}: nested */ in comment")
70
+ return (Result("T2.4", Status.FAIL, "; ".join(bad), "space the token: '* /' or reword")
71
+ if bad else Result("T2.4", Status.PASS, "no comment-terminator footgun"))
72
+
73
+
74
+ @register("T2.5", "static", "no hardcoded 40-data path construction")
75
+ def t2_5() -> Result:
76
+ pat = re.compile(r'/\s*["\']40-data["\']')
77
+ bad = []
78
+ for p in PY:
79
+ if not (p.startswith("backend/") or p.startswith("rag/")):
80
+ continue
81
+ if p.endswith("config.py"):
82
+ continue
83
+ for i, ln in enumerate(open(REPO / p, encoding="utf-8", errors="replace"), 1):
84
+ if pat.search(ln) and not ln.strip().startswith("#"):
85
+ bad.append(f"{p}:{i}")
86
+ return (Result("T2.5", Status.FAIL, "; ".join(bad[:8]), "use settings.DATA_DIR")
87
+ if bad else Result("T2.5", Status.PASS, "DATA_DIR centralized"))
88
+
89
+
90
+ @register("T2.6", "static", "ruff + tsc clean")
91
+ def t2_6() -> Result:
92
+ ruff = sh([".venv/bin/ruff", "check", "backend", "rag", "audit"], timeout=120)
93
+ tsc = sh(["npx", "--prefix", "frontend", "--no-install", "tsc", "-p", "frontend", "--noEmit"], timeout=240)
94
+ probs = []
95
+ if ruff.returncode not in (0, 127):
96
+ tail = (ruff.stdout or ruff.stderr).strip().splitlines()
97
+ probs.append("ruff: " + (tail[-1][:200] if tail else "error"))
98
+ if tsc.returncode not in (0, 127):
99
+ tail = (tsc.stdout or tsc.stderr).strip().splitlines()
100
+ probs.append("tsc: " + (tail[-1][:200] if tail else "error"))
101
+ if 127 in (ruff.returncode, tsc.returncode) and not probs:
102
+ return Result("T2.6", Status.SKIP, "ruff/tsc not installed", "pip install ruff / npm i")
103
+ return (Result("T2.6", Status.FAIL, " | ".join(probs), "fix lint/type errors")
104
+ if probs else Result("T2.6", Status.PASS, "ruff + tsc clean"))