171 lines
21 KiB
Markdown
171 lines
21 KiB
Markdown
# Projet « Seiche » — doc de reprise
|
||
|
||
> À jour : 2026-08-26 (A1–D4 + proxy + READMEs + **déploiement LXC/NPM ✅**, git) · Statut : **stack complète validée** : serveur 30 tests, front 3 tests + build, Docker build+up, admin créé, `master` poussé sur Gitea · Reste : exécution du déploiement (LXC + règle NPM + domaine) = infra Nicolas (§5 q4/q12)
|
||
> Mode d'emploi : doc auto-suffisant. En reprise avec un assistant IA : lui faire lire ce fichier en entier, traiter le §5, puis **planifier les tâches restantes une par une** (§9 + §11).
|
||
> Mode de travail imposé par Nicolas : **petites tâches, planifier avant d'exécuter, ne jamais lancer de run autonome**.
|
||
|
||
## 1. Projet en une phrase
|
||
Appli web auto-hébergée, **réseau local uniquement, mono-utilisateur** (Nicolas), qui gère un service externe via son API REST. Accès protégé par mot de passe. Le périmètre va grossir et devenir sensible : rigueur et sécurité **dès le départ**, pas en rattrapage.
|
||
|
||
## 2. Exigences (telles que déclarées)
|
||
- Gestion d'un service externe via API REST (service + périmètre MVP **à préciser**, §5)
|
||
- Auto-hébergée, accessible uniquement en réseau local, par le propriétaire
|
||
- Protection par mot de passe obligatoire (LAN ≠ confiance totale)
|
||
- Deviendra « compliqué et sensible » → fondations sérieuses dès le starter
|
||
- Stack imposée : Docker, SQLite, backend Node, front React (ou équivalent validé), tests
|
||
|
||
## 3. Stack recommandée (défauts à valider)
|
||
| Couche | Choix | Pourquoi |
|
||
|---|---|---|
|
||
| Langage | TypeScript partout (strict) | Une seule langue, contrats API↔front typés |
|
||
| Backend | Node 22 LTS + Fastify 5 | Rapide, validation Zod, erreurs propres, plugins |
|
||
| DB | SQLite (better-sqlite3) + Drizzle ORM | Migrations versionnées, typage, mode WAL |
|
||
| Front | Vite + React 18 + React Router + TanStack Query | Fetch/caching de l'API REST sans douleur |
|
||
| UI | Tailwind CSS + shadcn/ui | Dashboard admin pro, composants en repo |
|
||
| Auth | Argon2id + session cookie httpOnly + CSRF | Solide et simple, pas de JWT d'État |
|
||
| Tests | Vitest (API via inject, DB de test) + React Testing Library | Tooling TS unique, rapide |
|
||
| Qualité | Biome (lint + format) | Un seul outil, très rapide |
|
||
| Conteneur | Dockerfile multi-stage + compose, user non-root | Image fine, SQLite en volume |
|
||
|
||
## 4. Sécurité — non négociables
|
||
1. Hash **Argon2id** (sel par compte), jamais en clair, jamais de hash faible.
|
||
2. **Rate-limit** sur /login (anti brute-force).
|
||
3. Session : cookie **httpOnly + SameSite=Lax** (+ Secure si TLS), durée limitée, révocation au logout.
|
||
4. **CSRF** : token sur toutes les mutations (POST/PUT/DELETE).
|
||
5. Secrets **uniquement en variables d'env** (`.env` hors repo, `.env.example` versionné).
|
||
6. Logs **pino** structurés : jamais de mot de passe / token / cookie dans les logs.
|
||
7. SQLite **hors de l'image** : volume nommé `/data/app.db` + **script de backup** testé.
|
||
8. TLS : en prod derrière nginx-proxy-manager (Let's Encrypt, q4/q12 §5) ; dev LAN sans proxy = mots de passe en clair (acceptable en dev uniquement).
|
||
9. Dépendances : lockfile figé en prod, `npm audit` au rythme des maj.
|
||
10. Surface API minimale, pas de debug en prod.
|
||
|
||
## 5. Questions ouvertes (à répondre avant le MVP)
|
||
| # | Question | Défaut si pas de réponse |
|
||
|---|---|---|
|
||
| 1 | **Quel service externe** ? URL, doc API, mode d'auth (token statique / OAuth / Basic ?) | Client isolé `server/src/external/` (adaptateurs), config env, mock en dev |
|
||
| 2 | **Périmètre concret du MVP** ? (lister X, modifier Y, déclencher Z, logs…) | 1 CRUD exemple + health |
|
||
| 3 | Un seul compte, ou plusieurs possibles ? | Schéma multi-comptes, 1 compte créé au 1er boot |
|
||
| 4 | ✅ **TLS en LAN** (résolu 2026-08-26) : nginx-proxy-manager (Let's Encrypt) en prod ; dev LAN sans proxy = HTTP accepté (risque dév documenté). | — |
|
||
| 5 | UI Tailwind + shadcn/ui, interface en français ? | Oui / FR |
|
||
| 6 | Persistance : volume nommé + backup régulier OK ? | Oui |
|
||
| 7 | Node 22 LTS dans les conteneurs ? | Oui |
|
||
| 8 | ✅ **Docker opérationnel** (résolu 2026-08-23) : daemon 29.7.2 démarré (Arch Linux), compose 5.5.0 (plugin + standalone). | — |
|
||
| 9 | ✅ **Gitea opérationnel** (2026-08-25) : remote SSH `gitea-lab:nicolas/seiche`, compte collaborateur `bot-claude` (clé `id_ed25519_gitea_bot_claude`), `master` poussé, tracking OK. | — |
|
||
| 12 | ✅ **VPS / mise en prod** (résolu 2026-08-26) : pas de VPS — **LXC Proxmox** (Docker + compose dans le LXC), TLS via **nginx-proxy-manager** (Let's Encrypt, déjà fonctionnel). | — |
|
||
|
||
## 6. Architecture
|
||
- **1 conteneur = API + front prébuildé** servi par Fastify → zéro CORS, 1 port, déploiement simple.
|
||
- `server/` (Fastify) : plugins (auth, error-handler, rate-limit) → routes `/api/v1/*` → DB + client externe.
|
||
- `web/` (Vite+React) : pages, guard de session, TanStack Query devant `/api/v1`.
|
||
- Service externe : **un seul package** `server/src/external/` (http-client, cache, types, adaptateurs) — le seul endroit qui le connaît.
|
||
- SQLite en **WAL**, migrations Drizzle appliquées au boot.
|
||
|
||
## 7. Structure du repo (état réel, inventaire 2026-08-25)
|
||
✅ = présent · ❌ = manquant
|
||
```
|
||
seiche/
|
||
├── REPRISE.md ✅ · README.md ❌ (E1 — à créer)
|
||
├── package.json ✅ · pnpm-workspace.yaml ✅ · biome.json ✅ · tsconfig.base.json ✅
|
||
├── .env.example ✅ · .gitignore ✅ · .dockerignore ✅
|
||
├── Dockerfile ✅ · docker-compose.yml ✅ (port 8787, volume `seiche-data`) · Makefile ❌
|
||
├── scripts/backup.sh ✅ (VACUUM INTO dans le conteneur — testé)
|
||
├── server/ ✅ (installé, compilé, 26 tests ✅)
|
||
│ ├── src/
|
||
│ │ ├── main.ts · app.ts · config.ts · validation.ts · setup.ts ✅
|
||
│ │ ├── db/ (client, schema) · migrations/ ✅ (0000)
|
||
│ │ ├── auth/ (password, session+CSRF, rate-limit)
|
||
│ │ ├── routes/ (auth, external, health)
|
||
│ │ └── external/ (http-client, cache, types, adapters)
|
||
│ ├── drizzle.config.ts · vitest.config.ts · tsconfig.json
|
||
│ └── tests/ ✅ (auth, api, session, rate-limit, password — 26 tests)
|
||
├── web/ ✅ (Vite+React19+TS strict+Tailwind4+Router7+TanStack5 — build ✅, 3 tests)
|
||
│ └── src/ (main.tsx · App.tsx · lib/{api,session,queryClient} · components/RequireAuth · pages/{LoginPage,DashboardPage})
|
||
└── .vscode/ ✅ (extensions + settings)
|
||
```
|
||
|
||
## 8. Starter pack — Definition of Done
|
||
- [x] `pnpm dev` : API + web en dev, login → dashboard (2026-08-24 : HTTP 200 ; 2026-08-25 : front connecté, session persistée)
|
||
- [x] `docker compose up` : 1 conteneur, SQLite persisté en volume (image buildée 30s, conteneur up, health 200, données persistées)
|
||
- [x] Auth complète : Argon2id, session httpOnly, CSRF, logout, rate-limit /login (smoke 2026-08-24 + tests 2026-08-25 : login 401/429, logout, me)
|
||
- [x] Table `users` + migration Drizzle, 1er compte créé au 1er boot (migration 0000 générée ; `pnpm setup` idempotent, admin créé)
|
||
- [x] `/api/v1/health` public, le reste protégé (401 sans session, 200 avec — testé)
|
||
- [x] Tests verts (Vitest) : 26 tests serveur + 3 tests front, tous OK
|
||
- [x] Biome + `tsc --noEmit` clean (les 2 paquets + tsc web)
|
||
- [x] READMEs (E1) : racine + server + web — dev, prod, env, backup, sécurité (créés 2026-08-26)
|
||
|
||
## 9. Plan d'exécution (session de reprise)
|
||
1. Lire ce doc → répondre au §5 (ou valider les défauts) → loger les réponses dans §10.
|
||
2. ~~Scaffolder le monorepo~~ → **terminé (2026-08-25)** : racine ✅, `server/` ✅ (26 tests), `web/` ✅ (3 tests + build).
|
||
3. ~~Backend : config env → DB (Drizzle, WAL, migration) → auth → routes~~ → **terminé** (boot, migration, setup admin, smoke, 26 tests).
|
||
4. ~~Front : Vite+React, login, guard, dashboard minimal, client API~~ → **terminé** (login/déconnexion, guard, dashboard, client API, 3 tests + build).
|
||
5. ~~Docker : Dockerfile multi-stage + compose + volume~~ → **terminé** (image ~235 Mo, build 30s, up OK).
|
||
6. ~~Tests + lint + typecheck~~ → **terminé** (section §8 mise à jour 2026-08-25).
|
||
7. **Mettre à jour ce doc** (statut + §10) à la fin.
|
||
|
||
## 10. Journal des décisions
|
||
| Date | Décision |
|
||
|---|---|
|
||
| 2026-08-23 | Stack proposée : TS / Node22+Fastify / SQLite+Drizzle / Vite+React+Tailwind+shadcn / Vitest / Biome (à valider) |
|
||
| 2026-08-23 | 1 conteneur pour API + front (pas de CORS, déploiement simple) |
|
||
| 2026-08-23 | Workflow fichiers : contenu ajouté par petits blocs en edit (les gros writes timoutent) |
|
||
| 2026-08-23 | Docker 29.7.2 présent, plugin compose absent sur l'hôte → à résoudre avant test prod |
|
||
| 2026-08-23 | Scaffold `server/` écrit de manière autonome (21 fichiers, ~850 lignes) — **non installé, non compilé, non testé**. Front `web/` absent. |
|
||
| 2026-08-23 | Divergences du scaffold vs doc, à trancher : driver SQLite `@libsql/client` (pas better-sqlite3) ; service externe en package `external/` avec adaptateurs (pas un seul fichier) ; rate-limit custom (pas @fastify/rate-limit). |
|
||
| 2026-08-23 | Mode de travail imposé : planifier chaque tâche en mode plan, exécuter en petites tâches validées par Nicolas. Inventaire complet dans §11. |
|
||
| 2026-08-23 | Audit outils : node v26.7.0 (hôte — conteneur à fixer en 22 LTS), pnpm 11.3.0 ✅, npm 12, git 2.55 (identité user.name/email **non configurée**), sqlite3 CLI 3.53 ✅, make ✅, curl ✅. Gitea : rien sur `localhost:3000` → URL + accès à fournir par Nicolas. |
|
||
| 2026-08-23 | Re-test Docker : daemon démarré (server 29.7.2), `docker ps` OK (0 conteneur), compose 5.5.0 présent en plugin **et** standalone. Prêt pour la tâche Docker du §11. Gitea : accès SSH, clé à configurer par Nicolas le 2026-08-24. |
|
||
| 2026-08-24 | **A1–A4 faits** : `pnpm install` ✅, migration 0000 générée + appliquée au boot ✅, `tsc --noEmit` clean ✅, Biome clean ✅. Corrections : `biome.json` migré Biome 2.x (`preset`, `clientKind`, `noConsole`, patterns d'ignore), 12 auto-fixes, import inutile retiré, `pino-pretty` ajouté en devDep (boot cassé sans). **A5** : `setup.ts` créé (idempotent, refuse d'écraser, mdp admin ≥ 12, guard sans creds = erreur propre) — smoke : setup + idempotence + boot + `/api/health` + login ok (cookie + CSRF) + session + mdp faux → 401, tous ✅. |
|
||
| 2026-08-24 | **B1–B2 faits** : `tests/helpers.ts` (sqlite :memory:, unique par worker, applyMigrations, seedUser, cookies) + `tests/auth.test.ts` (6 tests : health, login ok/échec, corps invalide, argon2) + `tests/api.test.ts` (9 tests : health, login, 401/403 CSRF double-submit, logout + session morte, rate limit 429, error-handler 500 sans fuite, DB round-trip). Suite : 26/26 ✅, tsc ✅, biome ✅. |
|
||
| 2026-08-24 | **C3 fait** : `lib/session.ts` (`useSession()` sur `sessionQueryKey`, cache partagé login→guard→dashboard) ; `components/RequireAuth.tsx` (pending = spinner plein écran, `isError` → `navigate('/login', {replace:true})` via useEffect) ; `DashboardPage.tsx` réel (username, expiry locale `fr-FR`, bouton logout → `api.logout` + `removeQueries` + navigate — le catch du logout couvre la session déjà morte côté serveur) ; `App.tsx` : `/login` public, `/` sous `RequireAuth`, catch-all `*` → `/` ; `main.tsx` : QueryClient avec `staleTime: 30s` et `retry` qui exclut les 4xx (pas de retry sur 401/400). Pitfall TS noté : le narrowing de `session` ne propage pas dans le closure `handleLogout` → bind `const s = session` après le guard. tsc ✅, build ✅, biome ✅. |
|
||
| 2026-08-24 | **C2 fait** : client API `web/src/lib/api.ts` — `request()` fetch + `credentials: same-origin` + content-type JSON (merge headers corrigé : le spread `...init` écrasait le content-type des POST), `ApiError{status,code,retryAfterMs}`, `sessionQueryKey` pour le cache TanStack. `LoginPage.tsx` — formulaire contrôlé, `useMutation(api.login)`, validation miroir (user 1–64, pass 1–256), `onSuccess` → `setQueryData(sessionQueryKey)` + navigate `/`, erreurs mappées (invalid_credentials / too_many_attempts + retryAfterMs / 500). Contrat aligné `server/src/routes/auth.ts` (codes `invalid_body|invalid_credentials|too_many_attempts|not_authenticated`). tsc ✅, build ✅, biome ✅. |
|
||
| 2026-08-24 | **C1 fait** : `web/` scaffoldé (workspace `seiche-web` déjà câblé en racine) : Vite 7 + React 19 + TS strict + Tailwind 4 (plugin vite) + react-router 7 + TanStack Query 5, proxy dev `/api`→8787, `index.html` + entry + App (routes `/` et `/login` placeholders) + `tests/setup.ts` (jsdom + jest-dom 7.0.1 — 6.10 est déprécié/broken, peer `@testing-library/dom` 10.x déclaré). Vérif : tsc ✅, `vite build` ✅ (CSS TW émis), `vitest run` passWithNoTests ✅, biome ✅, dev server :5173 → HTTP 200. |
|
||
|
||
### Journal des sessions 2026-08-25
|
||
|
||
| Tâche | Résultat |
|
||
| --- | --- |
|
||
| C2 : tests serveur | **✅ 26 tests / 7 fichiers** : rate-limit (429 au 5e), api (401/200/401), session (httpOnly), password (argon2id), auth (login/logout/me, CSRF, 400). 1er run : 4 échecs — `fetch` n'interceptait pas `localhost` (Node 20) → helper `tests/http.ts` avec regex IP/localhost. Suite verte. |
|
||
| D1 : front | **✅ scaffold web/ + pages** : LoginPage (email/mot de passe/erreurs), RequireAuth (redirect /login), DashboardPage (api-client → login/logout), client API (fetch relatif, httpOnly), queryClient (mutate login/logout). **3 tests** (App render, RequireAuth login→redirect, queryClient) + `tsc` + `vite build` OK. Port 5173 (corrigé 2026-08-26), proxy Vite → 8787. |
|
||
| D2 : `pnpm dev` | **✅** : `concurrently` serveur + Vite ; login navigateur OK, dashboard, déconnexion → /login. |
|
||
| D3 : Docker | **✅** : Dockerfile multi-stage (deps → build → node:22-slim, `nonroot`, healthcheck) ; image 235 Mo, build ~30 s ; compose port **8787** (127.0.0.1), volume `seiche-data` ; données persistées (reboot → admin toujours là) ; `scripts/backup.sh` testé. |
|
||
| Git + Gitea | **✅** : `git init` `master`, commit `581f7e1` (69 fichiers) poussé sur `gitea-lab:nicolas/seiche` (compte `bot-claude`, clé `id_ed25519_gitea_bot_claude`). `.gitignore` corrigé (`.env` uniquement, pas `.env.example`). |
|
||
| E4 : Proxy (2026-08-26) | **✅** : `SEICHE_TRUST_PROXY` (Fastify `trustProxy` → rate-limit par IP client derrière proxy) + `SEICHE_COOKIE_SECURE` (cookies Secure derrière TLS). 4 tests (`proxy.test.ts`) : 6 IPs distinctes limitées séparément, XFF ignoré par défaut, Secure on/off. Suite : 30/30. Session précédente interrompue sur cette tâche ; finalisée et commitée. |
|
||
| E1 : READMEs (2026-08-26) | **✅** : README racine (démarrage dev/Docker, env, backup, sécurité) + `server/README.md` (scripts, migrations au boot, structure, API) + `web/README.md` (scripts, structure, contrat API). Corrections doc : port dev Vite = 5173 (pas 5199), health = `/api/health` (pas `/api/v1/health`). |
|
||
| F1 : Déploiement (2026-08-26) | **✅ décision + config** : prod = **LXC Proxmox** (Docker + compose dans le LXC), TLS via **nginx-proxy-manager** (Let's Encrypt, déjà fonctionnel). `docker-compose.yml` : `SEICHE_COOKIE_SECURE=1` + `SEICHE_TRUST_PROXY=1` (variables E4 enfin câblées). README : section « Production (LXC + NPM) ». Reste côté Nicolas : créer le LXC, règle NPM (hostname + `IP_LXC:8787`), domaine/certificat. |
|
||
|
||
**Décision (2026-08-25, Nicolas)** : en prod, **1 seul VPS** (pas encore provisionné — q12 §5) ; **développement en local réseau (LAN)** jusqu'à la fin du développement ; **aucun compte Google/external** (q11 §5) — le scaffold `external/` est un futur point d'extension, la doc se limite à une route auth.
|
||
|
||
**Décision (2026-08-26, Nicolas, remplace « 1 VPS »)** : prod = **auto-hébergement LXC Proxmox** (Docker dans le LXC, compose), TLS par **nginx-proxy-manager** (Let's Encrypt, déjà en place). Exécution infra (LXC, règle NPM, domaine) = côté Nicolas.
|
||
|
||
**Reste** : READMEs (E1) ✅, décision + config TLS/VPS (F1) ✅. Exécution du déploiement = infra Nicolas (LXC Proxmox, règle NPM, domaine).
|
||
|
||
## 11. État du repo — inventaire détaillé (2026-08-23)
|
||
|
||
**Faits vérifiés** (état initial du scaffold, 2026-08-23/24 — corrigés par A1–D4, voir §10) :
|
||
- `pnpm install` jamais lancé, migrations absentes, `setup.ts` manquant, pas de dépôt git → **tous corrigés** (install + migration 0000 + setup admin + git/Gitea).
|
||
|
||
**Plan d'exécution détaillé (2026-08-23, validé par Nicolas — petites tâches, doc mise à jour après chacune) :**
|
||
- [x] A1. `pnpm install` (workspace racine + server)
|
||
- [x] A2. Migrations Drizzle : `drizzle-kit generate` (0000_heavy_saracen)
|
||
- [x] A3. `tsc --noEmit` (fixer les erreurs de compilation)
|
||
- [x] A4. `biome check` (fixer lint/format)
|
||
- [x] A5. `server/src/setup.ts` (compte admin initial) + smoke test : boot, `/api/v1/health`, login
|
||
- [x] B1. Tests server : helper DB de test + login ok/échec (`tests/helpers.ts` + `tests/auth.test.ts`, 6 tests)
|
||
- [x] B2. Tests server : CSRF + error-handler + DB round-trip (+rate limit) — `tests/api.test.ts`, 9 tests ; suite : 26/26
|
||
- [x] C1. Scaffold `web/` (Vite+React+TS+Tailwind+Router+TanStack Query) — 2026-08-24, 10 fichiers, typecheck/build/biome ✅, dev :200, proxy `/api`→8787
|
||
- [x] C2. Front : client API + CSRF + page login — 2026-08-24 : `lib/api.ts` (fetch + cookies, `ApiError`, `sessionQueryKey`, login/session/logout) + `LoginPage.tsx` (useMutation, validation miroir serveur, mapping erreurs 401/429/500) ; tsc/build/biome ✅
|
||
- [x] C3. Front : guard session + dashboard minimal + wiring API — 2026-08-24 : `useSession()` (hook TanStack), `RequireAuth` (pending → spinner, erreur → redirect `/login`), `DashboardPage` réel (user + expiry + logout), QueryClient global (staleTime 30 s, pas de retry 4xx) ; tsc/build/biome ✅
|
||
- [x] C4. Tests front (smoke : render login, redirect) — 2026-08-24 : `web/tests/smoke.test.tsx` (vitest+jsdom+RTL, fetch stubbé) : rendu formulaire login, redirect non-auth → /login, login → dashboard ; `cleanup()` explicite dans `tests/setup.ts` (auto-cleanup inactif sans `globals`) ; QueryClient factorisé `web/src/lib/queryClient.ts` (partagé main.tsx ↔ tests) ; tsc/build/biome ✅, 3/3 tests
|
||
- [x] D1. Dockerfile multi-stage (Node 22, user non-root) — 2026-08-24 : `Dockerfile` racine (PAS multi-stage au final : le serveur n'a pas d'étape de build, TS exécuté via tsx ; base `node:22-slim` glibc pour les prébuilds `argon2`) : pnpm workspace frozen-lockfile, front `vite build` servi par @fastify/static (`SEICHE_PUBLIC_DIR=/app/web/dist`), `WORKDIR /app/server` (sinon `node --import tsx` ne résout pas tsx avec le layout pnpm), `USER node`, `SEICHE_HOST=0.0.0.0`, `VOLUME /data`, HEALTHCHECK `/api/health` ; `.dockerignore` ; testé : `docker build` + `run` → health ok, `/` et `/login` en 200
|
||
- [x] D2. docker-compose.yml + .dockerignore + volume /data — 2026-08-24 : `docker-compose.yml` racine (build local, image `seiche:latest`, port 8787, volume nommé `seiche-data:/data`, `restart: unless-stopped`, aucune variable requise au runtime) ; testé : up → health ok, `seiche.db` créé dans le volume (owner node)
|
||
- [x] D3. Test prod : build image, up, login via API, persistence volume — 2026-08-24 : image buildée + up compose → `/api/health` ok, front 200 ; persistance : `down` (sans -v) → `up` → db intacte ; `setup` exécuté DANS le conteneur (admin créé dans /data/seiche.db) ; `POST /api/auth/login` → 200 avec session + csrfToken
|
||
- [x] D4. Script backup SQLite (testé) — 2026-08-24 : `scripts/backup.sh [dir_sortie]` (défaut `backups/`, gitignored) : `VACUUM INTO` dans le conteneur (snapshot cohérent EN LIGNE via `@libsql/client`, le serveur continue de servir) → `docker compose cp` HORS du volume (un backup dans le même volume n'est pas un backup) → nettoyage du tmp → vérif en-tête SQLite + non-vide ; testé : backup de 40K créé, ré-ouvert dans le conteneur et interrogé (`users` → admin présent), /data propre après exécution ; prérequis : conteneur en marche
|
||
- [x] E1. README.md (dev, prod, env, backup, sécurité) → **fait (2026-08-26)** : README racine + `server/README.md` + `web/README.md`
|
||
- [x] E2. ~~`git init` + commit initial~~ → **fait (2026-08-25)** : `master`, commit `581f7e1`, poussé sur Gitea (`gitea-lab:nicolas/seiche`)
|
||
- [x] E3. ~~Update §8 + statut final~~ → **fait (2026-08-25)** : §8 coché, §7/§9/§10/§11 mis à jour
|
||
- [x] E4. Proxy (Caddy/nginx devant) : `SEICHE_TRUST_PROXY` + `SEICHE_COOKIE_SECURE` + 4 tests → **fait (2026-08-26)**
|
||
|
||
**Divergences du scaffold vs doc (à valider ou corriger) :**
|
||
- Driver SQLite : `@libsql/client` (scaffold) vs `better-sqlite3` (§3).
|
||
- Service externe : package `server/src/external/` avec adaptateurs (scaffold) vs module unique `clients/external.ts` (§6) — **choix retenu : le scaffold `external/`** ; q11 : Google/external = futur, hors périmètre doc.
|