feat: add container manager API, islop router, and container runtime files with vim/bot/d stealth clients
DevPlace CI / test (push) Failing after 38m17s
DevPlace CI / test (push) Failing after 38m17s
This commit is contained in:
@@ -0,0 +1,506 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
import logging
|
||||
import re
|
||||
from urllib.parse import quote
|
||||
from typing import Annotated
|
||||
|
||||
import uuid_utils
|
||||
from fastapi import Depends, APIRouter, Request
|
||||
from fastapi.responses import FileResponse, HTMLResponse, JSONResponse, Response
|
||||
|
||||
from devplacepy.config import ISSLOP_MEDIA_DIR
|
||||
from devplacepy.constants import DEVII_GUEST_COOKIE
|
||||
from devplacepy.dependencies import json_or_form
|
||||
from devplacepy.models import IsslopRunForm
|
||||
from devplacepy.responses import respond
|
||||
from devplacepy.schemas import IsslopAnalysisOut, IsslopListOut, IsslopReportOut, IsslopSourceOut
|
||||
from devplacepy.seo import base_seo_context, site_url, web_application_schema, website_schema
|
||||
from devplacepy.services.jobs import queue
|
||||
from devplacepy.services.jobs.isslop import store
|
||||
from devplacepy.services.jobs.isslop.badge import badge_html, badge_markdown, render_badge
|
||||
from devplacepy.services.jobs.isslop.service import topic_for
|
||||
from devplacepy.templating import templates
|
||||
from devplacepy.utils import get_current_user, not_found, track_action
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter()
|
||||
|
||||
ACTIVE_STATES = (queue.PENDING, queue.RUNNING)
|
||||
GUEST_COOKIE_MAX_AGE = 31536000
|
||||
EVENT_LIMIT_MAX = 5000
|
||||
MEDIA_NAME_PATTERN = re.compile(r"^[a-f0-9]{16}\.webp$")
|
||||
SOURCE_NAME_PATTERN = re.compile(r"^s[a-f0-9]{16}\.txt$")
|
||||
SOURCE_LINE_CONTEXT = 400000
|
||||
|
||||
|
||||
def _owner(request: Request) -> tuple[str, str] | None:
|
||||
user = get_current_user(request)
|
||||
if user:
|
||||
return "user", user["uid"]
|
||||
guest = request.cookies.get(DEVII_GUEST_COOKIE)
|
||||
if guest:
|
||||
return "guest", guest
|
||||
return None
|
||||
|
||||
|
||||
def _ensure_owner(request: Request) -> tuple[str, str, str]:
|
||||
owner = _owner(request)
|
||||
if owner:
|
||||
return owner[0], owner[1], ""
|
||||
minted = uuid_utils.uuid7().hex
|
||||
return "guest", minted, minted
|
||||
|
||||
|
||||
def _set_guest_cookie(response, minted: str) -> None:
|
||||
if minted:
|
||||
response.set_cookie(
|
||||
DEVII_GUEST_COOKIE,
|
||||
minted,
|
||||
httponly=True,
|
||||
samesite="lax",
|
||||
max_age=GUEST_COOKIE_MAX_AGE,
|
||||
)
|
||||
|
||||
|
||||
def _sync_guest_history(request: Request) -> None:
|
||||
user = get_current_user(request)
|
||||
guest = request.cookies.get(DEVII_GUEST_COOKIE)
|
||||
if user and guest:
|
||||
store.claim_guest_analyses(guest, user["uid"])
|
||||
|
||||
|
||||
def _analysis_payload(row: dict) -> dict:
|
||||
uid = row.get("uid", "")
|
||||
return {
|
||||
"uid": uid,
|
||||
"status": row.get("status", ""),
|
||||
"source_url": row.get("source_url", ""),
|
||||
"source_kind": row.get("source_kind", "") or "unknown",
|
||||
"grade": row.get("grade"),
|
||||
"slop_score": row.get("slop_score"),
|
||||
"origin_score": row.get("origin_score"),
|
||||
"quality_deficit_score": row.get("quality_deficit_score"),
|
||||
"human_percent": row.get("human_percent"),
|
||||
"ai_percent": row.get("ai_percent"),
|
||||
"category": row.get("category"),
|
||||
"confidence": row.get("confidence"),
|
||||
"files_total": int(row.get("files_total") or 0),
|
||||
"files_analyzed": int(row.get("files_analyzed") or 0),
|
||||
"detected_builder": row.get("detected_builder") or None,
|
||||
"dom_slop_score": row.get("dom_slop_score"),
|
||||
"error": row.get("error_message_text") or None,
|
||||
"report_url": f"/tools/isslop/{uid}/report",
|
||||
"badge_url": f"/tools/isslop/{uid}/badge.svg",
|
||||
"events_url": f"/tools/isslop/{uid}/events",
|
||||
"topic": topic_for(uid),
|
||||
"created_at": row.get("created_at"),
|
||||
"finished_at": row.get("finished_at"),
|
||||
}
|
||||
|
||||
|
||||
SEVERITY_RANK = {"strong": 0, "medium": 1, "weak": 2}
|
||||
SIGNAL_LINES_CAP = 8
|
||||
|
||||
|
||||
def _signal_groups(signals: list) -> list:
|
||||
groups: dict[str, dict] = {}
|
||||
for signal in signals:
|
||||
if not isinstance(signal, dict):
|
||||
continue
|
||||
code = str(signal.get("code", ""))
|
||||
entry = groups.setdefault(
|
||||
code,
|
||||
{
|
||||
"code": code,
|
||||
"title": str(signal.get("title", "")),
|
||||
"severity": str(signal.get("severity", "weak")),
|
||||
"count": 0,
|
||||
"lines": [],
|
||||
},
|
||||
)
|
||||
entry["count"] += 1
|
||||
line = signal.get("line")
|
||||
if isinstance(line, int) and len(entry["lines"]) < SIGNAL_LINES_CAP:
|
||||
entry["lines"].append(line)
|
||||
return sorted(
|
||||
groups.values(),
|
||||
key=lambda group: (SEVERITY_RANK.get(group["severity"], 3), -group["count"], group["code"]),
|
||||
)
|
||||
|
||||
|
||||
def _source_url(uid: str, path: str, line: int = 0) -> str:
|
||||
url = f"/tools/isslop/{uid}/source?path={quote(path, safe='')}"
|
||||
if line > 0:
|
||||
url += f"&line={line}#L{line}"
|
||||
return url
|
||||
|
||||
|
||||
CRITERIA_URL = "/docs/isslop-checks.html"
|
||||
|
||||
|
||||
def _linkify_sources(markdown: str, uid: str, paths: set[str], signal_codes: set[str]) -> str:
|
||||
for path in sorted(paths, key=len, reverse=True):
|
||||
escaped = re.escape(path)
|
||||
markdown = re.sub(
|
||||
rf"`{escaped}:(\d+)`",
|
||||
lambda match, p=path: f"[`{p}:{match.group(1)}`]({_source_url(uid, p, int(match.group(1)))})",
|
||||
markdown,
|
||||
)
|
||||
markdown = markdown.replace(f"`{path}`", f"[`{path}`]({_source_url(uid, path)})")
|
||||
markdown = re.sub(
|
||||
rf"(?<![\w/`\(\[]){escaped}(?::(\d+))?(?![\w/`])",
|
||||
lambda match, p=path: (
|
||||
f"[{p}:{match.group(1)}]({_source_url(uid, p, int(match.group(1)))})"
|
||||
if match.group(1)
|
||||
else f"[{p}]({_source_url(uid, p)})"
|
||||
),
|
||||
markdown,
|
||||
)
|
||||
for code in sorted(signal_codes, key=len, reverse=True):
|
||||
escaped = re.escape(code)
|
||||
markdown = markdown.replace(f"`{code}`", f"[`{code}`]({CRITERIA_URL})")
|
||||
markdown = re.sub(
|
||||
rf"(?<![\w`\[]){escaped}(?![\w`])",
|
||||
f"[{code}]({CRITERIA_URL})",
|
||||
markdown,
|
||||
)
|
||||
return markdown
|
||||
|
||||
|
||||
def _badge_info(request: Request, uid: str) -> dict:
|
||||
base = site_url(request).rstrip("/")
|
||||
badge_url = f"{base}/tools/isslop/{uid}/badge.svg"
|
||||
report_url = f"{base}/tools/isslop/{uid}/report"
|
||||
return {
|
||||
"badge_url": badge_url,
|
||||
"report_url": report_url,
|
||||
"markdown": badge_markdown(badge_url, report_url),
|
||||
"html": badge_html(badge_url, report_url),
|
||||
}
|
||||
|
||||
|
||||
@router.get("", response_class=HTMLResponse)
|
||||
async def isslop_page(request: Request):
|
||||
user = get_current_user(request)
|
||||
_sync_guest_history(request)
|
||||
base = site_url(request)
|
||||
description = (
|
||||
"Measure how a codebase or website was made: untouched AI defaults, AI steered by a "
|
||||
"knowing hand, or work no model would ever produce. Transparent, reproducible analysis "
|
||||
"with a shareable authenticity badge."
|
||||
)
|
||||
seo_ctx = base_seo_context(
|
||||
request,
|
||||
title="AI Usage Analyzer",
|
||||
description=description,
|
||||
breadcrumbs=[
|
||||
{"name": "Home", "url": "/feed"},
|
||||
{"name": "Tools", "url": "/tools"},
|
||||
{"name": "AI Usage Analyzer", "url": "/tools/isslop"},
|
||||
],
|
||||
schemas=[
|
||||
website_schema(base),
|
||||
web_application_schema("AI Usage Analyzer", description, "/tools/isslop", base),
|
||||
],
|
||||
)
|
||||
minted = "" if _owner(request) else uuid_utils.uuid7().hex
|
||||
response = templates.TemplateResponse(
|
||||
request,
|
||||
"tools/isslop.html",
|
||||
{**seo_ctx, "request": request, "user": user},
|
||||
)
|
||||
_set_guest_cookie(response, minted)
|
||||
return response
|
||||
|
||||
|
||||
@router.post("/run")
|
||||
async def isslop_run(request: Request, data: Annotated[IsslopRunForm, Depends(json_or_form(IsslopRunForm))]):
|
||||
from devplacepy.services.audit import record as audit
|
||||
|
||||
owner_kind, owner_id, minted = _ensure_owner(request)
|
||||
active = [
|
||||
job
|
||||
for job in queue.list_jobs(kind="isslop", owner=(owner_kind, owner_id))
|
||||
if job.get("status") in ACTIVE_STATES
|
||||
]
|
||||
if active:
|
||||
audit.record(
|
||||
request,
|
||||
"isslop.run.request",
|
||||
result="denied",
|
||||
summary=f"AI usage analysis denied for {data.url}: analysis already running",
|
||||
metadata={"target": data.url, "reason": "active_job", "uid": active[0]["uid"]},
|
||||
links=[audit.job(active[0]["uid"])],
|
||||
)
|
||||
return JSONResponse(
|
||||
{
|
||||
"error": {
|
||||
"status": 429,
|
||||
"message": "You already have an analysis running. Wait for it to finish.",
|
||||
"uid": active[0]["uid"],
|
||||
}
|
||||
},
|
||||
status_code=429,
|
||||
)
|
||||
uid = queue.enqueue(
|
||||
"isslop",
|
||||
{"url": data.url},
|
||||
owner_kind,
|
||||
owner_id,
|
||||
f"AI usage: {data.url}"[:64],
|
||||
)
|
||||
store.create_analysis(uid, data.url, owner_kind, owner_id)
|
||||
audit.record(
|
||||
request,
|
||||
"isslop.run.request",
|
||||
summary=f"requested AI usage analysis of {data.url}",
|
||||
metadata={"target": data.url},
|
||||
links=[audit.job(uid)],
|
||||
)
|
||||
if owner_kind == "user":
|
||||
track_action(owner_id, "isslop")
|
||||
response = JSONResponse(
|
||||
{
|
||||
"uid": uid,
|
||||
"status_url": f"/tools/isslop/{uid}",
|
||||
"events_url": f"/tools/isslop/{uid}/events",
|
||||
"report_url": f"/tools/isslop/{uid}/report",
|
||||
"topic": topic_for(uid),
|
||||
}
|
||||
)
|
||||
_set_guest_cookie(response, minted)
|
||||
return response
|
||||
|
||||
|
||||
@router.get("/list")
|
||||
async def isslop_list(request: Request, limit: int = store.LIST_LIMIT_DEFAULT):
|
||||
_sync_guest_history(request)
|
||||
owner = _owner(request)
|
||||
rows = []
|
||||
if owner:
|
||||
rows = store.list_analyses(owner[0], owner[1], min(max(1, limit), 200))
|
||||
return JSONResponse(
|
||||
IsslopListOut.model_validate(
|
||||
{"analyses": [_analysis_payload(row) for row in rows]}
|
||||
).model_dump(mode="json")
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{uid}")
|
||||
async def isslop_status(request: Request, uid: str):
|
||||
row = store.get_analysis(uid)
|
||||
if not row:
|
||||
raise not_found("Analysis not found")
|
||||
return JSONResponse(
|
||||
IsslopAnalysisOut.model_validate(_analysis_payload(row)).model_dump(mode="json")
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{uid}/events")
|
||||
async def isslop_events(request: Request, uid: str, after: int = 0, limit: int = store.EVENT_LIMIT_DEFAULT):
|
||||
row = store.get_analysis(uid)
|
||||
if not row:
|
||||
raise not_found("Analysis not found")
|
||||
rows = store.events_for(uid, max(0, after), min(max(1, limit), EVENT_LIMIT_MAX))
|
||||
events = [
|
||||
{
|
||||
"seq": event["seq"],
|
||||
"kind": event["kind"],
|
||||
"message": event["message"],
|
||||
"data": store.decode_json(event.get("payload"), {}),
|
||||
"created_at": event["created_at"],
|
||||
}
|
||||
for event in rows
|
||||
]
|
||||
return JSONResponse({"uid": uid, "status": row.get("status", ""), "events": events})
|
||||
|
||||
|
||||
@router.get("/{uid}/report")
|
||||
async def isslop_report(request: Request, uid: str):
|
||||
row = store.get_analysis(uid)
|
||||
if not row:
|
||||
raise not_found("Analysis not found")
|
||||
report = store.get_report(uid)
|
||||
files = []
|
||||
linkable_paths: set[str] = set()
|
||||
signal_codes: set[str] = set()
|
||||
for item in store.file_results_for(uid):
|
||||
signals = store.decode_json(item.get("signals"), [])
|
||||
for signal in signals:
|
||||
if isinstance(signal, dict) and signal.get("code"):
|
||||
signal_codes.add(str(signal["code"]))
|
||||
source_name = str(item.get("source") or "")
|
||||
has_source = bool(source_name and SOURCE_NAME_PATTERN.match(source_name))
|
||||
path = item.get("path", "")
|
||||
if has_source:
|
||||
linkable_paths.add(path)
|
||||
files.append(
|
||||
{
|
||||
"path": path,
|
||||
"language": item.get("language", "unknown"),
|
||||
"lines": int(item.get("lines") or 0),
|
||||
"origin_score": float(item.get("origin_score") or 0.0),
|
||||
"quality_deficit_score": float(item.get("quality_deficit_score") or 0.0),
|
||||
"category": item.get("category", "uncertain"),
|
||||
"signals": signals,
|
||||
"signal_groups": _signal_groups(signals),
|
||||
"source_url": _source_url(uid, path) if has_source else None,
|
||||
}
|
||||
)
|
||||
images = [
|
||||
{
|
||||
"path": item.get("path", ""),
|
||||
"ai_probability": float(item.get("ai_probability") or 0.0),
|
||||
"grade": item.get("grade", "n/a"),
|
||||
"verdict": item.get("verdict", "uncertain"),
|
||||
"image_kind": item.get("image_kind", "image"),
|
||||
"tells": store.decode_json(item.get("tells"), []),
|
||||
"description": item.get("description", ""),
|
||||
"thumb_url": (
|
||||
f"/tools/isslop/{uid}/media/{item['thumb']}"
|
||||
if item.get("thumb") and MEDIA_NAME_PATTERN.match(str(item["thumb"]))
|
||||
else None
|
||||
),
|
||||
}
|
||||
for item in store.image_results_for(uid)
|
||||
]
|
||||
dom_pages = [
|
||||
{
|
||||
"url": item.get("url", ""),
|
||||
"detected_builder": item.get("detected_builder") or None,
|
||||
"signal_count": int(item.get("signal_count") or 0),
|
||||
"signals": store.decode_json(item.get("signals"), []),
|
||||
"screenshot_url": (
|
||||
f"/tools/isslop/{uid}/media/{item['screenshot']}"
|
||||
if item.get("screenshot") and MEDIA_NAME_PATTERN.match(str(item["screenshot"]))
|
||||
else None
|
||||
),
|
||||
}
|
||||
for item in store.dom_results_for(uid)
|
||||
]
|
||||
seo_ctx = base_seo_context(
|
||||
request,
|
||||
title="AI usage analysis report",
|
||||
description=f"Authenticity analysis of {row.get('source_url', '')}",
|
||||
robots="noindex,nofollow",
|
||||
breadcrumbs=[
|
||||
{"name": "Home", "url": "/feed"},
|
||||
{"name": "Tools", "url": "/tools"},
|
||||
{"name": "AI Usage Analyzer", "url": "/tools/isslop"},
|
||||
{"name": "Report", "url": f"/tools/isslop/{uid}/report"},
|
||||
],
|
||||
)
|
||||
context = {
|
||||
**seo_ctx,
|
||||
**_analysis_payload(row),
|
||||
"content_hash": row.get("content_hash"),
|
||||
"markdown": _linkify_sources(report.get("markdown", ""), uid, linkable_paths, signal_codes) if report else "",
|
||||
"generator_model": report.get("model_used", "") if report else "",
|
||||
"generated_at": report.get("generated_at") if report else None,
|
||||
"badge": _badge_info(request, uid),
|
||||
"files": files,
|
||||
"images": images,
|
||||
"dom_pages": dom_pages,
|
||||
"request": request,
|
||||
"user": get_current_user(request),
|
||||
}
|
||||
return respond(request, "tools/isslop_report.html", context, model=IsslopReportOut)
|
||||
|
||||
|
||||
@router.get("/{uid}/report.md")
|
||||
async def isslop_report_markdown(request: Request, uid: str):
|
||||
row = store.get_analysis(uid)
|
||||
if not row:
|
||||
raise not_found("Analysis not found")
|
||||
report = store.get_report(uid)
|
||||
if not report:
|
||||
raise not_found("Report not yet generated for this analysis")
|
||||
return Response(
|
||||
content=report["markdown"],
|
||||
media_type="text/markdown; charset=utf-8",
|
||||
headers={"content-disposition": f'attachment; filename="ai-usage-report-{uid}.md"'},
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{uid}/source")
|
||||
async def isslop_source(request: Request, uid: str, path: str, line: int = 0):
|
||||
row = store.get_analysis(uid)
|
||||
if not row:
|
||||
raise not_found("Analysis not found")
|
||||
result = store.file_result_for(uid, path)
|
||||
source_name = str(result.get("source") or "") if result else ""
|
||||
if not result or not SOURCE_NAME_PATTERN.match(source_name):
|
||||
raise not_found("Source not available for this file")
|
||||
source_path = (store.media_dir_for(uid) / source_name).resolve()
|
||||
if not source_path.is_relative_to(ISSLOP_MEDIA_DIR.resolve()) or not source_path.is_file():
|
||||
raise not_found("Source not available for this file")
|
||||
text = source_path.read_text(encoding="utf-8", errors="replace")
|
||||
signals = store.decode_json(result.get("signals"), [])
|
||||
marked: dict[int, list] = {}
|
||||
for signal in signals:
|
||||
if isinstance(signal, dict) and isinstance(signal.get("line"), int) and signal["line"] > 0:
|
||||
marked.setdefault(signal["line"], []).append(signal)
|
||||
source_lines = text.split("\n")
|
||||
seo_ctx = base_seo_context(
|
||||
request,
|
||||
title=f"Source: {path}",
|
||||
description=f"Annotated source of {path} from the AI usage analysis",
|
||||
robots="noindex,nofollow",
|
||||
breadcrumbs=[
|
||||
{"name": "Home", "url": "/feed"},
|
||||
{"name": "Tools", "url": "/tools"},
|
||||
{"name": "AI Usage Analyzer", "url": "/tools/isslop"},
|
||||
{"name": "Report", "url": f"/tools/isslop/{uid}/report"},
|
||||
{"name": "Source", "url": _source_url(uid, path)},
|
||||
],
|
||||
)
|
||||
context = {
|
||||
**seo_ctx,
|
||||
"uid": uid,
|
||||
"path": path,
|
||||
"language": result.get("language", "unknown"),
|
||||
"category": result.get("category", "uncertain"),
|
||||
"origin_score": float(result.get("origin_score") or 0.0),
|
||||
"quality_deficit_score": float(result.get("quality_deficit_score") or 0.0),
|
||||
"source": text,
|
||||
"truncated": len(text) >= SOURCE_LINE_CONTEXT,
|
||||
"signals": signals,
|
||||
"source_lines": source_lines,
|
||||
"marked_lines": marked,
|
||||
"focus_line": max(0, line),
|
||||
"report_url": f"/tools/isslop/{uid}/report",
|
||||
"request": request,
|
||||
"user": get_current_user(request),
|
||||
}
|
||||
return respond(request, "tools/isslop_source.html", context, model=IsslopSourceOut)
|
||||
|
||||
|
||||
@router.get("/{uid}/media/{name}")
|
||||
async def isslop_media(request: Request, uid: str, name: str):
|
||||
row = store.get_analysis(uid)
|
||||
if not row or not MEDIA_NAME_PATTERN.match(name):
|
||||
raise not_found("Image not available")
|
||||
root = ISSLOP_MEDIA_DIR.resolve()
|
||||
path = (store.media_dir_for(uid) / name).resolve()
|
||||
if not path.is_relative_to(root) or not path.is_file():
|
||||
raise not_found("Image not available")
|
||||
return FileResponse(
|
||||
path,
|
||||
media_type="image/webp",
|
||||
headers={"cache-control": "public, max-age=86400"},
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{uid}/badge.svg")
|
||||
async def isslop_badge(request: Request, uid: str):
|
||||
row = store.get_analysis(uid)
|
||||
if not row:
|
||||
raise not_found("Analysis not found")
|
||||
report_url = _badge_info(request, uid)["report_url"]
|
||||
svg = render_badge(row.get("human_percent"), row.get("grade"), report_url)
|
||||
return Response(
|
||||
content=svg,
|
||||
media_type="image/svg+xml",
|
||||
headers={"cache-control": "no-cache, max-age=300"},
|
||||
)
|
||||
Reference in New Issue
Block a user