From e98abcbb78ac4c83168669bb242411a9ce0c8c13 Mon Sep 17 00:00:00 2001 From: kodi Date: Sat, 7 Mar 2026 12:12:54 +0100 Subject: [PATCH] kader bestanden toegevoegd --- .gitignore | 6 ++ AGENTS.md | 202 +++++++++++++++++++++++++++++++++++++++ CHANGE_POLICY.md | 130 +++++++++++++++++++++++++ SAFE_FILES.md | 74 ++++++++++++++ container/Containerfile | 40 ++------ contracts/API_GOLDEN.md | 85 ++++++++++++++++ docs/ARCHITECTURE.md | 162 +++++++++++++++++++++++++++++++ feature_test_template.sh | 71 ++++++++++++++ regression_tests.sh | 106 ++++++++++++++++++++ 9 files changed, 845 insertions(+), 31 deletions(-) create mode 100644 .gitignore create mode 100644 AGENTS.md create mode 100644 CHANGE_POLICY.md create mode 100644 SAFE_FILES.md create mode 100644 contracts/API_GOLDEN.md create mode 100644 docs/ARCHITECTURE.md create mode 100755 feature_test_template.sh create mode 100755 regression_tests.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bc353e8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +__pycache__/ +*.pyc +*.log +.venv/ +venv/ +.DS_Store diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..1c0433b --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,202 @@ +# AGENTS.md + +## Project + +Rename-MVP + +Minimalistische webapplicatie voor het hernoemen van TV-series bestanden op basis van metadata van TheTVDB. + +Technologie: + +- Python +- FastAPI +- Rootless Podman +- SQLite +- TheTVDB API v4 + +Project root: + +/home/kodi/.config/rename-mvp + + +--- + +# AI Agent Governance + +Dit project wordt ontwikkeld met behulp van AI-agents (bijvoorbeeld Codex). + +Agents mogen code schrijven en aanpassen, maar **moeten zich strikt houden aan de regels in dit document**. + +Het primaire doel is **stabiliteit van het systeem**. + + +--- + +# Absolute Rules + +Agents mogen NOOIT: + +- bestaande API responses wijzigen +- bestaande endpoints verwijderen +- SAFE FILES aanpassen zonder expliciete toestemming +- projectstructuur wijzigen +- code buiten deze repository aanpassen +- andere Podman containers aanpassen +- systeemconfiguratie wijzigen +- Agents mogen geen wijzigingen maken die het contract in contracts/API_GOLDEN.md breken. +- Agents mogen geen refactors of verbeteringen uitvoeren die niet expliciet gevraagd zijn. + +Agents mogen alleen werken **binnen deze repository**. + + +--- + +# SAFE FILES + +De volgende bestanden zijn kritisch en mogen alleen aangepast worden na expliciete bevestiging van de gebruiker: +Deze bestanden bevatten infrastructuur en API-contracten. + +app/services/tvdb_auth_service.py +container/Containerfile +requirements.txt +contracts/API_GOLDEN.md +AGENTS.md +CHANGE_POLICY.md +SAFE_FILES.md +docs/ARCHITECTURE.md + +--- + +# Escalated Execution + +Agents mogen escalated execution gebruiken voor: + +- podman build +- podman run +- curl tests +- project builds +- dependency install +- bestanden aanmaken of aanpassen binnen dit project + +Agents mogen escalated execution **niet gebruiken voor**: + +- systeemupdates +- firewall wijzigingen +- systemd configuratie +- container prune +- docker/podman cleanup +- andere containers + + +--- + +# Development Workflow + +Elke wijziging moet deze stappen volgen: + +1 analyseer de wijziging +2 beschrijf welke bestanden worden aangepast +3 implementeer wijziging +4 voer regressietests uit +5 voer feature tests uit + +--- + +# Testing Policy + +Automatische tests zijn verplicht voor elke wijziging. + +Na iedere wijziging moet het volgende script worden uitgevoerd: + +./regression_tests.sh + +Dit script valideert: + +- API health endpoint +- TVDB auth-status endpoint +- TVDB search endpoint + +Als een regressietest faalt moet de wijziging worden teruggedraaid. + +--- + +# Feature Test Policy + +Nieuwe functionaliteit moet minimaal drie tests bevatten. + +Agents moeten hiervoor het volgende script als basis gebruiken: + +./feature_test_template.sh + +De template moet worden aangepast zodat deze: + +- de nieuwe endpoint of functionaliteit test +- de JSON response valideert +- controleert dat bestaande functionaliteit niet breekt + +De template zelf is alleen een startpunt en moet per feature worden aangepast. + + +--- + +# Regression Tests (verplicht) + +Regressietests mogen **geen nieuw TVDB-token forceren**. + +De volgende tests moeten altijd slagen: + +curl 127.0.0.1:8085/api/health +curl 127.0.0.1:8085/api/tvdb/auth-status +curl "127.0.0.1:8085/api/tvdb/search?q=elsbeth" + +Als een van deze tests faalt moet de wijziging worden teruggedraaid. + + +--- + +# Handmatige Auth Test + +De volgende endpoint forceert een nieuwe login en mag **niet in automatische regressietests worden gebruikt**: + +curl -X POST 127.0.0.1:8085/api/tvdb/login + +Deze endpoint is alleen bedoeld voor debugging. + + +--- + +# Feature Tests + +Nieuwe functionaliteit moet minimaal drie tests bevatten. + + +--- + +# Security Rules + +Agents moeten altijd: + +- path traversal voorkomen +- MEDIA_ROOT restricties respecteren +- rename preview afdwingen +- secrets niet loggen + + +--- + +# Logging Rules + +Agents mogen nooit loggen: + +- API keys +- tokens +- environment secrets + + +--- + +# Design Principle + +Stability over speed. + +Het doel is een stabiele backend, geen snelle refactors. diff --git a/CHANGE_POLICY.md b/CHANGE_POLICY.md new file mode 100644 index 0000000..36f7f23 --- /dev/null +++ b/CHANGE_POLICY.md @@ -0,0 +1,130 @@ +# CHANGE_POLICY.md + +Dit document bepaalt welke wijzigingen AI-agents zelfstandig mogen uitvoeren en welke wijzigingen expliciete toestemming vereisen. + +Doel: +- ongewenste refactors voorkomen +- contractbreuk voorkomen +- stabiliteit bewaken + +--- + +# Algemene regel + +AI-agents mogen alleen kleine, lokale, testbare wijzigingen uitvoeren. + +AI-agents mogen geen brede of structurele wijzigingen uitvoeren zonder expliciete toestemming van de gebruiker. + +--- + +# Wijzigingen die ZONDER expliciete toestemming mogen + +De volgende wijzigingen mogen zelfstandig worden gedaan, mits regressietests slagen: + +- kleine bugfixes +- toevoegen van nieuwe, lokale helperfuncties +- toevoegen van logging zonder secrets +- uitbreiden van bestaande responses met extra velden, zolang bestaande velden intact blijven +- toevoegen van nieuwe endpoints, zolang bestaande endpoints niet wijzigen +- uitbreiden van tests +- verbeteren van foutafhandeling +- verbeteren van type hints +- kleine frontend-aanpassingen zonder wijziging van workflow +- documentatie bijwerken + +--- + +# Wijzigingen die ALLEEN met expliciete toestemming mogen + +De volgende wijzigingen zijn verboden zonder expliciete toestemming: + +- wijzigen van bestaande endpointnamen +- wijzigen van bestaande response-structuren +- verwijderen van velden uit API responses +- wijzigen van token lifecycle logica +- wijzigen van rename template +- wijzigen van mount paths of allowed media roots +- wijzigen van container runtime model +- wijzigen van projectstructuur +- wijzigen van SAFE FILES +- vervangen van libraries of frameworkkeuzes +- brede refactors over meerdere modules +- wijzigen van securityregels rond file access + +--- + +# Verboden wijzigingen + +De volgende wijzigingen zijn altijd verboden zonder expliciete opdracht: + +- code buiten deze repository aanpassen +- andere containers aanpassen +- system config wijzigen +- firewall wijzigen +- systemd global wijzigen +- prune, cleanup of delete acties uitvoeren buiten deze projectscope +- secrets hardcoden +- API keys of tokens loggen + +--- + +# Refactor Policy + +Geen silent refactors. + +Elke refactor moet: +1. vooraf benoemd worden +2. beperkt blijven in scope +3. regressietests behouden +4. geen API contract breken + +Als een wijziging niet strikt nodig is voor het gevraagde doel, moet de agent die wijziging niet doen. + +--- + +# Test Policy + +Na iedere wijziging moeten minimaal drie regressietests worden uitgevoerd: + +- curl 127.0.0.1:8085/api/health +- curl 127.0.0.1:8085/api/tvdb/auth-status +- curl "127.0.0.1:8085/api/tvdb/search?q=elsbeth" + +Daarnaast moeten minimaal drie tests worden uitgevoerd voor de nieuwe of gewijzigde functionaliteit. + +Nieuwe feature tests moeten: +- het gevraagde gedrag verifiëren +- geen bestaande functionaliteit breken +- waar mogelijk mechanisch herhaalbaar zijn + +--- + +# Escalated Execution Policy + +Escalated execution is toegestaan binnen deze projectscope voor: + +- podman build +- podman run +- curl tests +- bestanden aanmaken en aanpassen +- dependency install binnen het project +- lokale build- en testacties + +Escalated execution is niet toegestaan voor: + +- systeembrede wijzigingen +- andere repositories +- andere stacks of containers +- destructieve cleanup acties + +--- + +# Output Policy + +Bij elke wijziging moet de agent kort rapporteren: + +- welke bestanden zijn aangepast +- waarom die wijziging nodig was +- welke regressietests zijn uitgevoerd +- welke feature tests zijn uitgevoerd +- welke risico’s of aandachtspunten er nog zijn diff --git a/SAFE_FILES.md b/SAFE_FILES.md new file mode 100644 index 0000000..2018a5c --- /dev/null +++ b/SAFE_FILES.md @@ -0,0 +1,74 @@ +# SAFE_FILES.md + +Dit document definieert kritieke bestanden die AI-agents niet zelfstandig mogen wijzigen. + +Doel: +- kritieke infrastructuur beschermen +- auth- en containerlogica stabiel houden +- API contract beschermen + +--- + +# Safe Files + +De volgende bestanden mogen alleen worden aangepast na expliciete toestemming van de gebruiker: + +- app/services/tvdb_auth_service.py +- container/Containerfile +- requirements.txt +- AGENTS.md +- CHANGE_POLICY.md +- SAFE_FILES.md +- contracts/API_GOLDEN.md +- docs/ARCHITECTURE.md + +--- + +# Waarom deze bestanden beschermd zijn + +## app/services/tvdb_auth_service.py +Bevat werkende TVDB auth, JWT exp parsing, renew-logica en token persistence. + +## container/Containerfile +Bevat de werkende runtime-basis voor het project. + +## requirements.txt +Kan runtime en dependency-gedrag ingrijpend veranderen. + +## AGENTS.md +Stuurt AI-gedrag aan en mag niet stilzwijgend gewijzigd worden. + +## CHANGE_POLICY.md +Bevat wijzigingsregels voor AI-agents. + +## SAFE_FILES.md +Bevat de lijst van beschermde bestanden zelf. + +## contracts/API_GOLDEN.md +Beschermt het bestaande API contract. + +## docs/ARCHITECTURE.md +Beschrijft architecturale invariants die niet ongemerkt mogen veranderen. + +--- + +# Toegestane werkwijze bij Safe Files + +Als een wijziging aan een safe file echt nodig is, moet de agent: + +1. expliciet melden welk safe file gewijzigd moet worden +2. uitleggen waarom dit nodig is +3. wachten op toestemming van de gebruiker + +Zonder expliciete toestemming mag een safe file niet gewijzigd worden. + +--- + +# Niet-safe bestanden + +Bestanden buiten deze lijst mogen aangepast worden, zolang: + +- de wijziging binnen projectscope valt +- de API contracten niet worden gebroken +- regressietests slagen +- de architectuurregels gerespecteerd blijven diff --git a/container/Containerfile b/container/Containerfile index b3b0fbf..3cd2bd4 100644 --- a/container/Containerfile +++ b/container/Containerfile @@ -1,34 +1,12 @@ -FROM python:3.12-slim +Ik heb een paar opmerkingen -WORKDIR /app +AGENTS.md +Regression testst +curl -X POST 127.0.0.1:8085/api/tvdb/login +zorgt deze curl er niet continue voor dat er een nieuw token wordt aangevraagd en dat thetvdb mij dan gaat blokkeren? -# Install minimal system dependencies -RUN apt-get update && apt-get install -y \ - curl \ - ca-certificates \ - && rm -rf /var/lib/apt/lists/* +ARCHITECTURE.md +Ik wil de volumes voor de videobestanden graag als volgt. Dat komt overeen met de host en maakt het herkenbaarder -# Copy requirements -COPY requirements.txt . - -RUN pip install --no-cache-dir -r requirements.txt - -# Copy app -COPY app /app/app - -# Create runtime dirs -RUN mkdir -p /app/data - -EXPOSE 8080 - -ENV PYTHONUNBUFFERED=1 - -CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8080"] - - -curl "127.0.0.1:8085/api/tvdb/search?q=matlock" - - - - -http://kodidebian:9098/kodi/rename-mvp.git \ No newline at end of file +/Volumes/8TB/Shared_Folders/TV_Shows → /Volumes/8TB/Shared_Folders/TV_Shows +/Volumes/8TB_RAID1/Shared_Folders/Library/TV_Shows → /Volumes/8TB_RAID1/Shared_Folders/Library/TV_Shows \ No newline at end of file diff --git a/contracts/API_GOLDEN.md b/contracts/API_GOLDEN.md new file mode 100644 index 0000000..c0f3f3d --- /dev/null +++ b/contracts/API_GOLDEN.md @@ -0,0 +1,85 @@ +# API_GOLDEN.md + +Dit document definieert het API contract. + +Agents mogen dit contract **niet breken**. + +--- + +# Health + +GET /api/health + +response: + +{ + "status": "ok" +} + + +--- + +# TVDB Login + +POST /api/tvdb/login + +response: + +{ + "status": "ok", + "issued_at": "...", + "expires_at": "...", + "renew_after": "..." +} + + +--- + +# TVDB Auth Status + +GET /api/tvdb/auth-status + +response: + +{ + "configured": true, + "has_token": true, + "expires_at": "...", + "renew_after": "...", + "token_source": "cached" +} + + +--- + +# TVDB Search + +GET /api/tvdb/search?q=query + +response: + +{ + "items": [ + { + "id": "...", + "name": "...", + "year": "...", + "display_name": "..." + } + ] +} + + +--- + +# Contract Rules + +Agents mogen: + +- nieuwe velden toevoegen + +Agents mogen **niet**: + +- velden verwijderen +- veldnamen wijzigen +- response structuur wijzigen diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..641a544 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,162 @@ +# ARCHITECTURE.md + +## Project + +Rename-MVP + +Webapplicatie voor het hernoemen van mediabestanden op basis van metadata van TheTVDB. + +--- + +# High Level Architecture + +browser +↓ +FastAPI backend +↓ +TVDB service +↓ +TheTVDB API + + +--- + +# Container Architecture + +Runtime: + +Rootless Podman container + +Image: + +rename-mvp + +Expose: + +8085 → 8080 + + +--- + +# Volumes + +Host en container gebruiken identieke paden. + +Volumes: + +/Volumes/8TB/Shared_Folders/TV_Shows → /Volumes/8TB/Shared_Folders/TV_Shows + +/Volumes/8TB_RAID1/Shared_Folders/Library/TV_Shows → /Volumes/8TB_RAID1/Shared_Folders/Library/TV_Shows + +/home/kodi/.config/rename-mvp/data → /app/data + + +--- + +# Allowed Media Roots + +De applicatie mag alleen bestanden lezen of wijzigen binnen de volgende directories: + +/Volumes/8TB/Shared_Folders/TV_Shows + +/Volumes/8TB_RAID1/Shared_Folders/Library/TV_Shows + + +Alle file operations moeten binnen deze paden blijven. + + +--- + +# Backend Modules + +Belangrijke modules: + +tvdb_auth_service +tvdb_client +tvdb_service +file_service +preview_service +rename_service +session_service + + +--- + +# TVDB Authentication Flow + +1 login via /login +2 ontvang JWT token +3 decode JWT payload +4 lees exp claim +5 bereken renew_after + +token wordt opgeslagen in: + +/app/data/tvdb_auth.json + + +--- + +# Token Lifecycle + +Token wordt alleen vernieuwd als: + +now >= renew_after + +of + +TVDB response 401 geeft. + +Token wordt **niet bij elke request vernieuwd**. + + +--- + +# File Rename Flow + +1 user selecteert serie +2 afleveringen worden geladen +3 user selecteert afleveringen +4 user selecteert bestanden +5 backend genereert preview +6 user bevestigt rename +7 backend voert rename uit + + +--- + +# Data Storage + +SQLite: + +session state +rename logs + +JSON: + +tvdb_auth.json + + +--- + +# Naming Format + +Bestandsnaam: + +{series} ({year}) - S{season:02}E{episode:02} - {title}{ext} + +Voorbeeld: + +Elsbeth (2024) - S02E03 - Devil's Night.mkv + + +--- + +# Invariants + +Deze eigenschappen mogen niet veranderen: + +- API endpoints +- rename template +- token renew logic +- container runtime diff --git a/feature_test_template.sh b/feature_test_template.sh new file mode 100755 index 0000000..f1be18a --- /dev/null +++ b/feature_test_template.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +set -euo pipefail + +BASE_URL="${BASE_URL:-http://127.0.0.1:8085}" +TMP_DIR="$(mktemp -d)" +trap 'rm -rf "$TMP_DIR"' EXIT + +echo "== Feature test 1: baseline auth-status before feature call ==" +curl --fail --silent --show-error \ + "${BASE_URL}/api/tvdb/auth-status" \ + -o "${TMP_DIR}/auth_status_before.json" + +cat "${TMP_DIR}/auth_status_before.json" + +python3 - "${TMP_DIR}/auth_status_before.json" <<'PY' +import json +import sys +from pathlib import Path + +path = Path(sys.argv[1]) +data = json.loads(path.read_text(encoding="utf-8")) + +assert isinstance(data, dict), "auth-status before must be an object" +assert "token_source" in data, "auth-status before missing token_source" + +print("feature baseline auth-status validation passed") +PY + +echo +echo "== Feature test 2: replace this block with the specific endpoint under test ==" +echo "Example target:" +echo " ${BASE_URL}/api/tvdb/series/430543/episodes?order_type=aired" +echo +echo "Example implementation pattern:" +echo " curl --fail --silent --show-error \"\${BASE_URL}/api/your/new/endpoint\" -o \"\${TMP_DIR}/feature.json\"" +echo " python3 - \"\${TMP_DIR}/feature.json\" <<'PY'" +echo " import json, sys" +echo " from pathlib import Path" +echo " data = json.loads(Path(sys.argv[1]).read_text(encoding='utf-8'))" +echo " assert isinstance(data, dict)" +echo " # add exact assertions here" +echo " print('feature JSON validation passed')" +echo " PY" + +echo +echo "== Feature test 3: verify auth-status after feature call ==" +curl --fail --silent --show-error \ + "${BASE_URL}/api/tvdb/auth-status" \ + -o "${TMP_DIR}/auth_status_after.json" + +cat "${TMP_DIR}/auth_status_after.json" + +python3 - "${TMP_DIR}/auth_status_after.json" <<'PY' +import json +import sys +from pathlib import Path + +path = Path(sys.argv[1]) +data = json.loads(path.read_text(encoding="utf-8")) + +assert isinstance(data, dict), "auth-status after must be an object" +assert "token_source" in data, "auth-status after missing token_source" +assert data["token_source"] in {"none", "login", "cached", "renewed"}, \ + "token_source after must be valid" + +print("feature post-check auth-status validation passed") +PY + +echo +echo "Feature test template completed." +echo "Replace feature test 2 with the exact endpoint and assertions for the new functionality." diff --git a/regression_tests.sh b/regression_tests.sh new file mode 100755 index 0000000..4b575ef --- /dev/null +++ b/regression_tests.sh @@ -0,0 +1,106 @@ +#!/usr/bin/env bash +set -euo pipefail + +BASE_URL="${BASE_URL:-http://127.0.0.1:8085}" +TMP_DIR="$(mktemp -d)" +trap 'rm -rf "$TMP_DIR"' EXIT + +echo "== Regression test 1: health ==" +curl --fail --silent --show-error \ + "${BASE_URL}/api/health" \ + -o "${TMP_DIR}/health.json" + +cat "${TMP_DIR}/health.json" + +python3 - "${TMP_DIR}/health.json" <<'PY' +import json +import sys +from pathlib import Path + +path = Path(sys.argv[1]) +data = json.loads(path.read_text(encoding="utf-8")) + +assert isinstance(data, dict), "health response must be an object" +assert data.get("status") == "ok", "health.status must be 'ok'" + +print("health JSON validation passed") +PY + +echo +echo "== Regression test 2: tvdb auth-status ==" +curl --fail --silent --show-error \ + "${BASE_URL}/api/tvdb/auth-status" \ + -o "${TMP_DIR}/auth_status.json" + +cat "${TMP_DIR}/auth_status.json" + +python3 - "${TMP_DIR}/auth_status.json" <<'PY' +import json +import sys +from pathlib import Path + +path = Path(sys.argv[1]) +data = json.loads(path.read_text(encoding="utf-8")) + +assert isinstance(data, dict), "auth-status response must be an object" + +required_keys = [ + "configured", + "has_token", + "expires_at", + "renew_after", + "token_source", +] + +for key in required_keys: + assert key in data, f"auth-status missing key: {key}" + +assert isinstance(data["configured"], bool), "configured must be boolean" +assert isinstance(data["has_token"], bool), "has_token must be boolean" +assert data["token_source"] in {"none", "login", "cached", "renewed"}, \ + "token_source must be one of none/login/cached/renewed" + +print("auth-status JSON validation passed") +PY + +echo +echo "== Regression test 3: tvdb search ==" +curl --fail --silent --show-error \ + "${BASE_URL}/api/tvdb/search?q=elsbeth" \ + -o "${TMP_DIR}/search.json" + +cat "${TMP_DIR}/search.json" + +python3 - "${TMP_DIR}/search.json" <<'PY' +import json +import sys +from pathlib import Path + +path = Path(sys.argv[1]) +data = json.loads(path.read_text(encoding="utf-8")) + +assert isinstance(data, dict), "search response must be an object" +assert "items" in data, "search response missing items" +assert isinstance(data["items"], list), "items must be a list" +assert len(data["items"]) > 0, "items list must not be empty" + +first = data["items"][0] +assert isinstance(first, dict), "first item must be an object" + +required_item_keys = ["id", "name", "year", "display_name"] +for key in required_item_keys: + assert key in first, f"search item missing key: {key}" + +names = [ + (item.get("display_name") or item.get("name") or "") + for item in data["items"] +] + +assert any("Elsbeth" in name for name in names), \ + "expected at least one search result containing 'Elsbeth'" + +print("search JSON validation passed") +PY + +echo +echo "All regression tests passed."