profilDe ne lit que le manifeste de la racine : tout dépôt de profil nest rend profile: null, et c'est exactement le cas que eqs migrate sert #99

Closed
opened 2026-09-30 18:27:57 +00:00 by nicolas-fryder · 3 comments

Ce qui se passe

Sur nuage — apps/api NestJS 11 + apps/front Next 16, workspace pnpm, profil nest sans conteste :

$ eqs migrate inspect
{
  "service": "nuage-equilibre",
  "profile": null,
  …
}

Et le plan qui en découle ne peut rien filtrer :

$ eqs migrate plan
eqs migrate plan — nuage-equilibre
  …
  7. @eqs/platform-nest — createEqsApp(), health, version, contrat
  8. @eqs/platform-next — creerApi(), withApi(), proxy Edge

Forcer le profil suffit — eqs migrate inspect --profile nest rend bien "profile": "nest". Ce n'est donc pas la connaissance qui manque, c'est l'endroit où on la cherche.

La cause, lue dans dist/depot.js

export function profilDe(depot) {
    if (depot.profilDeclare) return { profil: depot.profilDeclare, observe: false };
    const noms = new Set(depot.paquets.map((p) => p.nom));
    if (noms.has('@eqs/platform-nest')) return { profil: 'nest', observe: true };
    if (noms.has('@eqs/platform-next')) return { profil: 'next-fullstack', observe: true };
    const dependances = { ...depot.manifeste.devDependencies, ...depot.manifeste.dependencies };
    if ('@nestjs/core' in dependances) return { profil: 'nest', observe: true };
    if ('next' in dependances) { … }
    return { profil: null, observe: true };
}

depot.manifeste est le package.json de la racine. Dans un profil nest, @nestjs/core vit dans apps/api/package.json et next dans apps/web/package.json — jamais à la racine. La détection par framework ne peut donc jamais aboutir sur un profil nest.

Pourquoi personne ne l'avait vu

Les trois chemins qui marchent masquent le quatrième :

pourquoi ça marche
le gabarit et eqs-demo ils déclarent eqs.profile, et profilDeclare court-circuite tout
ATLAS next-fullstack mono-application : next EST dans le manifeste de la racine
un dépôt déjà rétrofitté @eqs/platform-* est installé, et les deux premières lignes suffisent

Reste le seul cas que eqs migrate existe pour servir : un dépôt nest existant, qui ne déclare rien et n'a aucun paquet du socle. C'est précisément là que la déduction rend null.

L'ironie, et c'est elle qui rend le correctif évident

lireDepot, vingt lignes plus haut dans le même fichier, parcourt déjà les applications du workspace — avec un commentaire qui explique pourquoi :

// Les packages du socle vivent où le dépôt les met : dans un workspace, ils
// sont dans `apps/api` et `apps/web`, pas à la racine. Ne lire que la racine
// faisait dire à l'audit que `@eqs/http` n'était pas installé, donc que
// personne ne comptait les appels bruts — un `null` de complaisance là où il
// y avait une mesure à faire.
const projets = projetsDuWorkspace(root);
const emplacements = [{ dir: root, manifeste }, ...projets.map(…)];

La leçon est déjà tirée, écrite, et appliquée — mais seulement aux @eqs/*, parce que la boucle filtre sur nom.startsWith('@eqs/'). profilDe retombe deux fonctions plus loin sur depot.manifeste seul et refait exactement le « null de complaisance » que le commentaire dénonce.

C'est le miroir de #83 : là, knipSatellite() déduisait de apps/* et ne voyait pas une application à la racine ; ici, profilDe déduit de la racine et ne voit pas les applications.

Ce que je proposerais

Faire chercher profilDe dans les mêmes emplacements que lireDepot : un @nestjs/core où que ce soit dans le workspace donne nest ; un next sans NestJS donne front ou next-fullstack selon les route.ts. Deux lignes, et le cas que la commande sert cesse d'être celui qu'elle rate.

Au passage, quand le profil reste null, migrate plan gagnerait à le dire en en-tête plutôt que de lister les dix étapes sans filtre — le lecteur ne peut pas deviner que la liste n'est pas filtrée. C'est le versant « non déduit » de ce que #79 demande pour le versant « mal filtré ».

Ce qui n'est PAS un défaut, vérifié

migrate plan --profile nest liste quand même @eqs/platform-next à l'étape 8, et c'est correct : un profil nest a un apps/web en profil front, qui importe bien @eqs/platform-next (docs/profils.md). L'asymétrie de #79 tient donc — un next-fullstack n'a pas besoin de platform-nest, l'inverse n'est pas vrai.

Versions

@eqs/cli 0.14.0, pnpm 11.8.0, Node 24.15.0, Windows 11. Contexte : rétrofit de nuage (EQS-Sandbox/NUAGE, branche retrofit-socle-eqs), tâche 6 du plan docs/superpowers/plans/2026-09-30-retrofit-etages-0-2.md.

Contournement posé

nuage déclare eqs.profile: "nest" dans son manifeste — ce qu'il allait faire de toute façon à l'étage 2. Il n'y aura rien à retirer quand la déduction saura voir le workspace : elle restera court-circuitée par la déclaration, comme prévu.

## Ce qui se passe Sur `nuage` — `apps/api` NestJS 11 + `apps/front` Next 16, workspace pnpm, profil `nest` sans conteste : ``` $ eqs migrate inspect { "service": "nuage-equilibre", "profile": null, … } ``` Et le plan qui en découle ne peut rien filtrer : ``` $ eqs migrate plan eqs migrate plan — nuage-equilibre … 7. @eqs/platform-nest — createEqsApp(), health, version, contrat 8. @eqs/platform-next — creerApi(), withApi(), proxy Edge ``` Forcer le profil suffit — `eqs migrate inspect --profile nest` rend bien `"profile": "nest"`. Ce n'est donc pas la connaissance qui manque, c'est l'endroit où on la cherche. ## La cause, lue dans `dist/depot.js` ```js export function profilDe(depot) { if (depot.profilDeclare) return { profil: depot.profilDeclare, observe: false }; const noms = new Set(depot.paquets.map((p) => p.nom)); if (noms.has('@eqs/platform-nest')) return { profil: 'nest', observe: true }; if (noms.has('@eqs/platform-next')) return { profil: 'next-fullstack', observe: true }; const dependances = { ...depot.manifeste.devDependencies, ...depot.manifeste.dependencies }; if ('@nestjs/core' in dependances) return { profil: 'nest', observe: true }; if ('next' in dependances) { … } return { profil: null, observe: true }; } ``` `depot.manifeste` est le `package.json` de la **racine**. Dans un profil `nest`, `@nestjs/core` vit dans `apps/api/package.json` et `next` dans `apps/web/package.json` — jamais à la racine. La détection par framework ne peut donc jamais aboutir sur un profil `nest`. ## Pourquoi personne ne l'avait vu Les trois chemins qui marchent masquent le quatrième : | | pourquoi ça marche | | --- | --- | | le gabarit et `eqs-demo` | ils **déclarent** `eqs.profile`, et `profilDeclare` court-circuite tout | | ATLAS | `next-fullstack` mono-application : `next` EST dans le manifeste de la racine | | un dépôt déjà rétrofitté | `@eqs/platform-*` est installé, et les deux premières lignes suffisent | Reste le seul cas que `eqs migrate` existe pour servir : **un dépôt `nest` existant, qui ne déclare rien et n'a aucun paquet du socle.** C'est précisément là que la déduction rend `null`. ## L'ironie, et c'est elle qui rend le correctif évident `lireDepot`, vingt lignes plus haut dans le même fichier, parcourt déjà les applications du workspace — avec un commentaire qui explique pourquoi : ```js // Les packages du socle vivent où le dépôt les met : dans un workspace, ils // sont dans `apps/api` et `apps/web`, pas à la racine. Ne lire que la racine // faisait dire à l'audit que `@eqs/http` n'était pas installé, donc que // personne ne comptait les appels bruts — un `null` de complaisance là où il // y avait une mesure à faire. const projets = projetsDuWorkspace(root); const emplacements = [{ dir: root, manifeste }, ...projets.map(…)]; ``` La leçon est déjà tirée, écrite, et appliquée — mais seulement aux `@eqs/*`, parce que la boucle filtre sur `nom.startsWith('@eqs/')`. `profilDe` retombe deux fonctions plus loin sur `depot.manifeste` seul et refait exactement le « `null` de complaisance » que le commentaire dénonce. C'est le miroir de #83 : là, `knipSatellite()` déduisait de `apps/*` et ne voyait pas une application à la racine ; ici, `profilDe` déduit de la racine et ne voit pas les applications. ## Ce que je proposerais Faire chercher `profilDe` dans les mêmes `emplacements` que `lireDepot` : un `@nestjs/core` **où que ce soit** dans le workspace donne `nest` ; un `next` sans NestJS donne `front` ou `next-fullstack` selon les `route.ts`. Deux lignes, et le cas que la commande sert cesse d'être celui qu'elle rate. Au passage, quand le profil reste `null`, `migrate plan` gagnerait à le dire en en-tête plutôt que de lister les dix étapes sans filtre — le lecteur ne peut pas deviner que la liste n'est pas filtrée. C'est le versant « non déduit » de ce que #79 demande pour le versant « mal filtré ». ## Ce qui n'est PAS un défaut, vérifié `migrate plan --profile nest` liste quand même `@eqs/platform-next` à l'étape 8, et **c'est correct** : un profil `nest` a un `apps/web` en profil `front`, qui importe bien `@eqs/platform-next` (`docs/profils.md`). L'asymétrie de #79 tient donc — un `next-fullstack` n'a pas besoin de `platform-nest`, l'inverse n'est pas vrai. ## Versions `@eqs/cli` 0.14.0, pnpm 11.8.0, Node 24.15.0, Windows 11. Contexte : rétrofit de `nuage` (`EQS-Sandbox/NUAGE`, branche `retrofit-socle-eqs`), tâche 6 du plan `docs/superpowers/plans/2026-09-30-retrofit-etages-0-2.md`. ## Contournement posé `nuage` déclare `eqs.profile: "nest"` dans son manifeste — ce qu'il allait faire de toute façon à l'étage 2. Il n'y aura rien à retirer quand la déduction saura voir le workspace : elle restera court-circuitée par la déclaration, comme prévu.
Author
Owner

Même cause, deuxième symptôme, trouvé en produisant le premier eqs-audit.json de nuage.

data.ownDatabase rend null sur un dépôt qui a une base

$ eqs inspect
…
  data.ownDatabase   null   ← « ni DATABASE_URL dans .env.example, ni schéma à lire »

Or les deux existent :

$ grep -c '^DATABASE_URL' apps/api/.env.example
1
$ ls apps/api/prisma/schema.prisma
apps/api/prisma/schema.prisma

Ce qui manque à la racine, c'est seulement le DATABASE_URL — il y a bien un .env.example racine, mais il porte les variables de la pile, pas celles de l'API.

C'est la même erreur que profilDe : la lecture s'arrête au dossier racine alors que, dans un profil nest, l'application vit dans apps/api — et avec elle son .env.example, son prisma/schema.prisma, son package.json. lireDepot sait déjà parcourir projetsDuWorkspace() ; le contrôle qui produit data.ownDatabase ne s'en sert pas plus que profilDe.

Le mode d'échec est le même qu'ailleurs, et c'est lui qui fait mal : null se lit comme « pas mesuré », alors qu'il y avait quelque chose à mesurer et qu'on est passé à côté. Le commentaire de lireDepot le dit mieux que moi — « un null de complaisance là où il y avait une mesure à faire ».

Ce que ça suggère

Le correctif proposé plus haut — faire chercher dans les mêmes emplacements — vaudrait la peine d'être appliqué à tous les contrôles statiques qui lisent un fichier de dépôt, pas seulement à profilDe : .env.example, prisma/schema.prisma, et tout ce qui se trouve « à côté de l'application » plutôt qu'à la racine. Sinon chaque contrôle le redécouvrira, un par un, sur chaque satellite nest.

C'est la troisième fois de la journée que ce motif sort : #83 pour knipSatellite() (qui déduisait de apps/* et ne voyait pas une racine), #100 pour porter() (qui préfixait la racine et ne voyait pas src/), et celui-ci. Les trois disent la même chose sous trois angles — ce qui est déduit de la forme du gabarit ne vaut pas pour un dépôt que D-003 laisse où il est, y compris quand le dépôt a exactement la forme du gabarit mais que l'outil regarde au mauvais étage.

@eqs/cli 0.14.0, EQS-Sandbox/NUAGE branche retrofit-socle-eqs, profil nest déclaré.

Même cause, deuxième symptôme, trouvé en produisant le premier `eqs-audit.json` de `nuage`. ## `data.ownDatabase` rend `null` sur un dépôt qui a une base ``` $ eqs inspect … data.ownDatabase null ← « ni DATABASE_URL dans .env.example, ni schéma à lire » ``` Or les deux existent : ``` $ grep -c '^DATABASE_URL' apps/api/.env.example 1 $ ls apps/api/prisma/schema.prisma apps/api/prisma/schema.prisma ``` Ce qui manque à la racine, c'est seulement le `DATABASE_URL` — il y a bien un `.env.example` racine, mais il porte les variables de la pile, pas celles de l'API. C'est la même erreur que `profilDe` : la lecture s'arrête au dossier racine alors que, dans un profil `nest`, **l'application vit dans `apps/api`** — et avec elle son `.env.example`, son `prisma/schema.prisma`, son `package.json`. `lireDepot` sait déjà parcourir `projetsDuWorkspace()` ; le contrôle qui produit `data.ownDatabase` ne s'en sert pas plus que `profilDe`. Le mode d'échec est le même qu'ailleurs, et c'est lui qui fait mal : **`null` se lit comme « pas mesuré »**, alors qu'il y avait quelque chose à mesurer et qu'on est passé à côté. Le commentaire de `lireDepot` le dit mieux que moi — « un `null` de complaisance là où il y avait une mesure à faire ». ## Ce que ça suggère Le correctif proposé plus haut — faire chercher dans les mêmes `emplacements` — vaudrait la peine d'être appliqué **à tous les contrôles statiques qui lisent un fichier de dépôt**, pas seulement à `profilDe` : `.env.example`, `prisma/schema.prisma`, et tout ce qui se trouve « à côté de l'application » plutôt qu'à la racine. Sinon chaque contrôle le redécouvrira, un par un, sur chaque satellite `nest`. C'est la troisième fois de la journée que ce motif sort : #83 pour `knipSatellite()` (qui déduisait de `apps/*` et ne voyait pas une racine), #100 pour `porter()` (qui préfixait la racine et ne voyait pas `src/`), et celui-ci. Les trois disent la même chose sous trois angles — **ce qui est déduit de la forme du gabarit ne vaut pas pour un dépôt que D-003 laisse où il est**, y compris quand le dépôt a exactement la forme du gabarit mais que l'outil regarde au mauvais étage. `@eqs/cli` 0.14.0, `EQS-Sandbox/NUAGE` branche `retrofit-socle-eqs`, profil `nest` déclaré.
Author
Owner

Corrigé par cec12ef, PR #103, changeset profil-deduit-du-workspace. Les deux demandes sont faites. Fermeture à la fusion.

Mesuré sur la forme réelle de nuage, déclaration retirée

nuage déclare maintenant eqs.profile: "nest" — le contournement posé dans ce ticket — donc la déclaration court-circuite la déduction et on ne peut rien éprouver sur le dépôt tel quel. J'ai recopié ses trois manifestes dans un bac à part, sans eqs.profile. Le dépôt n'a pas été touché.

racine declare un framework ?  []
profile = "nest"

La racine ne déclare ni @nestjs/core ni next — c'est tout l'enjeu — et la déduction aboutit.

Le correctif, et votre diagnostic était juste jusqu'au bout

Depot porte désormais dependancesPartout : l'union des dependencies et devDependencies de tous les emplacements, sans le filtre startsWith('@eqs/') qui empêchait lireDepot de servir ici. profilDe s'en sert.

Un second endroit avait le même défaut, que le ticket n'avait pas relevé : la recherche des route handlers partait aussi de la racine seule (fichiers(depot.root, …, ['app', 'src/app'])). Un next-fullstack posé dans apps/web n'a rien sous app/ à la racine — il aurait été classé front. La recherche couvre maintenant la racine et les applications.

L'ordre des tests reste porteur de sens, et un test neuf le fixe explicitement : un profil nest a aussi un next, dans son apps/web en profil front. NestJS vu quelque part l'emporte sur un next vu ailleurs ; l'inverse serait faux. C'est la même asymétrie que celle que vous confirmez dans « ce qui n'est PAS un défaut ».

migrate plan dit son profil, et dit quand il ne sait pas

eqs migrate plan — nuage-equilibre
  profil : nest (déduit)
  déjà installés : @eqs/cli, @eqs/config, @eqs/errors, @eqs/telemetry, @eqs/testing, @eqs/tooling

Et le cas indéterminé :

  profil : INDÉTERMINÉ — la liste ci-dessous n’est donc pas filtrée.
    Aucun framework reconnu dans le dépôt ni dans ses applications.
    Déclarer `eqs.profile` dans le manifeste, ou forcer avec --profile.

Votre remarque était juste et vaut plus que la ligne : la liste a exactement la même forme dans les deux cas, donc rien ne distinguait « dix étapes filtrées » de « dix étapes non filtrées ». --json porte aussi profil.

Les tests portent sur des workspaces, et c'est le point du ticket

Les trois tests existants de profilDe étaient tous mono-application, framework à la racine. C'est précisément pour cela que le défaut avait survécu : la suite couvrait exactement la forme qui marche. Quatre cas neufs, tous en workspace, éprouvés en réinjectant les deux moitiés du défaut :

× classe un workspace nest par le framework de apps/api, pas par la racine (#99)
× préfère nest à front quand les deux frameworks sont dans le workspace
× trouve les route handlers dans une application, pas seulement à la racine

Le quatrième — « rend null quand aucun framework n'est déclaré » — passe dans les deux cas, ce qui est juste, et c'est pourquoi il ne figure pas dans cette liste. Un test qui ne rougit pas sur le défaut qu'il est censé couvrir n'aurait rien prouvé.

Votre contournement n'a rien à retirer

eqs.profile: "nest" reste court-circuitant, comme vous l'aviez prévu. La déclaration continue de l'emporter sur la déduction — c'est ce que observe: false dit dans la sortie, et un test l'assure.

Corrigé par `cec12ef`, PR #103, changeset `profil-deduit-du-workspace`. Les deux demandes sont faites. Fermeture à la fusion. ## Mesuré sur la forme réelle de `nuage`, déclaration retirée `nuage` déclare maintenant `eqs.profile: "nest"` — le contournement posé dans ce ticket — donc la déclaration court-circuite la déduction et on ne peut rien éprouver sur le dépôt tel quel. J'ai recopié ses trois manifestes dans un bac à part, sans `eqs.profile`. **Le dépôt n'a pas été touché.** ``` racine declare un framework ? [] profile = "nest" ``` La racine ne déclare ni `@nestjs/core` ni `next` — c'est tout l'enjeu — et la déduction aboutit. ## Le correctif, et votre diagnostic était juste jusqu'au bout `Depot` porte désormais `dependancesPartout` : l'union des `dependencies` et `devDependencies` de tous les emplacements, **sans le filtre `startsWith('@eqs/')`** qui empêchait `lireDepot` de servir ici. `profilDe` s'en sert. Un second endroit avait le même défaut, que le ticket n'avait pas relevé : la recherche des route handlers partait aussi de la racine seule (`fichiers(depot.root, …, ['app', 'src/app'])`). Un `next-fullstack` posé dans `apps/web` n'a rien sous `app/` à la racine — il aurait été classé `front`. La recherche couvre maintenant la racine **et** les applications. L'ordre des tests reste porteur de sens, et un test neuf le fixe explicitement : un profil `nest` a **aussi** un `next`, dans son `apps/web` en profil `front`. NestJS vu quelque part l'emporte sur un `next` vu ailleurs ; l'inverse serait faux. C'est la même asymétrie que celle que vous confirmez dans « ce qui n'est PAS un défaut ». ## `migrate plan` dit son profil, et dit quand il ne sait pas ``` eqs migrate plan — nuage-equilibre profil : nest (déduit) déjà installés : @eqs/cli, @eqs/config, @eqs/errors, @eqs/telemetry, @eqs/testing, @eqs/tooling ``` Et le cas indéterminé : ``` profil : INDÉTERMINÉ — la liste ci-dessous n’est donc pas filtrée. Aucun framework reconnu dans le dépôt ni dans ses applications. Déclarer `eqs.profile` dans le manifeste, ou forcer avec --profile. ``` Votre remarque était juste et vaut plus que la ligne : **la liste a exactement la même forme dans les deux cas**, donc rien ne distinguait « dix étapes filtrées » de « dix étapes non filtrées ». `--json` porte aussi `profil`. ## Les tests portent sur des workspaces, et c'est le point du ticket Les trois tests existants de `profilDe` étaient **tous** mono-application, framework à la racine. C'est précisément pour cela que le défaut avait survécu : la suite couvrait exactement la forme qui marche. Quatre cas neufs, tous en workspace, éprouvés en réinjectant les deux moitiés du défaut : ``` × classe un workspace nest par le framework de apps/api, pas par la racine (#99) × préfère nest à front quand les deux frameworks sont dans le workspace × trouve les route handlers dans une application, pas seulement à la racine ``` Le quatrième — « rend null quand aucun framework n'est déclaré » — passe dans les deux cas, ce qui est juste, et c'est pourquoi il ne figure pas dans cette liste. Un test qui ne rougit pas sur le défaut qu'il est censé couvrir n'aurait rien prouvé. ## Votre contournement n'a rien à retirer `eqs.profile: "nest"` reste court-circuitant, comme vous l'aviez prévu. La déclaration continue de l'emporter sur la déduction — c'est ce que `observe: false` dit dans la sortie, et un test l'assure.
Author
Owner

Quatrième occurrence, et celle-ci coûterait une mauvaise valeur dans le portail Azure.

eqs auth entra-manifest rend un manifeste vide sur un profil nest

$ eqs auth entra-manifest
{
  "displayName": "eqs-nuage-equilibre",
  "identifierUris": ["api://eqs-nuage-equilibre"],
  "signInAudience": "AzureADMyOrg",
  "web": { "redirectUris": [], … },
  "appRoles": [],
  "requiredResourceAccess": []
}

redirectUris est vide, et c'est le champ pour lequel on lance la commande.

La cause, dans dist/outils.js:295 :

const publicUrl = /^PUBLIC_URL=(.*)$/m.exec(lireTexte(join(root, '.env.example')))?.[1]?.trim()

Le .env.example de la racine. Dans un profil nest, l'application vit dans apps/api et son .env.example avec elle — celui de la racine porte les variables de la pile compose, pas celles du service. roles.yaml est lu au même endroit, et subira le même sort quand un satellite le posera à côté de son application.

C'est exactement profilDe et data.ownDatabase une fois de plus : la lecture s'arrête à la racine. lireDepot est pourtant appelé deux lignes plus haut, et il sait parcourir projetsDuWorkspace().

Deux détails de moins d'importance, mais dans le même manifeste

displayName et identifierUris viennent de package.json.name, pas de SERVICE_NAME :

const service = (depot.manifeste.name ?? 'service').replace(/^@[^/]+\//, '')

Pour nuage, le paquet s'appelle nuage-equilibre et le service nuage. On obtient donc api://eqs-nuage-equilibre, alors que le commentaire de baseSchema documente la convention comme api://eqs-<service> — et que c'est SERVICE_NAME qui est le nom du service pour Topologie et SigNoz. Deux noms pour une chose, et celui qui finit dans Entra n'est pas celui qu'on a déclaré.

groupMembershipClaims n'est pas dans le manifeste. Or mapProfileToUser de creerAuth lit profile['groups'] pour en déduire les rôles via roles.yaml/entra.groupes. Sans ce réglage dans l'app registration, Entra n'émet pas la revendication : groupes reste vide, rolesDesGroupes() rend [], et tout le monde se connecte avec SANS_ROLE. Le manifeste qu'on colle produit donc une application qui authentifie correctement et n'autorise personne — un mode d'échec silencieux de plus, et celui-là se diagnostique dans le portail Azure, pas dans le code.

Il manque aussi la décision GUID ou nom : Entra émet les identifiants d'objet par défaut, et entra.groupes de roles.yaml doit alors porter des GUID. Rien ne le dit, et les deux formes se ressemblent assez pour qu'on écrive la mauvaise.

Ce que je proposerais

  1. Lire PUBLIC_URL et roles.yaml dans les mêmes emplacements que lireDepot — le correctif déjà proposé plus haut, appliqué ici aussi.
  2. Prendre SERVICE_NAME plutôt que le nom du paquet, ou dire pourquoi pas.
  3. Ajouter "groupMembershipClaims": "SecurityGroup" au manifeste, et un mot dans le guide sur GUID contre nom.

Contexte

@eqs/cli 0.14.0, rétrofit de nuage (EQS-Sandbox/NUAGE, branche retrofit-socle-eqs), profil nest, étages 0 à 5 terminés. Trouvé en préparant l'app registration pour l'étage 6 — c'est-à-dire au moment précis où la commande sert.

Quatrième occurrence, et celle-ci coûterait une mauvaise valeur dans le portail Azure. ## `eqs auth entra-manifest` rend un manifeste vide sur un profil `nest` ``` $ eqs auth entra-manifest { "displayName": "eqs-nuage-equilibre", "identifierUris": ["api://eqs-nuage-equilibre"], "signInAudience": "AzureADMyOrg", "web": { "redirectUris": [], … }, "appRoles": [], "requiredResourceAccess": [] } ``` `redirectUris` est **vide**, et c'est le champ pour lequel on lance la commande. La cause, dans `dist/outils.js:295` : ```js const publicUrl = /^PUBLIC_URL=(.*)$/m.exec(lireTexte(join(root, '.env.example')))?.[1]?.trim() ``` Le `.env.example` de la **racine**. Dans un profil `nest`, l'application vit dans `apps/api` et son `.env.example` avec elle — celui de la racine porte les variables de la pile compose, pas celles du service. `roles.yaml` est lu au même endroit, et subira le même sort quand un satellite le posera à côté de son application. C'est exactement `profilDe` et `data.ownDatabase` une fois de plus : la lecture s'arrête à la racine. `lireDepot` est pourtant appelé deux lignes plus haut, et il sait parcourir `projetsDuWorkspace()`. ## Deux détails de moins d'importance, mais dans le même manifeste **`displayName` et `identifierUris` viennent de `package.json.name`**, pas de `SERVICE_NAME` : ```js const service = (depot.manifeste.name ?? 'service').replace(/^@[^/]+\//, '') ``` Pour `nuage`, le paquet s'appelle `nuage-equilibre` et le service `nuage`. On obtient donc `api://eqs-nuage-equilibre`, alors que le commentaire de `baseSchema` documente la convention comme `api://eqs-<service>` — et que c'est `SERVICE_NAME` qui est le nom du service pour Topologie et SigNoz. Deux noms pour une chose, et celui qui finit dans Entra n'est pas celui qu'on a déclaré. **`groupMembershipClaims` n'est pas dans le manifeste.** Or `mapProfileToUser` de `creerAuth` lit `profile['groups']` pour en déduire les rôles via `roles.yaml/entra.groupes`. Sans ce réglage dans l'app registration, Entra **n'émet pas** la revendication : `groupes` reste vide, `rolesDesGroupes()` rend `[]`, et tout le monde se connecte avec `SANS_ROLE`. Le manifeste qu'on colle produit donc une application qui authentifie correctement et n'autorise personne — un mode d'échec silencieux de plus, et celui-là se diagnostique dans le portail Azure, pas dans le code. Il manque aussi la décision **GUID ou nom** : Entra émet les identifiants d'objet par défaut, et `entra.groupes` de `roles.yaml` doit alors porter des GUID. Rien ne le dit, et les deux formes se ressemblent assez pour qu'on écrive la mauvaise. ## Ce que je proposerais 1. Lire `PUBLIC_URL` et `roles.yaml` dans les mêmes `emplacements` que `lireDepot` — le correctif déjà proposé plus haut, appliqué ici aussi. 2. Prendre `SERVICE_NAME` plutôt que le nom du paquet, ou dire pourquoi pas. 3. Ajouter `"groupMembershipClaims": "SecurityGroup"` au manifeste, et un mot dans le guide sur GUID contre nom. ## Contexte `@eqs/cli` 0.14.0, rétrofit de `nuage` (`EQS-Sandbox/NUAGE`, branche `retrofit-socle-eqs`), profil `nest`, étages 0 à 5 terminés. Trouvé en préparant l'app registration pour l'étage 6 — c'est-à-dire au moment précis où la commande sert.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Infrastructure/eqs-platform#99
No description provided.