Files
asf/scripts/seed-code-artifacts.py
2026-09-08 07:21:34 -04:00

259 lines
11 KiB
Python
Executable File

#!/usr/bin/env python3
"""Seed source_file + code_smell artifacts per Code Analysis page."""
import json
import urllib.request
import urllib.error
HEADERS = {
"Content-Type": "application/json",
"X-ASF-Tenant-Id": "t-demo-003",
"X-ASF-User-Id": "u-mario",
}
BASE = "http://localhost:8000/api/v1"
def req(method, path, body=None):
data = json.dumps(body).encode() if body is not None else None
r = urllib.request.Request(
BASE + path,
data=data,
headers=HEADERS,
method=method,
)
try:
with urllib.request.urlopen(r, timeout=15) as resp:
return resp.status, json.loads(resp.read().decode() or "null")
except urllib.error.HTTPError as e:
txt = e.read().decode()
try:
payload = json.loads(txt)
except Exception:
payload = {"raw": txt}
return e.code, payload
if __name__ == "__main__":
# 1. list projects
code, projects = req("GET", "/projects?limit=10")
assert code == 200, f"projects {code} {projects}"
pid = projects[0]["id"]
print(f"project_id={pid} name={projects[0]['name']}")
# 2. list existing to dedupe by external_id
code, arts = req("GET", "/artifacts?limit=200")
existing_external = {
a["external_id"] for a in arts if a.get("external_id")
}
existing_types = {
(a["type"], a["title"]) for a in arts
}
print(f"existing artifacts total: {len(arts)} ext_ids: {len(existing_external)} type/titles: {len(existing_types)}")
SOURCE_FILES = [
{
"external_id": "SRC-CORE-MW",
"type": "source_file",
"title": "asf-core/app/middleware/correlation.py",
"status": "active",
"source": "git",
"content": (
"\"\"\"Tenant & correlation middleware.\n\n"
"Adds X-ASF-Correlation-Id and coerces X-ASF-Tenant-Id to UUID5.\n\"\"\"\n"
"import uuid\nfrom fastapi import Request, Response\nfrom starlette.middleware.base import BaseHTTPMiddleware\n\n"
"TENANT_NS = uuid.UUID('6ba7b812-9dad-11d1-80b4-00c04fd430c8')\n\n"
"def _coerce_uuid(val):\n"
" try:\n"
" return uuid.UUID(val)\n"
" except Exception:\n"
" return uuid.uuid5(TENANT_NS, val or 'default')\n\n"
"class CorrelationMiddleware(BaseHTTPMiddleware):\n"
" async def dispatch(self, request: Request, call_next):\n"
" cid = request.headers.get('X-ASF-Correlation-Id') or str(uuid.uuid4())\n"
" request.state.correlation_id = cid\n"
" tid = _coerce_uuid(request.headers.get('X-ASF-Tenant-Id'))\n"
" request.state.tenant_id = tid\n"
" response: Response = await call_next(request)\n"
" response.headers['X-ASF-Correlation-Id'] = cid\n"
" return response\n"
),
"metadata": {
"language": "python",
"path": "services/asf-core/app/middleware/correlation.py",
"repo": "git@github.com:azurian/asf.git",
"branch": "main",
"loc": 36,
"lines": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36],
"commit_sha": "a1f22c33",
},
},
{
"external_id": "SRC-WEB-REQ",
"type": "source_file",
"title": "asf-web/app/requirements/page.tsx",
"status": "active",
"source": "git",
"content": (
"\"use client\";\n"
"import React, { useMemo, useState } from \"react\";\n"
"import { useQuery } from \"@tanstack/react-query\";\n"
"import { ClipboardList, Search } from \"lucide-react\";\n"
"import { artifacts, projects } from \"@/lib/api\";\n"
"import { Card, CardBody, CardHeader, CardTitle } from \"@/components/ui/Card\";\n"
"\n"
"export default function RequirementsPage() {\n"
" const [projectFilter, setProjectFilter] = useState('all');\n"
" const [statusFilter, setStatusFilter] = useState('all');\n"
" const projectsQ = useQuery({ queryKey: ['req-projects'], queryFn: () => projects.list({ limit: 50 }) });\n"
" const listQ = useQuery({\n"
" queryKey: ['req-list', projectFilter, statusFilter],\n"
" queryFn: () => artifacts.list({\n"
" status: statusFilter !== 'all' ? statusFilter : undefined,\n"
" project_id: projectFilter !== 'all' ? projectFilter : undefined,\n"
" type: 'requirement',\n"
" limit: 200,\n"
" }),\n"
" });\n"
" return (<Card>...</Card>);\n"
"}\n"
),
"metadata": {
"language": "typescript",
"path": "frontend/asf-web/app/requirements/page.tsx",
"repo": "git@github.com:azurian/asf.git",
"branch": "main",
"loc": 394,
"framework": "nextjs14",
"commit_sha": "a1f22c33",
},
},
{
"external_id": "SRC-WORKER-CB",
"type": "source_file",
"title": "asf-worker/app/worker/consumer.py",
"status": "active",
"source": "git",
"content": (
"\"\"\"RabbitMQ consumer factory + sync callbacks for DIRECT job queues.\"\"\"\n"
"import asyncio\nimport structlog\nfrom aio_pika import IncomingMessage\n\n"
"log = structlog.get_logger(__name__)\n\n"
"def _make_consumer_callback(handler):\n"
" def _cb(message: IncomingMessage):\n"
" loop = asyncio.new_event_loop()\n"
" try:\n"
" loop.run_until_complete(handler(message))\n"
" except Exception as exc:\n"
" log.exception('consumer_handler_error', err=exc)\n"
" finally:\n"
" loop.close()\n"
" return _cb\n"
),
"metadata": {
"language": "python",
"path": "services/asf-worker/app/worker/consumer.py",
"repo": "git@github.com:azurian/asf.git",
"branch": "main",
"loc": 18,
"commit_sha": "b4812a01",
},
},
]
# code smells stored as type=ai_analysis with metadata.kind = "code_smell"
# (to keep ArtifactType enum closed — no tests break)
SMELLS = [
{
"external_id": "CS-001",
"type": "ai_analysis",
"title": "Bare except clause in CorrelationMiddleware.dispatch",
"status": "open",
"source": "ruff:EM102",
"content": (
"Rule Ruff E722 — bare `except Exception` without narrow clause may swallow "
"cancellation errors unrelated to UUID parse. Prefer explicit ValueError/AttributeError "
"or log.exception with correlation_id bound.\n"
"Lines affected: 14-16."
),
"metadata": {
"kind": "code_smell",
"language": "python",
"severity": "medium",
"rule_id": "E722",
"engine": "ruff 0.5.0",
"path": "services/asf-core/app/middleware/correlation.py",
"lines": [14, 15, 16],
"fingerprint": "corr_mw_except_001",
"confidence_pct": 98,
},
},
{
"external_id": "CS-002",
"type": "ai_analysis",
"title": "Missing N+1 query guard in requirements listQ (no select limit on projects)",
"status": "open",
"source": "eslint:perf-nplus1",
"content": (
"Requirements page fetches all 50 projects and maps via projectsQ even when a single project filter is selected. "
"Consider deriving projectNameById lazily and paginating projects lookup.\n"
"Affected page.tsx lines 78-89."
),
"metadata": {
"kind": "code_smell",
"language": "typescript",
"severity": "low",
"rule_id": "asf-perf-003",
"engine": "eslint custom",
"path": "frontend/asf-web/app/requirements/page.tsx",
"lines": [78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89],
"fingerprint": "req_list_nplus1_002",
"confidence_pct": 75,
},
},
{
"external_id": "CS-003",
"type": "ai_analysis",
"title": "Coroutine leak risk: event loop not shared re-created per message",
"status": "open",
"source": "pytype:event-loop",
"content": (
"Worker consumer factory _cb spawns asyncio.new_event_loop() per IncomingMessage. "
"Long-lived handlers under high volume may accumulate GC pressure from leftover loop objects. "
"Recommendation: pass the shared running event loop to handler() via q.consume() and make handler truly async.\n"
"Severity HIGH — affects every DIRECT job 24/7."
),
"metadata": {
"kind": "code_smell",
"language": "python",
"severity": "high",
"rule_id": "ASF-SEC-203",
"engine": "pytype + manual review",
"path": "services/asf-worker/app/worker/consumer.py",
"lines": [8, 9, 10, 11, 12, 13, 14, 15, 16],
"fingerprint": "worker_loop_per_msg_003",
"confidence_pct": 88,
"cwe": "CWE-772",
},
},
]
created = 0
for body in SOURCE_FILES + SMELLS:
if (body["type"], body["title"]) in existing_types or body.get("external_id") in existing_external:
print(f" SKIP exists ext_id={body.get('external_id')} type={body['type']} title={body['title'][:60]}")
continue
body_full = {"project_id": pid, **body}
code, resp = req("POST", "/artifacts", body_full)
if code in (201, 200):
created += 1
print(f" OK [{code}] type={body['type']} id={str(resp['id'])[:8]} title={body['title'][:60]}")
else:
print(f" FAIL [{code}] type={body['type']} body={resp}")
print(f"\nDONE: created={created} new code artifacts")
# print summary counts per type
code, arts2 = req("GET", "/artifacts?limit=200")
from collections import Counter
cnt = Counter(a["type"] for a in arts2)
for t, c in cnt.items():
print(f" count {t}: {c}")