#!/usr/bin/env python3 """Generate docs/audit/2026-06-04-bug-scan-registry.json — hand-written source SSOT.""" from __future__ import annotations import json import re from datetime import datetime, timezone from pathlib import Path ROOT = Path(__file__).resolve().parent.parent OUT = ROOT / "docs/audit/2026-06-04-bug-scan-registry.json" # Batch 1–6 coverage (path relative to ROOT) BATCH_1_6 = { "server/api/install.py": {"batch_id": 1, "status": "done", "needs_full_rescan": False}, "web/app/install.html": {"batch_id": 1, "status": "done", "needs_full_rescan": False}, "server/application/services/script_job_callback.py": { "batch_id": 1, "status": "done", "needs_full_rescan": False, }, "server/api/agent.py": {"batch_id": 1, "status": "partial", "needs_full_rescan": True}, "server/api/servers.py": {"batch_id": 1, "status": "partial", "needs_full_rescan": True}, "server/background/schedule_runner.py": {"batch_id": 2, "status": "done", "needs_full_rescan": False}, "server/application/services/auth_service.py": { "batch_id": 2, "status": "partial", "needs_full_rescan": True, }, "server/application/services/sync_engine_v2.py": { "batch_id": 2, "status": "partial", "needs_full_rescan": True, }, "server/infrastructure/redis/script_execution_store.py": { "batch_id": 2, "status": "partial", "needs_full_rescan": True, }, "frontend/src/composables/terminal/useTerminalSessions.ts": { "batch_id": 2, "status": "partial", "needs_full_rescan": True, }, "server/background/retry_runner.py": {"batch_id": 3, "status": "done", "needs_full_rescan": False}, "server/application/services/files_browse_service.py": { "batch_id": 3, "status": "partial", "needs_full_rescan": True, }, "frontend/src/pages/DashboardPage.vue": { "batch_id": 3, "status": "partial", "needs_full_rescan": True, }, "server/api/sync_v2.py": {"batch_id": 4, "status": "partial", "needs_full_rescan": True}, "server/api/files.py": {"batch_id": 4, "status": "done", "needs_full_rescan": False}, "server/background/heartbeat_flush.py": { "batch_id": 4, "status": "partial", "needs_full_rescan": True, }, "frontend/src/composables/push/usePushProgress.ts": { "batch_id": 4, "status": "done", "needs_full_rescan": False, }, "server/api/schemas.py": {"batch_id": 4, "status": "partial", "needs_full_rescan": True}, "server/background/self_monitor.py": {"batch_id": 5, "status": "done", "needs_full_rescan": False}, "server/api/auth_jwt.py": {"batch_id": 6, "status": "done", "needs_full_rescan": False}, "server/background/script_execution_flush.py": { "batch_id": 6, "status": "done", "needs_full_rescan": False, }, "frontend/src/composables/files/useFilesBrowse.ts": { "batch_id": 6, "status": "done", "needs_full_rescan": False, }, "frontend/src/stores/files.ts": {"batch_id": 6, "status": "done", "needs_full_rescan": False}, } EXCLUDED_JUNK = {"scripts/sedul3hFb"} # Local secrets — not in repo SSOT; example only in docker/.env.example EXCLUDED_LOCAL = {"docker/.env", ".env"} LARGE_FILE_THRESHOLD = 800 # Planned batch assignments (relative path -> batch_id) PLANNED_BATCHES: dict[str, int] = {} def _add_batch(batch_id: int, paths: list[str]) -> None: for p in paths: PLANNED_BATCHES[p] = batch_id # Phase A B7-B16 _add_batch(7, ["server/api/servers.py"]) _add_batch(8, ["server/api/sync_v2.py"]) _add_batch( 9, [ "server/api/webssh.py", "server/api/auth.py", "server/api/websocket.py", "server/api/terminal.py", "server/api/settings.py", ], ) _add_batch( 10, [ "server/api/scripts.py", "server/api/search.py", "server/api/assets.py", "server/api/files.py", "server/api/health.py", ], ) _add_batch( 11, [ "server/api/dependencies.py", "server/api/remote_path_validation.py", "server/api/schema_path_validators.py", "server/api/schemas.py", "server/api/__init__.py", ], ) _add_batch(12, ["server/api/install.py"]) _add_batch(13, ["server/api/agent.py"]) _add_batch(14, ["server/api/auth_jwt.py"]) # Phase B B17-B24 _add_batch(17, ["server/application/services/script_service.py"]) _add_batch(18, ["server/application/services/sync_engine_v2.py"]) _add_batch(19, ["server/application/services/auth_service.py"]) _add_batch( 20, [ "server/application/services/script_job_callback.py", "server/application/services/files_browse_service.py", "server/application/services/server_service.py", "server/application/services/sync_service.py", "server/application/services/script_jobs.py", ], ) _add_batch( 21, [ "server/application/__init__.py", "server/application/services/__init__.py", ], ) _add_batch( 22, [ "server/background/schedule_runner.py", "server/background/retry_runner.py", "server/background/heartbeat_flush.py", "server/background/self_monitor.py", "server/background/script_execution_flush.py", ], ) _add_batch( 23, [ "server/background/ip_allowlist_refresh.py", "server/background/upload_staging_cleanup.py", "server/background/__init__.py", ], ) _add_batch(24, ["server/main.py", "server/config.py", "server/__init__.py"]) # Phase C B25-B34 — filled dynamically below after collecting infra paths INFRA_DB = sorted( [ "server/infrastructure/database/admin_repo.py", "server/infrastructure/database/audit_log_repo.py", "server/infrastructure/database/crypto.py", "server/infrastructure/database/db_credential_repo.py", "server/infrastructure/database/engine_compat.py", "server/infrastructure/database/migrations.py", "server/infrastructure/database/password_preset_repo.py", "server/infrastructure/database/platform_node_repo.py", "server/infrastructure/database/push_schedule_repo.py", "server/infrastructure/database/script_repo.py", "server/infrastructure/database/server_repo.py", "server/infrastructure/database/session.py", "server/infrastructure/database/setting_repo.py", "server/infrastructure/database/ssh_key_preset_repo.py", "server/infrastructure/database/ssh_session_repo.py", "server/infrastructure/database/sync_log_repo.py", "server/infrastructure/database/__init__.py", ] ) INFRA_SSH = sorted( [ "server/infrastructure/ssh/asyncssh_pool.py", "server/infrastructure/ssh/files_capability.py", "server/infrastructure/ssh/remote_archive.py", "server/infrastructure/ssh/remote_probe.py", "server/infrastructure/ssh/remote_shell.py", "server/infrastructure/ssh/__init__.py", ] ) INFRA_OTHER = sorted( [ "server/infrastructure/redis/client.py", "server/infrastructure/redis/script_callback_rate.py", "server/infrastructure/redis/script_execution_store.py", "server/infrastructure/redis/__init__.py", "server/infrastructure/telegram/__init__.py", "server/infrastructure/agent_url.py", "server/infrastructure/subscription_parser.py", "server/infrastructure/__init__.py", ] ) DOMAIN_UTILS = sorted( [ "server/domain/__init__.py", "server/domain/models/__init__.py", "server/domain/repositories/__init__.py", "server/utils/files_chmod_policy.py", "server/utils/files_elevation.py", "server/utils/posix_paths.py", "server/utils/sync_error_message.py", "server/utils/text_io.py", "server/utils/unix_ls.py", ] ) def _chunk(paths: list[str], size: int) -> list[list[str]]: return [paths[i : i + size] for i in range(0, len(paths), size)] bid = 25 for chunk in _chunk(INFRA_DB, 5): _add_batch(bid, chunk) bid += 1 for chunk in _chunk(INFRA_SSH, 5): _add_batch(bid, chunk) bid += 1 _add_batch(bid, INFRA_OTHER) bid += 1 for chunk in _chunk(DOMAIN_UTILS, 5): _add_batch(bid, chunk) bid += 1 # Phase D frontend — pages B35+ FRONTEND_PAGES = sorted((ROOT / "frontend/src/pages").glob("*.vue")) FRONTEND_PAGES = [str(p.relative_to(ROOT)).replace("\\", "/") for p in FRONTEND_PAGES] bid = 35 for chunk in _chunk(FRONTEND_PAGES, 5): _add_batch(bid, chunk) bid += 1 # composables — useTerminalSessions alone first (large) _add_batch(bid, ["frontend/src/composables/terminal/useTerminalSessions.ts"]) bid += 1 COMPOSABLES = sorted( (ROOT / "frontend/src/composables").rglob("*"), key=lambda p: str(p), ) COMPOSABLES = [ str(p.relative_to(ROOT)).replace("\\", "/") for p in COMPOSABLES if p.is_file() and p.suffix in {".ts", ".vue"} and "useTerminalSessions.ts" not in p.name ] for chunk in _chunk(COMPOSABLES, 5): _add_batch(bid, chunk) bid += 1 COMPONENTS = sorted( (ROOT / "frontend/src/components").rglob("*.vue"), key=lambda p: str(p), ) COMPONENTS = [str(p.relative_to(ROOT)).replace("\\", "/") for p in COMPONENTS] for chunk in _chunk(COMPONENTS, 5): _add_batch(bid, chunk) bid += 1 _add_batch( bid, [ "frontend/src/api/index.ts", "frontend/src/stores/auth.ts", "frontend/src/stores/files.ts", "frontend/src/router/index.ts", "frontend/src/App.vue", ], ) bid += 1 FRONTEND_REST = sorted( p for p in (ROOT / "frontend/src").rglob("*") if p.is_file() and p.suffix in {".ts", ".scss"} and "assets" not in p.parts and p not in {ROOT / f for f in [ "frontend/src/api/index.ts", "frontend/src/stores/auth.ts", "frontend/src/stores/files.ts", "frontend/src/router/index.ts", "frontend/src/App.vue", "frontend/src/main.ts", ]} ) FRONTEND_REST_PATHS = [] for p in FRONTEND_REST: rel = str(p.relative_to(ROOT)).replace("\\", "/") if rel.startswith("frontend/src/pages/"): continue if rel.startswith("frontend/src/composables/"): continue if rel.startswith("frontend/src/components/"): continue if rel.endswith((".png", ".svg", ".jpg", ".ico")): continue FRONTEND_REST_PATHS.append(rel) for chunk in _chunk(sorted(FRONTEND_REST_PATHS), 5): _add_batch(bid, chunk) bid += 1 if (ROOT / "frontend/src/main.ts").exists(): PLANNED_BATCHES.setdefault("frontend/src/main.ts", bid) # Phase E _add_batch( bid, [ "web/app/index.html", "web/app/install.html", "web/app/api.js", "web/app/layout.js", "web/app/preview.html", ], ) bid += 1 _add_batch( bid, [ "web/app/preview-v3.html", "web/app/preview-v4.html", "web/app/preview-v5.html", "web/agent/agent.py", "web/agent/agent.sh", ], ) bid += 1 _add_batch( bid, [ "web/agent/install.sh", "web/agent/uninstall.sh", "web/agent/config.example.json", ], ) # Phase F tests TESTS = sorted((ROOT / "tests").rglob("*.py")) TESTS = [str(p.relative_to(ROOT)).replace("\\", "/") for p in TESTS] for chunk in _chunk(TESTS, 5): _add_batch(bid, chunk) bid += 1 DEPLOY = sorted( p for p in (ROOT / "deploy").rglob("*") if p.is_file() and p.name != "gate_log.jsonl" ) DEPLOY = [str(p.relative_to(ROOT)).replace("\\", "/") for p in DEPLOY] for chunk in _chunk(DEPLOY, 5): _add_batch(bid, chunk) bid += 1 SCRIPTS = sorted( p for p in (ROOT / "scripts").rglob("*") if p.is_file() and p.name not in EXCLUDED_JUNK ) SCRIPTS = [str(p.relative_to(ROOT)).replace("\\", "/") for p in SCRIPTS] for chunk in _chunk(SCRIPTS, 5): _add_batch(bid, chunk) bid += 1 DOCKER_DEPLOY_ROOT = sorted( [ "docker/generate_env.py", "docker/sync_root_env.py", "docker/entrypoint.sh", "docker/.env.example", "docker/README.md", "Dockerfile", ".dockerignore", ] ) _add_batch(bid, [p for p in DOCKER_DEPLOY_ROOT if (ROOT / p).exists()]) bid += 1 _add_batch( bid, [ "frontend/vite.config.mts", "frontend/package.json", "requirements.txt", "requirements-dev.txt", "ruff.toml", ], ) def collect_must_audit() -> list[Path]: paths: list[Path] = [] # server py paths.extend(ROOT.glob("server/**/*.py")) # frontend src for p in (ROOT / "frontend/src").rglob("*"): if not p.is_file(): continue if p.suffix not in {".ts", ".vue", ".scss"}: continue if "assets" in p.parts and p.suffix in {".png", ".svg", ".jpg", ".ico"}: continue paths.append(p) # web app root html/js for name in ["*.html", "*.js"]: paths.extend((ROOT / "web/app").glob(name)) # agent paths.extend((ROOT / "web/agent").rglob("*")) paths = [p for p in paths if p.is_file()] # tests paths.extend((ROOT / "tests").rglob("*.py")) # deploy scripts docker (selected) for p in (ROOT / "deploy").rglob("*"): if p.is_file() and p.name != "gate_log.jsonl": paths.append(p) for p in (ROOT / "scripts").rglob("*"): if p.is_file() and p.name not in EXCLUDED_JUNK: paths.append(p) for p in (ROOT / "docker").rglob("*"): if p.is_file(): paths.append(p) for extra in [ "Dockerfile", ".dockerignore", "frontend/vite.config.mts", "frontend/package.json", "requirements.txt", "requirements-dev.txt", "ruff.toml", ]: ep = ROOT / extra if ep.exists(): paths.append(ep) # dedupe seen: set[str] = set() out: list[Path] = [] for p in sorted(set(paths), key=lambda x: str(x)): rel = str(p.relative_to(ROOT)).replace("\\", "/") if rel in seen: continue seen.add(rel) out.append(p) return out def line_count(path: Path) -> int: try: return sum(1 for _ in path.open("rb")) except OSError: return 0 def main() -> None: must = collect_must_audit() entries: list[dict] = [] covered_assets = 0 for path in sorted((ROOT / "web/app/assets").rglob("*"), key=lambda x: str(x)): if not path.is_file(): continue rel = str(path.relative_to(ROOT)).replace("\\", "/") entries.append( { "path": rel, "category": "generated", "lines": line_count(path), "batch_id": None, "status": "covered-by-frontend-src", "needs_full_rescan": False, "planned_batch": "B-Assets", } ) covered_assets += 1 for path in must: rel = str(path.relative_to(ROOT)).replace("\\", "/") if rel in EXCLUDED_JUNK: entries.append( { "path": rel, "category": "excluded-junk", "lines": line_count(path), "batch_id": None, "status": "excluded", "needs_full_rescan": False, } ) continue if rel in EXCLUDED_LOCAL: entries.append( { "path": rel, "category": "excluded-local", "lines": line_count(path), "batch_id": None, "status": "excluded", "needs_full_rescan": False, "note": "local secrets; audit docker/.env.example instead", } ) continue prev = BATCH_1_6.get(rel, {}) planned = PLANNED_BATCHES.get(rel) n = line_count(path) large = n > LARGE_FILE_THRESHOLD entry = { "path": rel, "category": "must_audit", "lines": n, "large_file": large, "batch_id": prev.get("batch_id") or planned, "planned_batch": planned, "status": prev.get("status", "pending"), "needs_full_rescan": prev.get("needs_full_rescan", False), "read_range": None, "findings_count": 0, } entries.append(entry) must_audit = [e for e in entries if e["category"] == "must_audit"] pending = sum(1 for e in must_audit if e["status"] == "pending") partial_rescan = sum(1 for e in must_audit if e.get("needs_full_rescan")) done = sum(1 for e in must_audit if e["status"] == "done") doc = { "generated_at": datetime.now(timezone.utc).isoformat(), "summary": { "total_must_audit": len(must_audit), "pending": pending, "done": done, "partial_rescan": partial_rescan, "covered_by_frontend_src_assets": covered_assets, "excluded_junk": len(EXCLUDED_JUNK), "max_batch_id": max(PLANNED_BATCHES.values()) if PLANNED_BATCHES else 0, }, "entries": entries, } OUT.parent.mkdir(parents=True, exist_ok=True) OUT.write_text(json.dumps(doc, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") print(json.dumps(doc["summary"], indent=2)) if __name__ == "__main__": main()