reparatie diverse bestanden

This commit is contained in:
kodi
2026-03-07 13:22:52 +01:00
parent e98abcbb78
commit 4ab1f15f7a
7 changed files with 918 additions and 24 deletions
+18 -13
View File
@@ -43,8 +43,8 @@ Agents mogen NOOIT:
- 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.
- wijzigingen maken die het contract in contracts/API_GOLDEN.md breken
- refactors of verbeteringen uitvoeren die niet expliciet gevraagd zijn
Agents mogen alleen werken **binnen deze repository**.
@@ -56,14 +56,14 @@ Agents mogen alleen werken **binnen deze repository**.
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
- 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
---
@@ -118,6 +118,13 @@ Dit script valideert:
Als een regressietest faalt moet de wijziging worden teruggedraaid.
Test-URL policy:
- Op de host moet standaard getest worden via `http://127.0.0.1:8085`
- In een sandbox of containerized AI-omgeving moet getest worden via `http://host.containers.internal:8085`
- Testscripts moeten automatisch één van beide kunnen detecteren
- Agents mogen niet aannemen dat `127.0.0.1:8085` vanuit de sandbox naar de host verwijst
---
# Feature Test Policy
@@ -145,9 +152,7 @@ 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"
./regression_tests.sh
Als een van deze tests faalt moet de wijziging worden teruggedraaid.
+120
View File
@@ -55,3 +55,123 @@ def search_series(q: str = Query(..., min_length=1)):
return {"items": normalized}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
def _series_display_name(name: str | None, year: int | str | None) -> str | None:
if name and year:
return f"{name} ({year})"
return name
def _extract_episodes(payload: dict) -> list[dict]:
data = payload.get("data")
if isinstance(data, list):
return data
if isinstance(data, dict):
episodes = data.get("episodes")
if isinstance(episodes, list):
return episodes
return []
def _fetch_aired_episodes(client: TvdbClient, series_id: int) -> list[dict]:
attempts = [
(f"/series/{series_id}/episodes/default", {"seasonType": "aired"}),
(f"/series/{series_id}/episodes/official", None),
]
errors = []
for path, params in attempts:
try:
payload = client.get(path, params=params)
items = _extract_episodes(payload)
if items:
return items
except Exception as exc:
errors.append(f"{path}: {exc}")
raise RuntimeError(
"Unable to load aired episodes from TVDB; tried known episode endpoints"
if not errors
else f"Unable to load aired episodes from TVDB ({'; '.join(errors)})"
)
@router.get("/series/{series_id}/episodes")
def get_series_episodes(
series_id: int,
order_type: str = Query("aired", min_length=1),
):
if order_type.lower() != "aired":
raise HTTPException(
status_code=400,
detail="Unsupported order_type. Only 'aired' is supported in this MVP.",
)
client = TvdbClient()
try:
series_payload = client.get(f"/series/{series_id}")
series_data = series_payload.get("data", {}) if isinstance(series_payload, dict) else {}
series_name = (
series_data.get("name")
or series_data.get("seriesName")
or series_data.get("slug")
)
series_year = series_data.get("year")
episodes = _fetch_aired_episodes(client, series_id)
normalized = []
for item in episodes:
season_number = (
item.get("seasonNumber")
or item.get("airedSeason")
or item.get("season")
or 0
)
episode_number = (
item.get("number")
or item.get("airedEpisodeNumber")
or item.get("episodeNumber")
or 0
)
title = item.get("name") or item.get("episodeName") or ""
aired_on = item.get("aired") or item.get("firstAired")
label = None
try:
season_num = int(season_number)
episode_num = int(episode_number)
label = f"S{season_num:02}E{episode_num:02} - {title}"
if aired_on:
label = f"{label} - {aired_on}"
except (TypeError, ValueError):
pass
normalized.append(
{
"id": item.get("id"),
"season_number": season_number,
"episode_number": episode_number,
"title": title,
"aired": aired_on,
"label": label,
"raw": item,
}
)
return {
"series": {
"id": series_data.get("id") or series_id,
"name": series_name,
"year": series_year,
"display_name": _series_display_name(series_name, series_year),
},
"order_type": "aired",
"items": normalized,
}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
+23 -9
View File
@@ -1,12 +1,26 @@
Ik heb een paar opmerkingen
FROM python:3.12-slim
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?
WORKDIR /app
ARCHITECTURE.md
Ik wil de volumes voor de videobestanden graag als volgt. Dat komt overeen met de host en maakt het herkenbaarder
# Install minimal system dependencies
RUN apt-get update && apt-get install -y \
curl \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
/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
# 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"]
+10 -1
View File
@@ -1,7 +1,16 @@
#!/usr/bin/env bash
set -euo pipefail
BASE_URL="${BASE_URL:-http://127.0.0.1:8085}"
if [ -z "${BASE_URL:-}" ]; then
if curl --silent --fail http://127.0.0.1:8085/api/health >/dev/null 2>&1; then
BASE_URL="http://127.0.0.1:8085"
elif curl --silent --fail http://host.containers.internal:8085/api/health >/dev/null 2>&1; then
BASE_URL="http://host.containers.internal:8085"
else
echo "ERROR: could not determine BASE_URL. Tried 127.0.0.1 and host.containers.internal." >&2
exit 1
fi
fi
TMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_DIR"' EXIT
+98
View File
@@ -0,0 +1,98 @@
#!/usr/bin/env bash
set -euo pipefail
if [ -z "${BASE_URL:-}" ]; then
if curl --silent --fail http://127.0.0.1:8085/api/health >/dev/null 2>&1; then
BASE_URL="http://127.0.0.1:8085"
elif curl --silent --fail http://host.containers.internal:8085/api/health >/dev/null 2>&1; then
BASE_URL="http://host.containers.internal:8085"
else
echo "ERROR: could not determine BASE_URL. Tried 127.0.0.1 and host.containers.internal." >&2
exit 1
fi
fi
SERIES_ID="${SERIES_ID:-430543}"
TMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_DIR"' EXIT
echo "== Feature test 1: episodes endpoint returns series + items =="
curl --fail --silent --show-error \
"${BASE_URL}/api/tvdb/series/${SERIES_ID}/episodes?order_type=aired" \
-o "${TMP_DIR}/episodes.json"
cat "${TMP_DIR}/episodes.json"
python3 - "${TMP_DIR}/episodes.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), "episodes response must be an object"
assert "series" in data and isinstance(data["series"], dict), "episodes response missing series object"
assert data.get("order_type") == "aired", "order_type must be aired"
assert "items" in data and isinstance(data["items"], list), "episodes response missing items list"
series = data["series"]
for key in ["id", "name", "year", "display_name"]:
assert key in series, f"series missing key: {key}"
print("episodes endpoint JSON validation passed")
PY
echo
echo "== Feature test 2: unsupported order_type is rejected =="
curl --silent --show-error \
-o "${TMP_DIR}/episodes_bad_order.json" \
-w "%{http_code}" \
"${BASE_URL}/api/tvdb/series/${SERIES_ID}/episodes?order_type=absolute" \
> "${TMP_DIR}/episodes_bad_order.status"
cat "${TMP_DIR}/episodes_bad_order.json"
python3 - "${TMP_DIR}/episodes_bad_order.status" "${TMP_DIR}/episodes_bad_order.json" <<'PY'
import json
import sys
from pathlib import Path
status = Path(sys.argv[1]).read_text(encoding="utf-8").strip()
data = json.loads(Path(sys.argv[2]).read_text(encoding="utf-8"))
assert status == "400", f"expected HTTP 400, got {status}"
assert isinstance(data, dict), "error response must be an object"
assert "detail" in data, "error response missing detail"
print("unsupported order_type validation passed")
PY
echo
echo "== Feature test 3: search output keeps year/display_name contract =="
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 and isinstance(data["items"], list), "search response missing items list"
assert len(data["items"]) > 0, "search items list must not be empty"
first = data["items"][0]
for key in ["id", "name", "year", "display_name"]:
assert key in first, f"search item missing key: {key}"
print("search contract validation passed")
PY
echo
echo "All episodes feature tests passed."
+10 -1
View File
@@ -1,7 +1,16 @@
#!/usr/bin/env bash
set -euo pipefail
BASE_URL="${BASE_URL:-http://127.0.0.1:8085}"
if [ -z "${BASE_URL:-}" ]; then
if curl --silent --fail http://127.0.0.1:8085/api/health >/dev/null 2>&1; then
BASE_URL="http://127.0.0.1:8085"
elif curl --silent --fail http://host.containers.internal:8085/api/health >/dev/null 2>&1; then
BASE_URL="http://host.containers.internal:8085"
else
echo "ERROR: could not determine BASE_URL. Tried 127.0.0.1 and host.containers.internal." >&2
exit 1
fi
fi
TMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_DIR"' EXIT
+639
View File
@@ -0,0 +1,639 @@
# Rename-MVP — Definitieve Codex instructie en technisch ontwerp
## Doel
Bouw verder aan een minimalistische webapplicatie als uitgeklede webversie van **Rename My TV Series**.
De applicatie moet:
- draaien in een **rootless Podman container**
- lokale mediabestanden kunnen hernoemen binnen expliciet toegestane videomappen
- metadata ophalen via **TheTVDB v4 API**
- een **4-panel interface** hebben
- altijd eerst een **preview** tonen voordat bestanden echt worden hernoemd
- veilig omgaan met paden, tokens en bestandsoperaties
- de **serienaam inclusief jaartal** gebruiken in de UI en in de bestandsnaam
Dit project heeft al een **werkende baseline**:
- container build werkt
- FastAPI health endpoint werkt
- TVDB login werkt
- JWT parsing werkt
- token persistence werkt
- auth-status endpoint werkt
- TVDB search werkt
- `token_source` werkt
Belangrijk: dit project is nu **geen greenfield ontwerp meer**, maar een **bestaand werkend project** dat gecontroleerd verder moet worden uitgebouwd.
---
# 1. Technische context
## Hostomgeving
- Host OS: Debian Trixie
- Podman version: 5.4.2
- Project root: `/home/kodi/.config/rename-mvp`
## Runtime
- Rootless Podman
- FastAPI backend
- SQLite voor app-state en logging
- JSON voor TVDB auth state
- TheTVDB API v4
- Lokale tests via `127.0.0.1:8085`
## Bestaande paden
### Auth state JSON
- Host: `/home/kodi/.config/rename-mvp/data/tvdb_auth.json`
- Container: `/app/data/tvdb_auth.json`
### Werkende testscripts
- `/home/kodi/.config/rename-mvp/regression_tests.sh`
- `/home/kodi/.config/rename-mvp/feature_test_template.sh`
### Governance-bestanden
- `/home/kodi/.config/rename-mvp/AGENTS.md`
- `/home/kodi/.config/rename-mvp/CHANGE_POLICY.md`
- `/home/kodi/.config/rename-mvp/SAFE_FILES.md`
- `/home/kodi/.config/rename-mvp/docs/ARCHITECTURE.md`
- `/home/kodi/.config/rename-mvp/contracts/API_GOLDEN.md`
---
# 2. Niet-onderhandelbare regels
## 2.1 Geen contractbreuk
Je mag geen bestaande API endpoints of bestaande response-structuren breken.
Je mag:
- nieuwe velden toevoegen
- nieuwe endpoints toevoegen
Je mag niet:
- bestaande veldnamen wijzigen
- bestaande velden verwijderen
- response-structuren wijzigen
- bestaande endpoints hernoemen
- bestaande endpoints verwijderen
`contracts/API_GOLDEN.md` is leidend.
## 2.2 Geen speculative improvements
Voer **geen refactors, herstructureringen of “verbeteringen” uit die niet expliciet nodig zijn voor de gevraagde wijziging**.
Als een wijziging niet strikt nodig is om het gevraagde doel te bereiken, doe die wijziging niet.
## 2.3 Safe files
De volgende bestanden zijn beschermd en mogen alleen gewijzigd worden als dat expliciet noodzakelijk is en als je het duidelijk benoemt:
- `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`
## 2.4 Scopebeperking
Werk alleen binnen deze repository:
Pad op de host: `/home/kodi/.config/rename-mvp`
Pad in deze codex omgeving: `/workspace/rename-mvp`
Je mag niets aanpassen buiten deze projectscope.
## 2.5 Escalated execution
Escalated execution is toegestaan binnen deze projectscope voor:
- `podman build`
- `podman run`
- curl tests
- lokale build- en testacties
- bestanden aanmaken of aanpassen binnen dit project
- dependency install binnen dit project
Niet toegestaan:
- systeembrede wijzigingen
- firewallwijzigingen
- system upgrades
- andere repositories
- andere stacks of containers aanpassen
- destructieve cleanup acties buiten deze projectscope
---
# 3. Bestaande baseline die behouden moet blijven
Deze functionaliteit werkt al en mag niet stuk:
## 3.1 Health endpoint
`GET /api/health`
Expected:
```json
{"status":"ok"}
```
## 3.2 TVDB login endpoint
`POST /api/tvdb/login`
Doet een expliciete forced login en vraagt een nieuw token aan.
Deze endpoint is **alleen voor debugging en handmatige auth-tests**, niet voor regressietests.
## 3.3 TVDB auth-status endpoint
`GET /api/tvdb/auth-status`
Deze endpoint moet de actuele auth-status tonen inclusief:
- configured
- has_token
- issued_at
- expires_at
- expires_at_unix
- renew_after
- renew_after_unix
- is_expired
- is_renewal_due
- last_login_attempt_at
- last_login_success_at
- last_login_status
- last_login_error
- jwt_payload
- token_source
## 3.4 TVDB search endpoint
`GET /api/tvdb/search?q=elsbeth`
Deze endpoint werkt al en moet series teruggeven met ten minste:
- id
- name
- year
- display_name
De UI en bestandsnaamopbouw moeten het **jaartal van de serie** gebruiken.
## 3.5 Token lifecycle
De TVDB bearer token is een JWT.
Gebruik:
- `exp` als primaire bron voor expiry
- `iat` indien aanwezig
- renew alleen wanneer nodig
- niet onnodig opnieuw `/login` doen
De token mag **niet continu vernieuwd worden**.
## 3.6 token_source
De auth-state gebruikt:
- `none`
- `login`
- `cached`
- `renewed`
Dit gedrag moet behouden blijven.
---
# 4. Functioneel doel van de applicatie
De uiteindelijke webapp moet een minimalistische 4-panel interface hebben.
## Paneel 1 — TVDB Search
Functie:
- zoekterm invoeren
- zoeken via backend naar TheTVDB
- resultaten tonen
- gekozen serie details tonen
Zoekresultaten moeten minimaal tonen:
- serienaam
- jaar
- first aired indien beschikbaar
- network indien beschikbaar
- poster indien beschikbaar
De series moeten in de UI worden weergegeven als bijvoorbeeld:
- `Elsbeth (2024)`
- `The Office (2005)`
## Paneel 2 — Episodes
Functie:
- afleveringen van gekozen serie tonen
- minimaal `Aired Order` ondersteunen
- afleveringen per seizoen tonen
- gebruiker kan willekeurige afleveringen selecteren
Afleveringen tonen bijvoorbeeld:
- `S02E03 - Devil's Night - 2025-03-13`
## Paneel 3 — Selected Episodes
Functie:
- lijst van gekozen afleveringen voor de huidige sessie
Functionaliteit:
- toevoegen
- verwijderen
- clear
- reorder
- move up / move down
## Paneel 4 — Selected Files
Functie:
- lijst van gekozen lokale videobestanden
Functionaliteit:
- Add Files
- Add Directory
- Sort
- Remove
- Clear
- Move Up / Move Down
## Workflow
1. user zoekt serie
2. user kiest serie
3. backend haalt afleveringen op
4. user voegt afleveringen toe aan selected episodes
5. user voegt bestanden toe aan selected files
6. backend maakt een 1-op-1 mapping op basis van volgorde
7. backend maakt preview
8. user bevestigt
9. backend voert rename uit
---
# 5. Bestandsnaamopbouw
De standaard bestandsnaam moet zijn:
```text
{series} ({year}) - S{season:02}E{episode:02} - {title}{ext}
```
Voorbeeld:
```text
Elsbeth (2024) - S02E03 - Devil's Night.mkv
```
Belangrijk:
- het **serienaam-jaartal** is verplicht onderdeel van de naam
- het jaartal komt uit TheTVDB serie metadata
- de UI moet dit jaartal ook tonen
De eerste MVP hoeft **airdate niet per se in de filename** te zetten zolang het afgesproken template hierboven intact blijft.
---
# 6. TVDB authenticatie en tokenbeheer
## 6.1 Authflow
Gebruik de officiële TVDB v4 flow:
- `POST /login`
- body bevat `apikey`
- `pin` alleen meesturen als aanwezig
- response bevat bearer token
## 6.2 JWT verwerking
De token is een JWT.
Verplicht:
- decodeer payload
- lees `exp`
- lees `iat` indien aanwezig
- gebruik `exp` als primaire expiry-bron
## 6.3 Opslag
TVDB auth state wordt persistent opgeslagen in:
`/app/data/tvdb_auth.json`
Hostpad:
`/home/kodi/.config/rename-mvp/data/tvdb_auth.json`
In JSON mogen staan:
- token
- issued_at
- expires_at
- expires_at_unix
- renew_after
- renew_after_unix
- last_login_attempt_at
- last_login_success_at
- last_login_status
- last_login_error
- jwt_payload
- token_source
Niet in JSON:
- `TVDB_API_KEY`
- `TVDB_PIN`
Die moeten uit environment variables komen.
## 6.4 Renew-logica
Renew alleen wanneer:
- `now >= renew_after`
- of TVDB `401` teruggeeft
Niet continu nieuwe tokens opvragen.
`POST /api/tvdb/login` is geen regressietest en geen normale flow; het is een debug endpoint.
---
# 7. Toegestane videopaden
De container moet dezelfde videopaden gebruiken als de host.
## Allowed media roots
- `/Volumes/8TB/Shared_Folders/TV_Shows`
- `/Volumes/8TB_RAID1/Shared_Folders/Library/TV_Shows`
## Volume mappings
- `/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`
De app mag alleen lezen/schrijven binnen een van de toegestane media roots.
---
# 8. Securitymodel
## Bestandspaden
Nooit:
- absolute clientpaden blind vertrouwen
- `..` toestaan
- buiten allowed media roots resolven
Altijd:
- path validation doen
- binnen allowlist van media roots blijven
- alleen toegestane extensies accepteren
- preview afdwingen vóór rename
## Allowed extensions MVP
- `.mkv`
- `.mp4`
- `.avi`
- `.m4v`
- `.srt`
## Logging
Nooit loggen:
- API keys
- bearer tokens
- env secrets
---
# 9. Container en runtime
## Containerfile
De bestaande werkende runtime moet behouden blijven.
## Rootless Podman
De applicatie draait rootless.
## Lokale test-URL
Gebruik voor runtime-tests de BASE_URL die bereikbaar is vanuit de huidige omgeving:
- op de host meestal: http://127.0.0.1:8085
- in sandbox/container-omgevingen meestal: http://host.containers.internal:8085
Gebruik niet automatisch localhost.
De testscripts moeten BASE_URL automatisch detecteren of expliciet meegegeven krijgen.
---
# 10. Verwachte repositorystructuur
Houd deze structuur aan. Geen onnodige nieuwe mappen toevoegen.
```text
/home/kodi/.config/rename-mvp
├── AGENTS.md
├── CHANGE_POLICY.md
├── SAFE_FILES.md
├── regression_tests.sh
├── feature_test_template.sh
├── requirements.txt
├── .env
├── .env.example
├── README.md
├── app/
├── container/
├── data/
├── contracts/
│ └── API_GOLDEN.md
└── docs/
└── ARCHITECTURE.md
```
---
# 11. API-contract dat behouden moet blijven
## 11.1 Health
`GET /api/health`
Response:
```json
{"status":"ok"}
```
## 11.2 TVDB Login
`POST /api/tvdb/login`
Response bevat ten minste:
```json
{
"status": "ok",
"issued_at": "...",
"expires_at": "...",
"renew_after": "..."
}
```
## 11.3 TVDB Auth Status
`GET /api/tvdb/auth-status`
Response bevat ten minste:
```json
{
"configured": true,
"has_token": true,
"expires_at": "...",
"renew_after": "...",
"token_source": "cached"
}
```
Meer velden zijn toegestaan, minder niet.
## 11.4 TVDB Search
`GET /api/tvdb/search?q=query`
Response bevat ten minste:
```json
{
"items": [
{
"id": "...",
"name": "...",
"year": "...",
"display_name": "..."
}
]
}
```
---
# 12. Testbeleid
## 12.1 Verplichte regressietests na elke wijziging
Na **iedere wijziging** moet dit script worden uitgevoerd:
```bash
./regression_tests.sh
```
Dit script is verplicht.
Het valideert de bestaande baseline en mag geen nieuw TVDB-token forceren.
## 12.2 Verplichte feature tests
Na iedere wijziging moet je daarnaast **drie tests uitvoeren voor de nieuwe of gewijzigde functionaliteit**.
Gebruik hiervoor als basis:
```bash
./feature_test_template.sh
```
Deze template moet per feature worden aangepast zodat de nieuwe functionaliteit echt en mechanisch getest wordt.
## 12.3 Geen forced login in regressie
Verboden als standaard regressietest:
```bash
curl -X POST 127.0.0.1:8085/api/tvdb/login
```
Deze endpoint is alleen voor debugging.
---
# 13. Ontwikkelworkflow voor Codex
Bij iedere opdracht moet je dit proces volgen:
1. lees eerst de bestaande governance-bestanden:
- `AGENTS.md`
- `CHANGE_POLICY.md`
- `SAFE_FILES.md`
- `docs/ARCHITECTURE.md`
- `contracts/API_GOLDEN.md`
2. vat kort samen wat je gaat wijzigen
3. benoem welke bestanden je wilt aanpassen
4. voer alleen de strikt noodzakelijke wijziging uit
5. draai verplichte regressietests:
- `./regression_tests.sh`
6. draai drie feature tests voor de nieuwe functionaliteit
7. rapporteer:
- welke bestanden zijn aangepast
- welke regressietests zijn uitgevoerd
- welke feature tests zijn uitgevoerd
- welke risicos of beperkingen er nog zijn
---
# 14. Eerste concrete opdracht aan Codex
Begin niet meteen met een brede refactor.
Begin met een gecontroleerde volgende stap.
## Eerste aanbevolen fase
Bouw de volgende functionaliteit uit:
### Doel
- endpoint om afleveringen van een gekozen TVDB serie op te halen
- minimaal ondersteuning voor `aired order`
- seriegegevens inclusief `year` behouden
- output geschikt maken voor paneel 2 van de UI
### Verwachte richting
Bijvoorbeeld iets als:
`GET /api/tvdb/series/{series_id}/episodes?order_type=aired`
Maar:
- breek geen bestaande endpoints
- wijzig geen bestaande auth-logica
- wijzig geen container setup
- gebruik bestaande TVDB token lifecycle
- voeg tests toe
### Voor deze wijziging verplicht
1. regressietests blijven slagen
2. drie feature tests toevoegen/uitvoeren voor het nieuwe episodes endpoint
3. output JSON valideren
4. zoekresultaten en year-handling niet breken
---
# 15. Wat je uitdrukkelijk niet moet doen
- geen speculative refactors
- geen auth-systeem herschrijven
- geen token lifecycle vereenvoudigen
- geen nieuwe frameworkkeuzes introduceren
- geen projectstructuur wijzigen
- geen governance-bestanden wijzigen tenzij expliciet nodig en gemeld
- geen forced login gebruiken als regressietest
- geen container- of pathmodel veranderen
---
# 16. Samenvatting voor Codex
Dit project heeft al een werkende baseline.
Jouw taak is niet om het project opnieuw te ontwerpen, maar om het gecontroleerd uit te bouwen.
Belangrijkste regels:
- behoud werkende baseline
- breek geen API contract
- gebruik JWT `exp` als expiry-bron
- renew token alleen wanneer nodig
- gebruik seriejaar in UI en filename
- werk alleen binnen allowed media roots
- draai altijd `./regression_tests.sh`
- draai altijd drie feature tests voor nieuwe functionaliteit
- geen speculative improvements
- escalated execution is toegestaan binnen alleen deze projectscope