Commit 7b2e575e authored by Kourser's avatar Kourser
Browse files

Des parcours dans un vrai navigateur, et la charge mesurée

« 1 662 garanties » disait la même chose d'un contrôle qui consigne pour de
vrai et d'un contrôle qui cherche un nom dans un fichier. Le bilan de
`pnpm verif` compte désormais les deux à part, et nomme ce qu'il ne voit pas.

`pnpm e2e` : vingt parcours dans Chrome, sur une instance levée par index.ts
avec sa propre base (EX-40). Deux personnes, deux navigateurs ; la voix
mesurée à l'énergie que l'autre reçoit ; chaque écran par le rail et par son
adresse ; axe-core et le clavier ; le premier affichage sur réseau bridé.
Toute exception, erreur de console, réponse 5xx ou route absente fait
échouer le parcours. Ce sont eux qui ont trouvé les défauts corrigés dans les
lots précédents.

`pnpm charge` : cinq cents écrans qui rejouent ce que fait le navigateur,
relevé sur l'interface. `pnpm charge:visio` : le volume qu'une cellule de
cinquante fait relayer au serveur média. Seuils d'EX-14, échec au-delà.

À l'intégration : le budget à chaque passage, les parcours quand une base et
un navigateur sont là, la pile complète la nuit.

Co-Authored-By: default avatarClaude Opus 5 <noreply@anthropic.com>
parent cd4fbc58
Loading
Loading
Loading
Loading
Loading
+64 −1
Changes for .gitlab-ci.yml: 64 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -54,11 +54,16 @@ verifications:
    - pnpm install --frozen-lockfile
    - pnpm typecheck
    - pnpm accords
    # Le poids du premier chargement (EX-12) : l'interface construite, son
    # manifeste lu, et l'échec au-delà de 300 Ko. Aucun service n'est requis —
    # il n'y a donc aucune raison que ce contrôle attende quoi que ce soit.
    - pnpm --filter @kastell/web build
    - pnpm budget
    # Un lien mort fait échouer la construction : dans une documentation de
    # crise, c'est un défaut, pas un détail.
    - pnpm --filter @kastell/site build

# ── Les 602 garanties du produit ───────────────────────────────────────────
# ── Les garanties du produit ───────────────────────────────────────────────
#
# Elles exigent PostgreSQL, Redis et un stockage objet. Sur un exécuteur shell
# sans ces services, ce travail ne se déclenche pas — plutôt que d'échouer à
@@ -74,8 +79,66 @@ garanties:
    - if: $DATABASE_URL
  script:
    - pnpm install --frozen-lockfile
    # « --env-file » exige un fichier : l'exemple le fournit, les variables de
    # l'intégration l'emportent sur lui.
    - test -f .env || cp .env.example .env
    - pnpm verif

# ── Les parcours en navigateur (EX-40) ─────────────────────────────────────
#
# Un vrai Chromium sur une instance levée par le même démarrage qu'en
# production, avec sa base à part (<base>_navigateur). Même condition que les
# garanties — une base d'intégration — et une seconde, KASTELL_E2E, parce qu'il
# faut aussi que l'hôte puisse faire tourner un navigateur.
#
# Les variables de l'intégration l'emportent sur le fichier d'exemple :
# « --env-file » ne remplace pas une variable déjà posée. Sans serveur média,
# poser KASTELL_E2E_SANS_VISIO : les parcours d'appel se sautent en le disant,
# et s'éprouvent au travail « pile ».
navigateur:
  stage: verifier
  needs: [verifications]
  rules:
    - if: $DATABASE_URL && $KASTELL_E2E
  script:
    - pnpm install --frozen-lockfile
    - test -f .env || cp .env.example .env
    - pnpm --filter @kastell/e2e exec playwright install chromium
    - pnpm e2e
  artifacts:
    when: always
    expire_in: 14 days
    paths: [apps/e2e/rapport, apps/e2e/test-results]
    reports:
      junit: apps/e2e/rapport/junit.xml

# ── La pile complète, la nuit (EX-14, EX-40) ───────────────────────────────
#
# Sur un hôte où la composition tourne : le banc contre les vrais services,
# les parcours en navigateur visio comprise, cinq cents écrans sur une crise
# et une cellule de cinquante en appel. Long, et dépendant de l'hôte — d'où un
# déclenchement programmé plutôt qu'à chaque poussée. Les rapports sont gardés ;
# les jetons de session et les traces de l'instance d'essai, jamais.
pile:
  stage: verifier
  rules:
    - if: $CI_PIPELINE_SOURCE == "schedule" && $KASTELL_PILE
  timeout: 2h
  script:
    - pnpm install --frozen-lockfile
    - pnpm --filter @kastell/e2e exec playwright install chromium
    - pnpm banc
    - pnpm e2e
    - pnpm charge
    - pnpm charge:visio
  artifacts:
    when: always
    expire_in: 30 days
    paths:
      - apps/e2e/rapport
      - apps/api/.essais/rapport-charge.json
      - apps/api/.essais/charge-visio.log

# ── La documentation, publiée par GitLab Pages ─────────────────────────────
pages:
  stage: publier
+55 −0
Changes for README.md: 55 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -106,6 +106,11 @@ falsification par un vérificateur qui n'utilise pas la base, et reconstitution
d'un état passé. Chaque ligne renvoie à l'exigence du cahier des charges qu'elle
démontre.

**Le bilan dit ce qu'il prouve.** Une partie des garanties exécute le produit ;
une autre juge en lisant son code — elles gardent une décision de conception,
elles ne prouvent pas qu'un écran fonctionne. Le bilan compte les deux à part,
et nomme ce que la suite ne voit pas.

### Éprouver la vraie pile

```bash
@@ -125,6 +130,56 @@ réussite ou échec.
La composition doit tourner. Ce que le poste ne permet pas d'éprouver — pas de
synthèse vocale, pas de relais SMTP — est dit et **ne compte pas comme réussi**.

### Les parcours en navigateur

```bash
pnpm e2e
```

Un vrai Chrome sur une vraie instance : `index.ts` lui-même, l'interface
construite, une base à part (`…_navigateur`) remise à zéro, et aucun envoi réel.
Deux personnes, deux navigateurs. Ils entrent, déclenchent, consignent et
regardent l'entrée paraître chez l'autre ; coupent le flux et vérifient qu'il
reprend sans trou ; s'appellent et mesurent l'énergie audio que l'autre reçoit ;
ouvrent chaque écran par le rail et par son adresse ; passent axe-core sur
l'écran rendu et font le tour au clavier ; chronomètrent le premier affichage sur
un réseau mobile bridé. Toute exception, erreur de console, réponse 5xx ou route
absente fait échouer le parcours où elle survient.

Le premier passage a trouvé, entre autres : une entrée de main courante qui ne
paraissait chez personne d'autre, la reprise après coupure qui rendait le texte
des canaux privés, les routes des agents jamais enregistrées, un lien
d'évitement qui ne menait nulle part, la feuille de Google Fonts qui laissait la
page blanche sur un réseau qui ne joint pas Google.

Le rapport HTML : `pnpm --filter @kastell/e2e e2e:rapport`. Sans serveur média,
`KASTELL_E2E_SANS_VISIO=1` saute les parcours d'appel, en le disant.

### La charge

```bash
pnpm charge          # cinq cents écrans sur une crise (EX-14)
pnpm charge:visio    # une cellule de cinquante en appel (EX-14)
```

`pnpm charge` lève une instance semée comme une crise au bout de quelques
heures — cinq cents personnes, deux mille entrées, mille messages — et un robot
qui rejoue ce que fait le navigateur, relevé sur l'interface : les lectures à
l'ouverture, le flux, et les relectures qu'une dépêche déclenche. Il mesure ce
qu'une personne vit : le temps entre l'entrée consignée par l'une et l'entrée
lisible chez les cinq cents autres. Seuils d'EX-14, et échec au-delà. Le volume
et le rythme se règlent par `KASTELL_CHARGE_*`.

`pnpm charge:visio` confie au testeur de LiveKit le volume qu'une cellule de
cinquante fait relayer au serveur média — cinquante micros, neuf caméras,
2 900 pistes — et échoue si une piste ne se publie pas, si le volume n'est pas
relayé, ou si la perte dépasse 2 %.

Mesuré sur un poste de développement (8 cœurs, pile et robot sur la même
machine) : une entrée lisible chez les cinq cents autres en 0,8 s au 95ᵉ
centile, aucune dépêche perdue sur 89 000 ; 2 924 pistes relayées sans perte.
Avant les corrections que ce robot a imposées, la même entrée mettait 28 s.

---

## Structure

apps/api/.gitignore

0 → 100644
+2 −0
Changes for apps/api/.gitignore: 2 added lines, 0 removed lines.
Original line number Diff line number Diff line
# Ce que les essais écrivent à chaque passage : jetons de session, comptes.
.essais/
+5 −1
Changes for apps/api/package.json: 5 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -9,8 +9,12 @@
    "migrate": "tsx --env-file=../../.env src/db/migrate.ts",
    "verif": "tsx --env-file=../../.env src/verif.ts",
    "banc": "tsx --env-file=../../.env src/banc.ts",
    "essai:navigateur": "tsx --env-file=../../.env src/essais/navigateur.ts",
    "build": "esbuild src/index.ts --bundle --platform=node --target=node22 --format=esm --outfile=dist/index.js --banner:js=\"import{createRequire}from'module';const require=createRequire(import.meta.url);\" && esbuild src/racine.ts --bundle --platform=node --target=node22 --format=esm --outfile=dist/racine.js --banner:js=\"import{createRequire}from'module';const require=createRequire(import.meta.url);\" && esbuild src/amorce-cli.ts --bundle --platform=node --target=node22 --format=esm --outfile=dist/amorce.js --banner:js=\"import{createRequire}from'module';const require=createRequire(import.meta.url);\"",
    "amorcer": "node --env-file=../../.env --import tsx src/amorce-cli.ts"
    "amorcer": "node --env-file=../../.env --import tsx src/amorce-cli.ts",
    "essai:charge": "tsx --env-file=../../.env src/essais/instance-charge.ts",
    "charge": "tsx --env-file=../../.env src/essais/charge.ts",
    "charge:visio": "tsx --env-file=../../.env src/essais/charge-visio.ts"
  },
  "dependencies": {
    "@aws-sdk/client-s3": "^3.1116.0",
+348 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading