{"version": "2.1.0", "$schema": "https://json.schemastore.org/sarif-2.1.0.json", "runs": [{"tool": {"driver": {"name": "Repobility", "informationUri": "https://repobility.com", "rules": [{"id": "scanner-b37958f59f27e0d0", "name": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/components/dashboard/stat-card.tsx:24", "shortDescription": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/components/dashboard/stat-card.tsx:24"}, "fullDescription": {"text": "A truncated value should reveal the full text on hover. Pass the full string via `title={...}` so the user can read it.\n\nWhy: P2 in CHECKLIST.md \u2014 truncate without hover-reveal.\nRule id: fq.truncate.no-title"}, "properties": {"scanner": "scanner-primary", "layer": "frontend", "severity": "info", "confidence": 1.0}}, {"id": "scanner-2897b68d13e613b3", "name": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/routes/upload.tsx:202", "shortDescription": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/routes/upload.tsx:202"}, "fullDescription": {"text": "A truncated value should reveal the full text on hover. Pass the full string via `title={...}` so the user can read it.\n\nWhy: P2 in CHECKLIST.md \u2014 truncate without hover-reveal.\nRule id: fq.truncate.no-title"}, "properties": {"scanner": "scanner-primary", "layer": "frontend", "severity": "info", "confidence": 1.0}}, {"id": "scanner-d63da3583b14afc0", "name": "Dockerfile runs as root: Dockerfile", "shortDescription": {"text": "Dockerfile runs as root: Dockerfile"}, "fullDescription": {"text": "No non-root USER set. Containers running as root expand the blast radius of any vulnerability inside the image."}, "properties": {"scanner": "scanner-primary", "layer": "hardware", "severity": "medium", "confidence": 1.0}}, {"id": "scanner-8987d6d1c30c7202", "name": "Docker base image is tag-pinned but not digest-pinned: node:20-alpine", "shortDescription": {"text": "Docker base image is tag-pinned but not digest-pinned: node:20-alpine"}, "fullDescription": {"text": "Container tags can be retagged upstream. Pin production base images to a reviewed digest (`image@sha256:...`) when reproducibility and supply-chain integrity matter."}, "properties": {"scanner": "scanner-primary", "layer": "hardware", "severity": "low", "confidence": 1.0}}, {"id": "scanner-e066691601852931", "name": "Docker base image is tag-pinned but not digest-pinned: python:3.11-slim", "shortDescription": {"text": "Docker base image is tag-pinned but not digest-pinned: python:3.11-slim"}, "fullDescription": {"text": "Container tags can be retagged upstream. Pin production base images to a reviewed digest (`image@sha256:...`) when reproducibility and supply-chain integrity matter."}, "properties": {"scanner": "scanner-primary", "layer": "hardware", "severity": "low", "confidence": 1.0}}, {"id": "scanner-aa5acaa49eb8315b", "name": "Containers defined but no K8s/orchestration manifest found", "shortDescription": {"text": "Containers defined but no K8s/orchestration manifest found"}, "fullDescription": {"text": "Repo has Dockerfiles/compose but no Kubernetes/Nomad manifests. If the target deployment is K8s, the manifests may live in a separate ops repo."}, "properties": {"scanner": "scanner-primary", "layer": "hardware", "severity": "low", "confidence": 1.0}}, {"id": "scanner-6372cebde0220094", "name": "No auth library detected", "shortDescription": {"text": "No auth library detected"}, "fullDescription": {"text": "The scanner did not find any standard auth library (JWT, OAuth, NextAuth, Auth0, etc.). The repo has auth/admin/session surface indicators, so auth may live in custom code, in a separate service, or be missing."}, "properties": {"scanner": "scanner-primary", "layer": "security", "severity": "medium", "confidence": 1.0}}, {"id": "scanner-4601e3ad3bb28677", "name": "No CI/CD pipelines detected", "shortDescription": {"text": "No CI/CD pipelines detected"}, "fullDescription": {"text": "No GitHub Actions, GitLab CI, or CircleCI configs found. Without CI you can't gate deploys on tests/lints."}, "properties": {"scanner": "scanner-primary", "layer": "cicd", "severity": "medium", "confidence": 1.0}}, {"id": "scanner-6893a6c8b0861585", "name": "Very low test-to-source ratio", "shortDescription": {"text": "Very low test-to-source ratio"}, "fullDescription": {"text": "4 test file(s) for 76 source file(s) (ratio 0.05). Consider adding integration or unit tests for critical paths."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "medium", "confidence": 1.0}}, {"id": "scanner-141b30a41e03817b", "name": "No license file detected", "shortDescription": {"text": "No license file detected"}, "fullDescription": {"text": "No LICENSE/COPYING/NOTICE file was found. Generated repositories often omit licensing, which blocks reuse and automated intake."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "low", "confidence": 1.0}}, {"id": "scanner-b9088664ace7f748", "name": "Composite production-readiness gap", "shortDescription": {"text": "Composite production-readiness gap"}, "fullDescription": {"text": "Multiple low-cost hardening controls are missing together: license, ci. Opus verification showed these co-occurring gaps are a better readiness signal than reading each flag in isolation."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "low", "confidence": 1.0}}, {"id": "scanner-ea8f3013f588db25", "name": "Shallow git history limits provenance confidence", "shortDescription": {"text": "Shallow git history limits provenance confidence"}, "fullDescription": {"text": "The repository is a shallow clone. Origin/evolution analysis cannot distinguish fresh generation, imported legacy code, or long-lived human code with high confidence."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "low", "confidence": 1.0}}, {"id": "scanner-8424db9c75e04ba4", "name": "Very short observed git history", "shortDescription": {"text": "Very short observed git history"}, "fullDescription": {"text": "The repo has multiple source files but two or fewer visible commits. This is not a failure by itself, but it lowers confidence in evolution-based diagnosis."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "info", "confidence": 1.0}}, {"id": "scanner-b972fd95e7489f1c", "name": "`fetch()` without try/.catch or AbortSignal \u2014 frontend/src/lib/api/client.ts:23", "shortDescription": {"text": "`fetch()` without try/.catch or AbortSignal \u2014 frontend/src/lib/api/client.ts:23"}, "fullDescription": {"text": "Bare `fetch(...)` will throw an unhandled rejection on network failure. Wrap in try/catch, attach a `.catch(...)`, or pass an AbortSignal with a timeout."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "medium", "confidence": 1.0}}, {"id": "scanner-741b7e1c737b6a11", "name": "Likely N+1 query: `for student in \u2026` triggers a query per row \u2014 core/views.py:355", "shortDescription": {"text": "Likely N+1 query: `for student in \u2026` triggers a query per row \u2014 core/views.py:355"}, "fullDescription": {"text": "The loop iterates a Django queryset and accesses `student.records.count` per row, which fires an additional database query each iteration. Add `.select_related('<fk_field>')` for FK / O2O relations or `.prefetch_related('<m2m_or_reverse>')` for many-related lookups, to load everything in 1-2 queries instead of N."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "medium", "confidence": 1.0}}, {"id": "scanner-37d59ef4213a5b52", "name": "Likely N+1 query: `for r in \u2026` triggers a query per row \u2014 core/views.py:405", "shortDescription": {"text": "Likely N+1 query: `for r in \u2026` triggers a query per row \u2014 core/views.py:405"}, "fullDescription": {"text": "The loop iterates a Django queryset and accesses `r.date.isoformat` per row, which fires an additional database query each iteration. Add `.select_related('<fk_field>')` for FK / O2O relations or `.prefetch_related('<m2m_or_reverse>')` for many-related lookups, to load everything in 1-2 queries instead of N."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "medium", "confidence": 1.0}}, {"id": "scanner-4930060e715c0792", "name": "Likely N+1 query: `for r in \u2026` triggers a query per row \u2014 core/views.py:748", "shortDescription": {"text": "Likely N+1 query: `for r in \u2026` triggers a query per row \u2014 core/views.py:748"}, "fullDescription": {"text": "The loop iterates a Django queryset and accesses `r.date.isoformat` per row, which fires an additional database query each iteration. Add `.select_related('<fk_field>')` for FK / O2O relations or `.prefetch_related('<m2m_or_reverse>')` for many-related lookups, to load everything in 1-2 queries instead of N."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "medium", "confidence": 1.0}}, {"id": "scanner-d56d45bb942ace5b", "name": "Likely N+1 query: `for r in \u2026` triggers a query per row \u2014 core/views.py:797", "shortDescription": {"text": "Likely N+1 query: `for r in \u2026` triggers a query per row \u2014 core/views.py:797"}, "fullDescription": {"text": "The loop iterates a Django queryset and accesses `r.date.isoformat` per row, which fires an additional database query each iteration. Add `.select_related('<fk_field>')` for FK / O2O relations or `.prefetch_related('<m2m_or_reverse>')` for many-related lookups, to load everything in 1-2 queries instead of N."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "medium", "confidence": 1.0}}, {"id": "scanner-0bd6337e63b401bf", "name": "Likely N+1 query: `for s in \u2026` triggers a query per row \u2014 core/views.py:846", "shortDescription": {"text": "Likely N+1 query: `for s in \u2026` triggers a query per row \u2014 core/views.py:846"}, "fullDescription": {"text": "The loop iterates a Django queryset and accesses `s.records.all` per row, which fires an additional database query each iteration. Add `.select_related('<fk_field>')` for FK / O2O relations or `.prefetch_related('<m2m_or_reverse>')` for many-related lookups, to load everything in 1-2 queries instead of N."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "medium", "confidence": 1.0}}, {"id": "scanner-85d7dc9e2f798b1b", "name": "Likely N+1 query: `for r in \u2026` triggers a query per row \u2014 core/views.py:848", "shortDescription": {"text": "Likely N+1 query: `for r in \u2026` triggers a query per row \u2014 core/views.py:848"}, "fullDescription": {"text": "The loop iterates a Django queryset and accesses `r.date.isoformat` per row, which fires an additional database query each iteration. Add `.select_related('<fk_field>')` for FK / O2O relations or `.prefetch_related('<m2m_or_reverse>')` for many-related lookups, to load everything in 1-2 queries instead of N."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "medium", "confidence": 1.0}}, {"id": "scanner-8e047ab7af03962f", "name": "7 env vars used in code but missing from .env.example", "shortDescription": {"text": "7 env vars used in code but missing from .env.example"}, "fullDescription": {"text": "Drift between code and config docs. The first few: `DB_DIR`, `DEBUG`, `DJANGO_SECRET_KEY`, `SILICONFLOW_API_KEY`, `SILICONFLOW_API_URL`, `SILICONFLOW_MODEL`, `SILICONFLOW_TIMEOUT`. Add them (with a placeholder/comment) to .env.example so onboarding doesn't break."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "low", "confidence": 1.0}}, {"id": "scanner-2c04133e54348533", "name": "Near-duplicate function bodies in 2 places", "shortDescription": {"text": "Near-duplicate function bodies in 2 places"}, "fullDescription": {"text": "Functions with the same first-5-line body hash:\ncore/views.py:delete_records_by_date, core/views.py:delete_record\n\nThis is *the* AI-coder failure mode (4\u00d7 more duplication in vibe-coded repos \u2014 see https://jw.hn/ai-code-hygiene). Consolidate or document why they're separate."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "low", "confidence": 1.0}}, {"id": "scanner-1abf2bd471ac57f2", "name": "Frontend route `upload` has no Link/navigate to it \u2014 frontend/src/App.tsx", "shortDescription": {"text": "Frontend route `upload` has no Link/navigate to it \u2014 frontend/src/App.tsx"}, "fullDescription": {"text": "The route is registered but no `<Link to=\u2026>` or `navigate(\u2026)` in the codebase navigates here. Either it's reachable only via direct URL (intentional), it's dead, or the link broke during a refactor."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "medium", "confidence": 1.0}}, {"id": "scanner-c029ecab8b82116a", "name": "Frontend route `students` has no Link/navigate to it \u2014 frontend/src/App.tsx", "shortDescription": {"text": "Frontend route `students` has no Link/navigate to it \u2014 frontend/src/App.tsx"}, "fullDescription": {"text": "The route is registered but no `<Link to=\u2026>` or `navigate(\u2026)` in the codebase navigates here. Either it's reachable only via direct URL (intentional), it's dead, or the link broke during a refactor."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "medium", "confidence": 1.0}}, {"id": "scanner-17bacb17f6efb788", "name": "Frontend route `settings` has no Link/navigate to it \u2014 frontend/src/App.tsx", "shortDescription": {"text": "Frontend route `settings` has no Link/navigate to it \u2014 frontend/src/App.tsx"}, "fullDescription": {"text": "The route is registered but no `<Link to=\u2026>` or `navigate(\u2026)` in the codebase navigates here. Either it's reachable only via direct URL (intentional), it's dead, or the link broke during a refactor."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "medium", "confidence": 1.0}}, {"id": "scanner-377cd837d8e71cba", "name": "Frontend route `academic-years` has no Link/navigate to it \u2014 frontend/src/App.tsx", "shortDescription": {"text": "Frontend route `academic-years` has no Link/navigate to it \u2014 frontend/src/App.tsx"}, "fullDescription": {"text": "The route is registered but no `<Link to=\u2026>` or `navigate(\u2026)` in the codebase navigates here. Either it's reachable only via direct URL (intentional), it's dead, or the link broke during a refactor."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "medium", "confidence": 1.0}}, {"id": "scanner-ecf11aa35f162ccb", "name": "Frontend route `data` has no Link/navigate to it \u2014 frontend/src/App.tsx", "shortDescription": {"text": "Frontend route `data` has no Link/navigate to it \u2014 frontend/src/App.tsx"}, "fullDescription": {"text": "The route is registered but no `<Link to=\u2026>` or `navigate(\u2026)` in the codebase navigates here. Either it's reachable only via direct URL (intentional), it's dead, or the link broke during a refactor."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "medium", "confidence": 1.0}}, {"id": "scanner-335aaaa3f45ac2c7", "name": "Dangling fetch: GET /api/test/ (frontend/tests/api-client.test.ts:22)", "shortDescription": {"text": "Dangling fetch: GET /api/test/ (frontend/tests/api-client.test.ts:22)"}, "fullDescription": {"text": "`frontend/tests/api-client.test.ts:22` calls `GET /api/test/` but no backend route matches that path. This is a runtime 404 waiting to happen.\n\nTool: helper:request\nNormalized path used for matching: `/test`\nIf this points at an external API, prefix it with `https://` so the matcher skips it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "high", "confidence": 1.0}}, {"id": "scanner-73be53912eb0b843", "name": "Dangling fetch: GET /api/test/ (frontend/tests/api-client.test.ts:33)", "shortDescription": {"text": "Dangling fetch: GET /api/test/ (frontend/tests/api-client.test.ts:33)"}, "fullDescription": {"text": "`frontend/tests/api-client.test.ts:33` calls `GET /api/test/` but no backend route matches that path. This is a runtime 404 waiting to happen.\n\nTool: helper:request\nNormalized path used for matching: `/test`\nIf this points at an external API, prefix it with `https://` so the matcher skips it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "high", "confidence": 1.0}}, {"id": "scanner-ea5bda5b2079c3f4", "name": "Dangling fetch: POST /api/test/ (frontend/tests/api-client.test.ts:39)", "shortDescription": {"text": "Dangling fetch: POST /api/test/ (frontend/tests/api-client.test.ts:39)"}, "fullDescription": {"text": "`frontend/tests/api-client.test.ts:39` calls `POST /api/test/` but no backend route matches that path. This is a runtime 404 waiting to happen.\n\nTool: helper:request\nNormalized path used for matching: `/test`\nIf this points at an external API, prefix it with `https://` so the matcher skips it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "high", "confidence": 1.0}}, {"id": "scanner-64e533775d5ef946", "name": "Dangling fetch: GET /api/test/ (frontend/tests/api-client.test.ts:48)", "shortDescription": {"text": "Dangling fetch: GET /api/test/ (frontend/tests/api-client.test.ts:48)"}, "fullDescription": {"text": "`frontend/tests/api-client.test.ts:48` calls `GET /api/test/` but no backend route matches that path. This is a runtime 404 waiting to happen.\n\nTool: helper:request\nNormalized path used for matching: `/test`\nIf this points at an external API, prefix it with `https://` so the matcher skips it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "high", "confidence": 1.0}}, {"id": "scanner-2c1f2df2608a921d", "name": "Unused endpoint: ANY /api/records/", "shortDescription": {"text": "Unused endpoint: ANY /api/records/"}, "fullDescription": {"text": "`core/urls.py` declares `ANY /api/records/` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-e18da83368313ad4", "name": "Unused endpoint: ANY /api/records/save/", "shortDescription": {"text": "Unused endpoint: ANY /api/records/save/"}, "fullDescription": {"text": "`core/urls.py` declares `ANY /api/records/save/` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-b308003d55647418", "name": "Unused endpoint: ANY /api/records/daily-details/", "shortDescription": {"text": "Unused endpoint: ANY /api/records/daily-details/"}, "fullDescription": {"text": "`core/urls.py` declares `ANY /api/records/daily-details/` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-478de731f8b116bb", "name": "Unused endpoint: ANY /api/records/<int:record_id>/", "shortDescription": {"text": "Unused endpoint: ANY /api/records/<int:record_id>/"}, "fullDescription": {"text": "`core/urls.py` declares `ANY /api/records/<int:record_id>/` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-ec7ee58ee7f71071", "name": "Unused endpoint: ANY /api/records/<int:record_id>/update/", "shortDescription": {"text": "Unused endpoint: ANY /api/records/<int:record_id>/update/"}, "fullDescription": {"text": "`core/urls.py` declares `ANY /api/records/<int:record_id>/update/` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-90230aad85a7bf27", "name": "Unused endpoint: ANY /api/records/<int:record_id>/delete/", "shortDescription": {"text": "Unused endpoint: ANY /api/records/<int:record_id>/delete/"}, "fullDescription": {"text": "`core/urls.py` declares `ANY /api/records/<int:record_id>/delete/` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-e101d29e72f4f7a7", "name": "Unused endpoint: ANY /api/records/delete-by-date/", "shortDescription": {"text": "Unused endpoint: ANY /api/records/delete-by-date/"}, "fullDescription": {"text": "`core/urls.py` declares `ANY /api/records/delete-by-date/` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-ae1d262ef815dc04", "name": "Unused endpoint: ANY /api/students/", "shortDescription": {"text": "Unused endpoint: ANY /api/students/"}, "fullDescription": {"text": "`core/urls.py` declares `ANY /api/students/` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-9c27cf94fe7d61db", "name": "Unused endpoint: ANY /api/students/add/", "shortDescription": {"text": "Unused endpoint: ANY /api/students/add/"}, "fullDescription": {"text": "`core/urls.py` declares `ANY /api/students/add/` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-d137acaf976c0630", "name": "Unused endpoint: ANY /api/students/<int:student_id>/", "shortDescription": {"text": "Unused endpoint: ANY /api/students/<int:student_id>/"}, "fullDescription": {"text": "`core/urls.py` declares `ANY /api/students/<int:student_id>/` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-f9166ef13355bf0b", "name": "Unused endpoint: ANY /api/students/<int:student_id>/delete/", "shortDescription": {"text": "Unused endpoint: ANY /api/students/<int:student_id>/delete/"}, "fullDescription": {"text": "`core/urls.py` declares `ANY /api/students/<int:student_id>/delete/` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-73dcf561b2c395e5", "name": "Unused endpoint: ANY /api/students/<int:student_id>/history/", "shortDescription": {"text": "Unused endpoint: ANY /api/students/<int:student_id>/history/"}, "fullDescription": {"text": "`core/urls.py` declares `ANY /api/students/<int:student_id>/history/` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-409190f28eee2940", "name": "Unused endpoint: ANY /api/students/focused/", "shortDescription": {"text": "Unused endpoint: ANY /api/students/focused/"}, "fullDescription": {"text": "`core/urls.py` declares `ANY /api/students/focused/` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-2cfbfbdc379dcca2", "name": "Unused endpoint: ANY /api/config/<str:key>/", "shortDescription": {"text": "Unused endpoint: ANY /api/config/<str:key>/"}, "fullDescription": {"text": "`core/urls.py` declares `ANY /api/config/<str:key>/` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-94ebaf323fcaa31e", "name": "Unused endpoint: ANY /api/config/<str:key>/set/", "shortDescription": {"text": "Unused endpoint: ANY /api/config/<str:key>/set/"}, "fullDescription": {"text": "`core/urls.py` declares `ANY /api/config/<str:key>/set/` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-0f98ca0e7fde2cfa", "name": "Unused endpoint: ANY /api/stats/", "shortDescription": {"text": "Unused endpoint: ANY /api/stats/"}, "fullDescription": {"text": "`core/urls.py` declares `ANY /api/stats/` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-6f9eb5c987a2cf6b", "name": "Unused endpoint: ANY /api/stats/range/", "shortDescription": {"text": "Unused endpoint: ANY /api/stats/range/"}, "fullDescription": {"text": "`core/urls.py` declares `ANY /api/stats/range/` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-b9d77a84e1117616", "name": "Unused endpoint: ANY /api/data/export/", "shortDescription": {"text": "Unused endpoint: ANY /api/data/export/"}, "fullDescription": {"text": "`core/urls.py` declares `ANY /api/data/export/` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-4bed60c9cd11066d", "name": "Unused endpoint: ANY /api/data/import/", "shortDescription": {"text": "Unused endpoint: ANY /api/data/import/"}, "fullDescription": {"text": "`core/urls.py` declares `ANY /api/data/import/` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-b2bae1a34d382e4a", "name": "Unused endpoint: ANY /api/data/clear/", "shortDescription": {"text": "Unused endpoint: ANY /api/data/clear/"}, "fullDescription": {"text": "`core/urls.py` declares `ANY /api/data/clear/` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-50167de95c28b5fb", "name": "Unused endpoint: ANY /api/academic-years/", "shortDescription": {"text": "Unused endpoint: ANY /api/academic-years/"}, "fullDescription": {"text": "`core/urls.py` declares `ANY /api/academic-years/` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-2755403003d9aaf1", "name": "Unused endpoint: ANY /api/academic-years/add/", "shortDescription": {"text": "Unused endpoint: ANY /api/academic-years/add/"}, "fullDescription": {"text": "`core/urls.py` declares `ANY /api/academic-years/add/` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-fc100c0c1c163f6b", "name": "Unused endpoint: ANY /api/academic-years/<int:year_id>/update/", "shortDescription": {"text": "Unused endpoint: ANY /api/academic-years/<int:year_id>/update/"}, "fullDescription": {"text": "`core/urls.py` declares `ANY /api/academic-years/<int:year_id>/update/` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-3e4b7cb2be3d1491", "name": "Unused endpoint: ANY /api/academic-years/<int:year_id>/delete/", "shortDescription": {"text": "Unused endpoint: ANY /api/academic-years/<int:year_id>/delete/"}, "fullDescription": {"text": "`core/urls.py` declares `ANY /api/academic-years/<int:year_id>/delete/` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-549f42192978c96e", "name": "Unused endpoint: ANY /api/academic-years/<int:year_id>/activate/", "shortDescription": {"text": "Unused endpoint: ANY /api/academic-years/<int:year_id>/activate/"}, "fullDescription": {"text": "`core/urls.py` declares `ANY /api/academic-years/<int:year_id>/activate/` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-7309bf2446873ccc", "name": "Unused endpoint: ANY /admin/", "shortDescription": {"text": "Unused endpoint: ANY /admin/"}, "fullDescription": {"text": "`classrank/urls.py` declares `ANY /admin/` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-ce0a9b4397331ef4", "name": "Unused endpoint: ANY /api/", "shortDescription": {"text": "Unused endpoint: ANY /api/"}, "fullDescription": {"text": "`classrank/urls.py` declares `ANY /api/` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-46c23a0cf63c1ad2", "name": "Unused endpoint: ANY /legacy/", "shortDescription": {"text": "Unused endpoint: ANY /legacy/"}, "fullDescription": {"text": "`classrank/urls.py` declares `ANY /legacy/` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-c470a7ad0244e79c", "name": "Unused endpoint: ANY /^.*$", "shortDescription": {"text": "Unused endpoint: ANY /^.*$"}, "fullDescription": {"text": "`classrank/urls.py` declares `ANY /^.*$` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}]}}, "automationDetails": {"id": "repobility/21314"}, "properties": {"repository": "timzc/classrank", "repoUrl": "https://github.com/timzc/classrank", "branch": "main"}, "results": [{"ruleId": "scanner-b37958f59f27e0d0", "level": "none", "message": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/components/dashboard/stat-card.tsx:24"}, "properties": {"repobilityId": "9f2740a95245e664", "scanner": "scanner-primary", "fingerprint": "b37958f59f27e0d0", "layer": "frontend", "severity": "info", "confidence": 1.0, "tags": ["frontend-quality", "fq.truncate.no-title"]}}, {"ruleId": "scanner-2897b68d13e613b3", "level": "none", "message": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/routes/upload.tsx:202"}, "properties": {"repobilityId": "1a2c12b63d3774de", "scanner": "scanner-primary", "fingerprint": "2897b68d13e613b3", "layer": "frontend", "severity": "info", "confidence": 1.0, "tags": ["frontend-quality", "fq.truncate.no-title"]}}, {"ruleId": "scanner-d63da3583b14afc0", "level": "warning", "message": {"text": "Dockerfile runs as root: Dockerfile"}, "properties": {"repobilityId": "a2ed1bd120e507db", "scanner": "scanner-primary", "fingerprint": "d63da3583b14afc0", "layer": "hardware", "severity": "medium", "confidence": 1.0, "tags": ["security", "container"]}}, {"ruleId": "scanner-8987d6d1c30c7202", "level": "note", "message": {"text": "Docker base image is tag-pinned but not digest-pinned: node:20-alpine"}, "properties": {"repobilityId": "6b3f063efa238aee", "scanner": "scanner-primary", "fingerprint": "8987d6d1c30c7202", "layer": "hardware", "severity": "low", "confidence": 1.0, "tags": ["supply-chain", "docker", "pinned-dependencies"]}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "Dockerfile"}, "region": {"startLine": 2}}}]}, {"ruleId": "scanner-e066691601852931", "level": "note", "message": {"text": "Docker base image is tag-pinned but not digest-pinned: python:3.11-slim"}, "properties": {"repobilityId": "70ac52ce8c663ebc", "scanner": "scanner-primary", "fingerprint": "e066691601852931", "layer": "hardware", "severity": "low", "confidence": 1.0, "tags": ["supply-chain", "docker", "pinned-dependencies"]}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "Dockerfile"}, "region": {"startLine": 11}}}]}, {"ruleId": "scanner-aa5acaa49eb8315b", "level": "note", "message": {"text": "Containers defined but no K8s/orchestration manifest found"}, "properties": {"repobilityId": "b230ea9b68736081", "scanner": "scanner-primary", "fingerprint": "aa5acaa49eb8315b", "layer": "hardware", "severity": "low", "confidence": 1.0, "tags": ["coverage", "deployment"]}}, {"ruleId": "scanner-6372cebde0220094", "level": "warning", "message": {"text": "No auth library detected"}, "properties": {"repobilityId": "a5b6035a5bbf8054", "scanner": "scanner-primary", "fingerprint": "6372cebde0220094", "layer": "security", "severity": "medium", "confidence": 1.0, "tags": ["coverage", "auth"]}}, {"ruleId": "scanner-4601e3ad3bb28677", "level": "warning", "message": {"text": "No CI/CD pipelines detected"}, "properties": {"repobilityId": "c3ee439bce2bc51e", "scanner": "scanner-primary", "fingerprint": "4601e3ad3bb28677", "layer": "cicd", "severity": "medium", "confidence": 1.0, "tags": ["coverage"]}}, {"ruleId": "scanner-6893a6c8b0861585", "level": "warning", "message": {"text": "Very low test-to-source ratio"}, "properties": {"repobilityId": "54a7de3f06314bf0", "scanner": "scanner-primary", "fingerprint": "6893a6c8b0861585", "layer": "quality", "severity": "medium", "confidence": 1.0, "tags": ["tests", "coverage"]}}, {"ruleId": "scanner-141b30a41e03817b", "level": "note", "message": {"text": "No license file detected"}, "properties": {"repobilityId": "cd2c6dd197e6ad0f", "scanner": "scanner-primary", "fingerprint": "141b30a41e03817b", "layer": "quality", "severity": "low", "confidence": 1.0, "tags": ["license", "repo-hardening", "generated-repo-pattern"]}}, {"ruleId": "scanner-b9088664ace7f748", "level": "note", "message": {"text": "Composite production-readiness gap"}, "properties": {"repobilityId": "5fbb51785880dcc8", "scanner": "scanner-primary", "fingerprint": "b9088664ace7f748", "layer": "quality", "severity": "low", "confidence": 1.0, "tags": ["production-readiness", "repo-hardening", "generated-repo-pattern"]}}, {"ruleId": "scanner-ea8f3013f588db25", "level": "note", "message": {"text": "Shallow git history limits provenance confidence"}, "properties": {"repobilityId": "90aaf01ca072ddbe", "scanner": "scanner-primary", "fingerprint": "ea8f3013f588db25", "layer": "quality", "severity": "low", "confidence": 1.0, "tags": ["provenance", "git-history", "generated-repo-pattern"]}}, {"ruleId": "scanner-8424db9c75e04ba4", "level": "none", "message": {"text": "Very short observed git history"}, "properties": {"repobilityId": "48fd09ad84a1ef85", "scanner": "scanner-primary", "fingerprint": "8424db9c75e04ba4", "layer": "quality", "severity": "info", "confidence": 1.0, "tags": ["provenance", "git-history", "generated-repo-pattern"]}}, {"ruleId": "scanner-b972fd95e7489f1c", "level": "warning", "message": {"text": "`fetch()` without try/.catch or AbortSignal \u2014 frontend/src/lib/api/client.ts:23"}, "properties": {"repobilityId": "0f48640f802513d8", "scanner": "scanner-primary", "fingerprint": "b972fd95e7489f1c", "layer": "quality", "severity": "medium", "confidence": 1.0, "tags": ["integrity", "fragile-runtime", "robustness"]}}, {"ruleId": "scanner-741b7e1c737b6a11", "level": "warning", "message": {"text": "Likely N+1 query: `for student in \u2026` triggers a query per row \u2014 core/views.py:355"}, "properties": {"repobilityId": "1c82637bc1a4646e", "scanner": "scanner-primary", "fingerprint": "741b7e1c737b6a11", "layer": "quality", "severity": "medium", "confidence": 1.0, "tags": ["integrity", "n-plus-one", "performance"]}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "core/views.py"}, "region": {"startLine": 355}}}]}, {"ruleId": "scanner-37d59ef4213a5b52", "level": "warning", "message": {"text": "Likely N+1 query: `for r in \u2026` triggers a query per row \u2014 core/views.py:405"}, "properties": {"repobilityId": "c7a9d9092309067b", "scanner": "scanner-primary", "fingerprint": "37d59ef4213a5b52", "layer": "quality", "severity": "medium", "confidence": 1.0, "tags": ["integrity", "n-plus-one", "performance"]}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "core/views.py"}, "region": {"startLine": 405}}}]}, {"ruleId": "scanner-4930060e715c0792", "level": "warning", "message": {"text": "Likely N+1 query: `for r in \u2026` triggers a query per row \u2014 core/views.py:748"}, "properties": {"repobilityId": "ef562a18b6ab20ad", "scanner": "scanner-primary", "fingerprint": "4930060e715c0792", "layer": "quality", "severity": "medium", "confidence": 1.0, "tags": ["integrity", "n-plus-one", "performance"]}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "core/views.py"}, "region": {"startLine": 748}}}]}, {"ruleId": "scanner-d56d45bb942ace5b", "level": "warning", "message": {"text": "Likely N+1 query: `for r in \u2026` triggers a query per row \u2014 core/views.py:797"}, "properties": {"repobilityId": "a9f4dbc93f6f0a94", "scanner": "scanner-primary", "fingerprint": "d56d45bb942ace5b", "layer": "quality", "severity": "medium", "confidence": 1.0, "tags": ["integrity", "n-plus-one", "performance"]}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "core/views.py"}, "region": {"startLine": 797}}}]}, {"ruleId": "scanner-0bd6337e63b401bf", "level": "warning", "message": {"text": "Likely N+1 query: `for s in \u2026` triggers a query per row \u2014 core/views.py:846"}, "properties": {"repobilityId": "961b61323310523d", "scanner": "scanner-primary", "fingerprint": "0bd6337e63b401bf", "layer": "quality", "severity": "medium", "confidence": 1.0, "tags": ["integrity", "n-plus-one", "performance"]}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "core/views.py"}, "region": {"startLine": 846}}}]}, {"ruleId": "scanner-85d7dc9e2f798b1b", "level": "warning", "message": {"text": "Likely N+1 query: `for r in \u2026` triggers a query per row \u2014 core/views.py:848"}, "properties": {"repobilityId": "387a99c3bfebf69b", "scanner": "scanner-primary", "fingerprint": "85d7dc9e2f798b1b", "layer": "quality", "severity": "medium", "confidence": 1.0, "tags": ["integrity", "n-plus-one", "performance"]}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "core/views.py"}, "region": {"startLine": 848}}}]}, {"ruleId": "scanner-8e047ab7af03962f", "level": "note", "message": {"text": "7 env vars used in code but missing from .env.example"}, "properties": {"repobilityId": "5a0455f6498c27ad", "scanner": "scanner-primary", "fingerprint": "8e047ab7af03962f", "layer": "quality", "severity": "low", "confidence": 1.0, "tags": ["integrity", "config-drift"]}}, {"ruleId": "scanner-2c04133e54348533", "level": "note", "message": {"text": "Near-duplicate function bodies in 2 places"}, "properties": {"repobilityId": "e78ddcbedbbf5ec8", "scanner": "scanner-primary", "fingerprint": "2c04133e54348533", "layer": "quality", "severity": "low", "confidence": 1.0, "tags": ["integrity", "duplicate", "dry"]}}, {"ruleId": "scanner-1abf2bd471ac57f2", "level": "warning", "message": {"text": "Frontend route `upload` has no Link/navigate to it \u2014 frontend/src/App.tsx"}, "properties": {"repobilityId": "a320e95df6b785d8", "scanner": "scanner-primary", "fingerprint": "1abf2bd471ac57f2", "layer": "quality", "severity": "medium", "confidence": 1.0, "tags": ["integrity", "orphan-page", "wiring"]}}, {"ruleId": "scanner-c029ecab8b82116a", "level": "warning", "message": {"text": "Frontend route `students` has no Link/navigate to it \u2014 frontend/src/App.tsx"}, "properties": {"repobilityId": "237255ebb37f9f77", "scanner": "scanner-primary", "fingerprint": "c029ecab8b82116a", "layer": "quality", "severity": "medium", "confidence": 1.0, "tags": ["integrity", "orphan-page", "wiring"]}}, {"ruleId": "scanner-17bacb17f6efb788", "level": "warning", "message": {"text": "Frontend route `settings` has no Link/navigate to it \u2014 frontend/src/App.tsx"}, "properties": {"repobilityId": "7a6f6cf42324831f", "scanner": "scanner-primary", "fingerprint": "17bacb17f6efb788", "layer": "quality", "severity": "medium", "confidence": 1.0, "tags": ["integrity", "orphan-page", "wiring"]}}, {"ruleId": "scanner-377cd837d8e71cba", "level": "warning", "message": {"text": "Frontend route `academic-years` has no Link/navigate to it \u2014 frontend/src/App.tsx"}, "properties": {"repobilityId": "629b29eb32a6241d", "scanner": "scanner-primary", "fingerprint": "377cd837d8e71cba", "layer": "quality", "severity": "medium", "confidence": 1.0, "tags": ["integrity", "orphan-page", "wiring"]}}, {"ruleId": "scanner-ecf11aa35f162ccb", "level": "warning", "message": {"text": "Frontend route `data` has no Link/navigate to it \u2014 frontend/src/App.tsx"}, "properties": {"repobilityId": "4d7eda8a4d14afa3", "scanner": "scanner-primary", "fingerprint": "ecf11aa35f162ccb", "layer": "quality", "severity": "medium", "confidence": 1.0, "tags": ["integrity", "orphan-page", "wiring"]}}, {"ruleId": "scanner-335aaaa3f45ac2c7", "level": "error", "message": {"text": "Dangling fetch: GET /api/test/ (frontend/tests/api-client.test.ts:22)"}, "properties": {"repobilityId": "5afbeb91a3edf107", "scanner": "scanner-primary", "fingerprint": "335aaaa3f45ac2c7", "layer": "api", "severity": "high", "confidence": 1.0, "tags": ["wiring", "dangling-fetch", "helper:request"]}}, {"ruleId": "scanner-73be53912eb0b843", "level": "error", "message": {"text": "Dangling fetch: GET /api/test/ (frontend/tests/api-client.test.ts:33)"}, "properties": {"repobilityId": "5bdde6b66d47de38", "scanner": "scanner-primary", "fingerprint": "73be53912eb0b843", "layer": "api", "severity": "high", "confidence": 1.0, "tags": ["wiring", "dangling-fetch", "helper:request"]}}, {"ruleId": "scanner-ea5bda5b2079c3f4", "level": "error", "message": {"text": "Dangling fetch: POST /api/test/ (frontend/tests/api-client.test.ts:39)"}, "properties": {"repobilityId": "c872f944b84c8286", "scanner": "scanner-primary", "fingerprint": "ea5bda5b2079c3f4", "layer": "api", "severity": "high", "confidence": 1.0, "tags": ["wiring", "dangling-fetch", "helper:request"]}}, {"ruleId": "scanner-64e533775d5ef946", "level": "error", "message": {"text": "Dangling fetch: GET /api/test/ (frontend/tests/api-client.test.ts:48)"}, "properties": {"repobilityId": "015e42e8c8fa8e14", "scanner": "scanner-primary", "fingerprint": "64e533775d5ef946", "layer": "api", "severity": "high", "confidence": 1.0, "tags": ["wiring", "dangling-fetch", "helper:request"]}}, {"ruleId": "scanner-2c1f2df2608a921d", "level": "note", "message": {"text": "Unused endpoint: ANY /api/records/"}, "properties": {"repobilityId": "b8ce3db12b31e9d2", "scanner": "scanner-primary", "fingerprint": "2c1f2df2608a921d", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-e18da83368313ad4", "level": "note", "message": {"text": "Unused endpoint: ANY /api/records/save/"}, "properties": {"repobilityId": "43a21f4fe22bb767", "scanner": "scanner-primary", "fingerprint": "e18da83368313ad4", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-b308003d55647418", "level": "note", "message": {"text": "Unused endpoint: ANY /api/records/daily-details/"}, "properties": {"repobilityId": "3635b9eab768583c", "scanner": "scanner-primary", "fingerprint": "b308003d55647418", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-478de731f8b116bb", "level": "note", "message": {"text": "Unused endpoint: ANY /api/records/<int:record_id>/"}, "properties": {"repobilityId": "fc737312931cf299", "scanner": "scanner-primary", "fingerprint": "478de731f8b116bb", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-ec7ee58ee7f71071", "level": "note", "message": {"text": "Unused endpoint: ANY /api/records/<int:record_id>/update/"}, "properties": {"repobilityId": "f9a3ace122dd2af4", "scanner": "scanner-primary", "fingerprint": "ec7ee58ee7f71071", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-90230aad85a7bf27", "level": "note", "message": {"text": "Unused endpoint: ANY /api/records/<int:record_id>/delete/"}, "properties": {"repobilityId": "6f6cf48a39040f63", "scanner": "scanner-primary", "fingerprint": "90230aad85a7bf27", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-e101d29e72f4f7a7", "level": "note", "message": {"text": "Unused endpoint: ANY /api/records/delete-by-date/"}, "properties": {"repobilityId": "592517990b38e5e0", "scanner": "scanner-primary", "fingerprint": "e101d29e72f4f7a7", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-ae1d262ef815dc04", "level": "note", "message": {"text": "Unused endpoint: ANY /api/students/"}, "properties": {"repobilityId": "e6e1f1714d79990e", "scanner": "scanner-primary", "fingerprint": "ae1d262ef815dc04", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-9c27cf94fe7d61db", "level": "note", "message": {"text": "Unused endpoint: ANY /api/students/add/"}, "properties": {"repobilityId": "35eae56853f4d1d3", "scanner": "scanner-primary", "fingerprint": "9c27cf94fe7d61db", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-d137acaf976c0630", "level": "note", "message": {"text": "Unused endpoint: ANY /api/students/<int:student_id>/"}, "properties": {"repobilityId": "f212800ba20148ef", "scanner": "scanner-primary", "fingerprint": "d137acaf976c0630", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-f9166ef13355bf0b", "level": "note", "message": {"text": "Unused endpoint: ANY /api/students/<int:student_id>/delete/"}, "properties": {"repobilityId": "0c5816d713210d11", "scanner": "scanner-primary", "fingerprint": "f9166ef13355bf0b", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-73dcf561b2c395e5", "level": "note", "message": {"text": "Unused endpoint: ANY /api/students/<int:student_id>/history/"}, "properties": {"repobilityId": "886beb483d878479", "scanner": "scanner-primary", "fingerprint": "73dcf561b2c395e5", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-409190f28eee2940", "level": "note", "message": {"text": "Unused endpoint: ANY /api/students/focused/"}, "properties": {"repobilityId": "ea6f688b07c8020d", "scanner": "scanner-primary", "fingerprint": "409190f28eee2940", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-2cfbfbdc379dcca2", "level": "note", "message": {"text": "Unused endpoint: ANY /api/config/<str:key>/"}, "properties": {"repobilityId": "c1d967413694c12d", "scanner": "scanner-primary", "fingerprint": "2cfbfbdc379dcca2", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-94ebaf323fcaa31e", "level": "note", "message": {"text": "Unused endpoint: ANY /api/config/<str:key>/set/"}, "properties": {"repobilityId": "9787fbe7f5aaf23b", "scanner": "scanner-primary", "fingerprint": "94ebaf323fcaa31e", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-0f98ca0e7fde2cfa", "level": "note", "message": {"text": "Unused endpoint: ANY /api/stats/"}, "properties": {"repobilityId": "9aa9272a75f5bd08", "scanner": "scanner-primary", "fingerprint": "0f98ca0e7fde2cfa", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-6f9eb5c987a2cf6b", "level": "note", "message": {"text": "Unused endpoint: ANY /api/stats/range/"}, "properties": {"repobilityId": "70b5de832c3b71e1", "scanner": "scanner-primary", "fingerprint": "6f9eb5c987a2cf6b", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-b9d77a84e1117616", "level": "note", "message": {"text": "Unused endpoint: ANY /api/data/export/"}, "properties": {"repobilityId": "02f7737dee3b34c0", "scanner": "scanner-primary", "fingerprint": "b9d77a84e1117616", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-4bed60c9cd11066d", "level": "note", "message": {"text": "Unused endpoint: ANY /api/data/import/"}, "properties": {"repobilityId": "4f089f75ba836245", "scanner": "scanner-primary", "fingerprint": "4bed60c9cd11066d", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-b2bae1a34d382e4a", "level": "note", "message": {"text": "Unused endpoint: ANY /api/data/clear/"}, "properties": {"repobilityId": "a309b3ae1fc421a2", "scanner": "scanner-primary", "fingerprint": "b2bae1a34d382e4a", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-50167de95c28b5fb", "level": "note", "message": {"text": "Unused endpoint: ANY /api/academic-years/"}, "properties": {"repobilityId": "71747d5c68f96e56", "scanner": "scanner-primary", "fingerprint": "50167de95c28b5fb", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-2755403003d9aaf1", "level": "note", "message": {"text": "Unused endpoint: ANY /api/academic-years/add/"}, "properties": {"repobilityId": "20ad2e1e2d782307", "scanner": "scanner-primary", "fingerprint": "2755403003d9aaf1", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-fc100c0c1c163f6b", "level": "note", "message": {"text": "Unused endpoint: ANY /api/academic-years/<int:year_id>/update/"}, "properties": {"repobilityId": "fa9ec3cdefc7949b", "scanner": "scanner-primary", "fingerprint": "fc100c0c1c163f6b", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-3e4b7cb2be3d1491", "level": "note", "message": {"text": "Unused endpoint: ANY /api/academic-years/<int:year_id>/delete/"}, "properties": {"repobilityId": "4bf34cbc5a9f5d39", "scanner": "scanner-primary", "fingerprint": "3e4b7cb2be3d1491", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-549f42192978c96e", "level": "note", "message": {"text": "Unused endpoint: ANY /api/academic-years/<int:year_id>/activate/"}, "properties": {"repobilityId": "b3c75ce5a8a62a6a", "scanner": "scanner-primary", "fingerprint": "549f42192978c96e", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-7309bf2446873ccc", "level": "note", "message": {"text": "Unused endpoint: ANY /admin/"}, "properties": {"repobilityId": "fe7357dab0381681", "scanner": "scanner-primary", "fingerprint": "7309bf2446873ccc", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-ce0a9b4397331ef4", "level": "note", "message": {"text": "Unused endpoint: ANY /api/"}, "properties": {"repobilityId": "47881a6e714b5fc1", "scanner": "scanner-primary", "fingerprint": "ce0a9b4397331ef4", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-46c23a0cf63c1ad2", "level": "note", "message": {"text": "Unused endpoint: ANY /legacy/"}, "properties": {"repobilityId": "5c8e9db893d57353", "scanner": "scanner-primary", "fingerprint": "46c23a0cf63c1ad2", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-c470a7ad0244e79c", "level": "note", "message": {"text": "Unused endpoint: ANY /^.*$"}, "properties": {"repobilityId": "71d6c565c5bf3adc", "scanner": "scanner-primary", "fingerprint": "c470a7ad0244e79c", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}]}]}