- TypeScript 67%
- JavaScript 26.6%
- Shell 3.9%
- Dockerfile 2.5%
|
|
||
|---|---|---|
| .claude | ||
| .forgejo/workflows | ||
| .githooks | ||
| apps | ||
| e2e | ||
| packages | ||
| pilotage | ||
| reference | ||
| scripts | ||
| .dockerignore | ||
| .editorconfig | ||
| .env.example | ||
| .gitattributes | ||
| .gitignore | ||
| .gitleaksignore | ||
| .npmrc | ||
| .prettierignore | ||
| .trivyignore.yaml | ||
| AGENTS.md | ||
| CLAUDE.md | ||
| docker-compose.coolify.yml | ||
| docker-compose.repetition.yml | ||
| docker-compose.yml | ||
| Dockerfile | ||
| eqs-audit.json | ||
| eqs-baseline.json | ||
| eslint.config.mjs | ||
| ETAT.md | ||
| knip.ts | ||
| package.json | ||
| playwright.config.ts | ||
| pnpm-lock.yaml | ||
| pnpm-workspace.yaml | ||
| README.md | ||
| trivy.yaml | ||
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.validationne 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/versionpar le mutator. Elle montre ce que@eqs/httppose 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 checkvert, 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 doctorsans écart,eqs auditsans 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