demo de test des packages EQS de bout en bout
  • TypeScript 67%
  • JavaScript 26.6%
  • Shell 3.9%
  • Dockerfile 2.5%
Find a file
Nicolas Fryder c672fff61d
All checks were successful
ci / porte (push) Successful in 1m34s
ci / securite (push) Successful in 1m25s
ci / application (push) Successful in 2m37s
ci / pipeline (push) Successful in 0s
Merge pull request 'CI : le fuzzing passe la porte avec eqs-ci' (#25) from nicolas-fryder/dev into main
Reviewed-on: #25
2026-10-16 13:26:24 +00:00
.claude Une seconde application (profil nest), et l'alerte deepmerge-ts supprimée 2026-09-15 17:45:36 +02:00
.forgejo/workflows ci: le fuzzing passe la porte avec eqs-ci ; retire le banc d'essai 2026-10-16 12:29:29 +02:00
.githooks Socle 0.23.0 : l'e2e au pre-push, la CI sur v1 2026-10-14 22:07:32 +02:00
apps ci: l'identité de CI eqs-ci pour le fuzzing (eqs-workflows#9) ; banc d'essai temporaire 2026-10-16 11:34:13 +02:00
e2e Le parcours d'authentification et le métier, dans un navigateur 2026-09-15 12:40:32 +02:00
packages L'application d'épreuve du socle 2026-09-08 10:52:14 +02:00
pilotage La CI de sécurité, enfin exécutée, refusait deux choses 2026-10-14 17:49:47 +02:00
reference Le relevé de couverture dit enfin zéro règle muette 2026-09-16 13:07:29 +02:00
scripts La CI de sécurité, enfin exécutée, refusait deux choses 2026-10-14 17:49:47 +02:00
.dockerignore L'application d'épreuve du socle 2026-09-08 10:52:14 +02:00
.editorconfig L'application d'épreuve du socle 2026-09-08 10:52:14 +02:00
.env.example Monté en @eqs/* 0.24.0 2026-10-15 11:27:13 +00:00
.gitattributes L'application d'épreuve du socle 2026-09-08 10:52:14 +02:00
.gitignore ci: TEMPORAIRE — éprouve eqs-workflows@dev avant de déplacer v1 2026-10-14 18:20:21 +02:00
.gitleaksignore L'application d'épreuve du socle 2026-09-08 10:52:14 +02:00
.npmrc L'application d'épreuve du socle 2026-09-08 10:52:14 +02:00
.prettierignore L'application d'épreuve du socle 2026-09-08 10:52:14 +02:00
.trivyignore.yaml La CI de sécurité, enfin exécutée, refusait deux choses 2026-10-14 17:49:47 +02:00
AGENTS.md Un correctif écrit Closes #N 2026-09-21 08:57:31 +02:00
CLAUDE.md L'application d'épreuve du socle 2026-09-08 10:52:14 +02:00
docker-compose.coolify.yml L'étage e2e regarde l'image, et la sonde redevient littérale 2026-09-25 23:01:13 +02:00
docker-compose.repetition.yml La répétition générale démarre, et l'ÉTAT dit la vérité sur 0.5.1 2026-09-08 20:04:03 +02:00
docker-compose.yml Socle 0.12.1, et la démo quitte les ports du gabarit (slot 05) 2026-09-21 08:10:47 +02:00
Dockerfile Une seconde application (profil nest), et l'alerte deepmerge-ts supprimée 2026-09-15 17:45:36 +02:00
eqs-audit.json Monté en @eqs/* 0.15.0 : la porte ESLint atteint enfin l'API, et plus de fetch nu 2026-10-07 08:40:26 +02:00
eqs-baseline.json L'étage e2e regarde l'image, et la sonde redevient littérale 2026-09-25 23:01:13 +02:00
eslint.config.mjs Monté en @eqs/* 0.15.0 : la porte ESLint atteint enfin l'API, et plus de fetch nu 2026-10-07 08:40:26 +02:00
ETAT.md La répétition générale démarre, et l'ÉTAT dit la vérité sur 0.5.1 2026-09-08 20:04:03 +02:00
knip.ts Socle 0.11.2, et les déclarations provisoires tombent 2026-09-16 19:44:01 +02:00
package.json ci: l'identité de CI eqs-ci pour le fuzzing (eqs-workflows#9) ; banc d'essai temporaire 2026-10-16 11:34:13 +02:00
playwright.config.ts L'étage e2e regarde l'image, et la sonde redevient littérale 2026-09-25 23:01:13 +02:00
pnpm-lock.yaml chore: @eqs/* en 0.24.3 ; le contrat régénéré (502, 503, 413 documentés) 2026-10-16 09:20:33 +02:00
pnpm-workspace.yaml Une seconde application (profil nest), et l'alerte deepmerge-ts supprimée 2026-09-15 17:45:36 +02:00
README.md La répétition générale du déploiement, et ce qu'elle a trouvé 2026-09-08 11:32:31 +02:00
trivy.yaml La CI de sécurité, enfin exécutée, refusait deux choses 2026-10-14 17:49:47 +02:00

eqs-demo

L'application d'épreuve du socle @eqs (ROADMAP #18). Elle n'a aucune valeur métier : elle existe pour appeler chaque package en conditions réelles et casser avant la flotte.

Ce n'est pas un banc d'essai hors sol. C'est un satellite ordinaire — profil next-fullstack, cloné du template, même porte, même contrat, même image — dont le domaine se résume à des relevés de mesure. La seule chose qu'elle fait de plus qu'un satellite : elle donne à la suite de conformité de quoi jouer les contrôles qui, ailleurs, se déclarent « non exécutables ».

Un contrôle non exécutable n'est pas un succès. C'est une promesse que personne n'a vérifiée.

Ce qu'elle prouve, package par package

@eqs/config baseSchema.extend({ DATABASE_URL }) dans src/lib/api.ts : le service refuse de démarrer sans base, en la nommant. Une variable posée mais VIDE compte comme absente
@eqs/errors EqsError.notFound / .validation / .conflict levées par src/lib/mesures.ts ; /api/demo/plantage prouve le 500 sans pile ni message interne
@eqs/telemetry withSpan autour de chaque opération et log.info corrélé, dans src/lib/mesures.ts
@eqs/auth Les rôles de roles.yaml ; le dépôt de clés sur Prisma (src/lib/cles.ts) ; l'émission bornée par scopesBornes ; le compte de secours ; la garde par operationId
@eqs/http defineTarget et le mutator dans src/lib/cibles.ts, exercés par /api/demo/sortant ; les fetch de même origine du navigateur sont annotés, pas cachés
@eqs/platform-next creerApi, withApi, les routes standard, le proxy à nonce, EqsErreurAffichee et app/error.tsx
@eqs/testing runConformance, demarrerRoutes, withTestDatabase : la suite monte les routes derrière le vrai proxy, sur une base jetable avec les vraies migrations
@eqs/cli eqs check, doctor, inspect, baseline, audit, openapi export --check
@eqs/tooling Les trois configurations partagées, dont la règle de l'horloge injectée que src/domaine/mesures.ts respecte

Les routes de démonstration

Trois routes n'ont pas de raison métier : elles existent pour rendre un contrôle exécutable, et chacune dit pourquoi dans son fichier.

  • POST /api/demo/valider — publique, corps .strict(). Sans elle, errors.validation ne peut rien constater : il appelle sans en-tête, et une route gardée répondrait 401 avant d'avoir regardé le corps.
  • GET /api/demo/plantage — publique, lève une exception non gérée. Sans elle, « un 500 ne fuite jamais de pile » reste une croyance.
  • GET /api/demo/sortant — gardée, appelle sa propre /api/version par le mutator. Elle montre ce que @eqs/http pose sans qu'on le demande.

Les trois étages de test

test/*.test.ts Le DOMAINE, sans réseau ni base. Horloge injectée : ces cas ne vieillissent pas
test/socle/*.test.ts Un fichier par package : ils n'éprouvent pas le package, ils éprouvent son branchement. Un package peut être parfait et mal branché
test/integration/socle.test.ts Les chemins que cette application emprunte vraiment, requête complète, statuts et en-têtes exacts
test/integration/conformite.test.ts Les contrôles du socle, joués par runConformance contre l'application montée

Les deux derniers exigent Docker : ils lèvent une base jetable, appliquent les vraies migrations, et la détruisent après coup.

Ce qui a été joué

  • pnpm check vert, largement sous le budget ;
  • 38 tests unitaires, 27 tests d'intégration ;
  • suite de conformité 12 sur 12, et surtout zéro contrôle non exécutable — contre huit au template — avec une vraie clé API, une clé révoquée, une identité valide sans le droit, et une connexion réelle par le compte de secours ;
  • contrat dérivé du code : douze opérations, toutes en ressource.action ;
  • eqs doctor sans écart, eqs audit sans alerte non dispensée.

Six défauts du socle en sont sortis, corrigés dans eqs-platform : le compte de secours jamais amorcé en profil next-fullstack ; la suite qui prenait une route publique pour une route gardée ; le contrôle « secret dans le bundle » qui fouillait la sortie serveur ; le masquage des journaux qui laissait passer AUTH_SECRET et DATABASE_URL ; assertEnvExample qui échouait sur le .env.example du template lui-même ; et operationsInconnues qui n'acceptait pas ce que chargerCatalogue rend.

Le dernier contrôle muet, et comment l'allumer

auth.403-audience reste non exécutable : il exige un jeton Entra valablement signé mais destiné à une autre audience. On ne peut pas le fabriquer sans un vrai locataire — un jeton simulé aurait une signature que le garde rejetterait en 401, alors que le contrôle attend un 403, et la nuance est tout l'objet du contrôle.

Ce qu'il faut poser dans le .env local (jamais versionné, .gitignore le couvre, et le hook pre-commit passe gitleaks) :

MS_TENANT_ID=…
MS_CLIENT_ID=…
MS_CLIENT_SECRET=…
ENTRA_AUDIENCE=api://eqs-demo

Une seule app registration suffit. La suite acquiert un vrai jeton de service pour api://eqs-demo (acquireServiceToken de @eqs/auth), puis monte l'application avec ENTRA_AUDIENCE=api://autre-chose : le jeton est authentique, l'audience ne correspond pas, la réponse doit être 403 et pas 401. Un service qui répond 401 dit « identifie-toi » à quelqu'un qui l'est déjà, et qui réessaiera indéfiniment.

Les mêmes variables débloquent au passage deux choses que rien n'éprouve aujourd'hui : le quatrième fournisseur entrant (jeton de service accepté) et le sortant avec identité de service (defineTarget avec audience), dans les deux sens.

Le manifeste d'app registration se génère : eqs auth entra-manifest. Sa création et son consentement restent manuels (D-031).

La répétition générale du déploiement

eqs coolify verify         # statique, une seconde, sans Docker
eqs coolify verify --up    # construit, démarre, attend que Docker dise `healthy`

Le premier étage vérifie ce que Coolify lira : les variables que personne ne fournit, la cible de construction absente du Dockerfile, la sonde qui interroge un autre port que celui servi, le secret de build que le compose ne donne pas. Il a vocation à tourner dans eqs check.

Le second est la répétition. Il charge en plus docker-compose.repetition.yml, qui fournit ce que Coolify fournit à côté — la base, qui est une ressource du projet et non un service de l'application. Il attend l'état healthy que DOCKER calcule, avec la sonde du compose : c'est le seul moyen de vérifier cette ligne-là, celle sur laquelle Coolify décide de router le trafic.

Ce qu'on ne fait pas : monter une instance jetable de Coolify. Elle répliquerait le produit, pas la nôtre — sa version, son proxy, ses réglages de projet sont précisément là où la dérive se loge. La vérification finale est un projet de bac à sable sur la vraie instance.

État au 2026-09-08 : l'étage statique est vert. L'étage --up échoue à la construction, et pour une raison qu'il faut dire : l'image installe les @eqs/* publiés, et cette application se sert de correctifs qui ne le sont pas encore (assertEnvExample à trois arguments, operationsInconnues sur un catalogue résolu). Il redeviendra jouable à la sortie suivante du socle. C'est la contrepartie assumée d'une application d'épreuve : elle vit un cran devant.

Faire tourner

docker compose up -d          # postgres + collecteur OTLP
cp .env.example .env          # puis remplir AUTH_SECRET
pnpm install
pnpm --filter web db:deploy
pnpm dev

La suite d'intégration exige Docker : elle lève sa propre base, applique les migrations, et la détruit après coup.

pnpm test:integration