From 7ee6882eb887064c83ba48be57442bdf564aa09c Mon Sep 17 00:00:00 2001 From: Claude Code DTP Worker Date: Fri, 31 Jul 2026 19:06:35 +0000 Subject: [PATCH] =?UTF-8?q?[DTP-Worker]=20Sprint=208=20=C2=B7=20buffer=20?= =?UTF-8?q?=C2=B7=20Domaine=20Portails=20(2e=20surface)=20:=20le=20CATALOG?= =?UTF-8?q?UE=20des=20DocTypes=20`custom`=20=C3=A0=20CR=C3=89ER=20avant=20?= =?UTF-8?q?import=20(=C2=AB=20`CONFOTUR=20Application`,=20`Faisabilit?= =?UTF-8?q?=C3=A9`,=20`Publiciste=20Log`=20=C2=BB)=20=C3=A9tait=20=C3=A9nu?= =?UTF-8?q?m=C3=A9r=C3=A9=20=C3=80=20LA=20MAIN=20dans=20le=20=C2=A7=C2=AB?= =?UTF-8?q?=20Hand-off=20=E2=86=92=20agent=20ERPNext=20Backend=20=C2=BB=20?= =?UTF-8?q?du=20README=20`frontend/portails`=20sans=20AUCUN=20gate=20d'IDE?= =?UTF-8?q?NTIT=C3=89.=20Le=20bloc=20portails=20existant=20ne=20gate=20que?= =?UTF-8?q?=20les=20COMPTES=20par=20workspace=20(cartes/liens/r=C3=B4les,?= =?UTF-8?q?=20=C3=972=20docs)=20=E2=80=94=20jamais=20ce=20catalogue=20de?= =?UTF-8?q?=20hand-off.=20Ce=20catalogue=20est=20DATA-DERIVED=20:=20`docty?= =?UTF-8?q?pes=5Fcustom=5Fa=5Fcreer`=20de=20`frontend/portails/out/MANIFES?= =?UTF-8?q?T.json`=20(byte-gat=C3=A9=20par=20check=5Fartifacts),=20lui-m?= =?UTF-8?q?=C3=AAme=20l'UNION=20des=20`doctypes=5Fcustom`=20par=20workspac?= =?UTF-8?q?e=20(flag=20`custom`=20du=20contrat=20rbac=5F50=5Froles.json,?= =?UTF-8?q?=20pour=20les=20seuls=20DocTypes=20C=C3=82BL=C3=89S=20dans=20un?= =?UTF-8?q?=20portail).=20C'est=20un=20ensemble=20PROPRE=20au=20module=20(?= =?UTF-8?q?sous-ensemble=20de=20celui=20de=20rbac/fixtures=5Fgen=20:=20`AP?= =?UTF-8?q?I=20Access`,=20custom=20mais=20non=20c=C3=A2bl=C3=A9,=20en=20es?= =?UTF-8?q?t=20ABSENT).=20PI=C3=88GE=20:=20check=5Fartifacts=20ne=20prouve?= =?UTF-8?q?=20QUE=20MANIFEST=3D=3Dbuild=20(byte-for-byte)=20et=20le=20buil?= =?UTF-8?q?d=20RECOMPUTE=20l'union=20=E2=80=94=20donc=20AJOUTER=20une=20ca?= =?UTF-8?q?rte=20visant=20un=20nouveau=20DocType=20custom=20au=20contrat?= =?UTF-8?q?=20(le=20MANIFEST=20reb=C3=A2tit=20le=20catalogue)=20=C2=B7=20e?= =?UTF-8?q?n=20RENOMMER=20un=20=C2=B7=20en=20RETIRER=20un=20du=20c=C3=A2bl?= =?UTF-8?q?age=20laisse=20la=20consigne=20de=20hand-off=20p=C3=A9rim=C3=A9?= =?UTF-8?q?e=20pendant=20que=20l'artefact=20dit=20autre=20chose=20:=20l'ag?= =?UTF-8?q?ent=20Backend=20cr=C3=A9erait=20le=20mauvais=20jeu=20de=20DocTy?= =?UTF-8?q?pes=20(liens=20pointant=20dans=20le=20vide=20=E2=80=94=20le=20r?= =?UTF-8?q?isque=20m=C3=AAme=20que=20la=20consigne=20veut=20pr=C3=A9venir)?= =?UTF-8?q?=20=E2=80=94=20=C2=AB=20vert=20trompeur=20=C2=BB=20qu'aucune=20?= =?UTF-8?q?suite=20tests/=20(qui=20teste=20des=20FONCTIONS=20de=20mapping,?= =?UTF-8?q?=20pas=20la=20prose=20de=20hand-off)=20n'attrape=20=E2=86=92=20?= =?UTF-8?q?nouveau=20bloc=20recomputant=20le=20catalogue=20depuis=20MANIFE?= =?UTF-8?q?ST=20(z=C3=A9ro=20duplication=20du=20contrat=20du=20g=C3=A9n?= =?UTF-8?q?=C3=A9rateur=20#6=20=C2=B7=20m=C3=AAme=20patron=20que=20le=20ca?= =?UTF-8?q?talogue=20custom=20de=20rbac/fixtures=5Fgen)=20et=20exigeant=20?= =?UTF-8?q?que=20la=20prose=20l'=C3=A9num=C3=A8re=20EXACTEMENT=20(absent?= =?UTF-8?q?=20ET=20en=20trop=20via=20set-diff=20=C2=B7=20accents/casse=20n?= =?UTF-8?q?ormalis=C3=A9s=20via=20unicodedata).=20Coh=C3=A9rences=20crois?= =?UTF-8?q?=C3=A9es=20en=20bonus=20(mordent=20un=20MANIFEST=20INTERNEMENT?= =?UTF-8?q?=20incoh=C3=A9rent)=20:=20catalogue=20NON=20VIDE,=20sans=20doub?= =?UTF-8?q?lon=20et=20TRI=C3=89=20(byte-d=C3=A9terminisme)=20=C2=B7=20?= =?UTF-8?q?=C3=A9gal=20=C3=A0=20l'UNION=20des=20`doctypes=5Fcustom`=20par?= =?UTF-8?q?=20workspace=20(pas=20de=20DocType=20custom=20c=C3=A2bl=C3=A9?= =?UTF-8?q?=20mais=20oubli=C3=A9=20du=20hand-off,=20ni=20l'inverse).=20Un?= =?UTF-8?q?=20claim=20absent=20=C3=A9choue=20AUSSI.=205=20morsures=20v?= =?UTF-8?q?=C3=A9rifi=C3=A9es=20:=20prose=20renomme=20Faisabilit=C3=A9?= =?UTF-8?q?=E2=86=92Faisabilite2=20(absents=3D[faisabilite]=20en=20trop=3D?= =?UTF-8?q?[faisabilite2])=20=C2=B7=20prose=20retire=20Publiciste=20Log=20?= =?UTF-8?q?(sous-ensemble)=20=C2=B7=20=C3=A9num=C3=A9ration=20supprim?= =?UTF-8?q?=C3=A9e=20(INTROUVABLE)=20=C2=B7=20MANIFEST=20retire=20du=20cat?= =?UTF-8?q?alogue=20un=20DocType=20encore=20c=C3=A2bl=C3=A9=20(incoh=C3=A9?= =?UTF-8?q?rence=20interne=20:=20c=C3=A2bl=C3=A9s=20hors=20catalogue=3D[Pu?= =?UTF-8?q?bliciste=20Log]=20+=20prose=20en=20trop)=20=C2=B7=20catalogue?= =?UTF-8?q?=20MANIFEST=20non=20tri=C3=A9=20(byte-d=C3=A9terminisme)=20;=20?= =?UTF-8?q?restaur=C3=A9=20=3D=20green=20:=20[CONFOTUR=20Application,Faisa?= =?UTF-8?q?bilit=C3=A9,Publiciste=20Log]=20=3D=3D=20MANIFEST=20=3D=3D=20un?= =?UTF-8?q?ion=20des=20workspaces=20=C2=B7=20exit=200.=20=C3=89tat=20coura?= =?UTF-8?q?nt=20:=20aucun=20catalogue=20p=C3=A9rim=C3=A9=20(anti-invention?= =?UTF-8?q?=20#6,=20rien=20=C3=A0=20r=C3=A9=C3=A9crire)=20=E2=80=94=20le?= =?UTF-8?q?=20d=C3=A9faut=20est=20la=20surface=20ungated.=20ci/README.md?= =?UTF-8?q?=20(table=20+=20d=C3=A9tail=20=C2=AB=202e=20surface=20Portails?= =?UTF-8?q?=20=C2=BB)=20mis=20=C3=A0=20jour=20=C2=B7=207=20gates=20re-vert?= =?UTF-8?q?s.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- 05_activity_log/2026-07-31.md | 38 ++++++++++++++++ ci/README.md | 23 +++++++++- ci/check_readme_claims.sh | 82 +++++++++++++++++++++++++++++++++++ 3 files changed, 142 insertions(+), 1 deletion(-) diff --git a/05_activity_log/2026-07-31.md b/05_activity_log/2026-07-31.md index 50d45f1..f2054ad 100644 --- a/05_activity_log/2026-07-31.md +++ b/05_activity_log/2026-07-31.md @@ -2177,3 +2177,41 @@ verrouillés) · `guard_constraints` · `validate_json`. signer contrat}` == workflow.json). - État courant : aucune action périmée (anti-invention #6) — le défaut est la surface ungated. `ci/README.md` (table + détail) mis à jour · **7 gates re-verts**. + +## Sprint 8 · buffer · Portails (2e surface) · catalogue DocTypes `custom` à créer +- **Constat** : le README `frontend/portails` énumère À LA MAIN, dans le §« Hand-off + → agent ERPNext Backend », les **DocTypes `custom` à créer avant import** : + `CONFOTUR Application`, `Faisabilité`, `Publiciste Log`. Ce catalogue est + DATA-DERIVED == `doctypes_custom_a_creer` d'`out/MANIFEST.json` (byte-gaté par + `check_artifacts`), lui-même l'UNION des `doctypes_custom` par workspace (flag + `custom` du contrat RBAC, pour les seuls DocTypes CÂBLÉS dans un portail). C'est + un ensemble PROPRE au module (sous-ensemble de celui de `rbac/fixtures_gen` : + `API Access`, custom mais non câblé, en est ABSENT). Le bloc portails existant ne + gatait que les COMPTES par workspace (cartes/liens/rôles) — jamais ce catalogue. +- **Piège** : `check_artifacts` ne prouve que MANIFEST==build (byte-for-byte) et le + build RECOMPUTE l'union → AJOUTER une carte visant un nouveau DocType custom · + en RENOMMER un · en RETIRER un du câblage laisse la consigne de hand-off périmée + pendant que l'artefact dit autre chose. L'agent Backend créerait le mauvais jeu + de DocTypes → liens pointant dans le vide (le risque même que la consigne veut + prévenir) — « vert trompeur » qu'aucune suite `tests/` (qui teste des FONCTIONS + de mapping, pas la prose de hand-off) n'attrape. +- **Gate ajouté** (`ci/check_readme_claims.sh`) : recompute le catalogue depuis + `MANIFEST.doctypes_custom_a_creer` (zéro duplication du contrat du générateur #6) + et exige que la prose l'énumère EXACTEMENT (absent ET en trop via set-diff · + accents/casse normalisés via `unicodedata`). Cohérences croisées en bonus + (mordent un MANIFEST INTERNEMENT incohérent) : catalogue NON VIDE, sans doublon + et TRIÉ (byte-déterminisme) · égal à l'UNION des `doctypes_custom` par workspace + (pas de DocType custom câblé mais oublié du hand-off, ni l'inverse). Un claim + absent échoue AUSSI. +- **5 morsures vérifiées** : prose renomme `Faisabilité → Faisabilite2` + (absents=[faisabilite] en trop=[faisabilite2]) · prose retire `Publiciste Log` + (sous-ensemble) · énumération supprimée = INTROUVABLE · MANIFEST retire du + catalogue un DocType encore câblé (câblés hors catalogue=[Publiciste Log]) · + catalogue MANIFEST non trié (byte-déterminisme) ; restauré = green + (`[CONFOTUR Application, Faisabilité, Publiciste Log]` == MANIFEST == union). +- État courant : aucun catalogue périmé (anti-invention #6, rien à réécrire) — le + défaut est la surface ungated. `ci/README.md` (table + détail) mis à jour · + **7 gates re-verts** (`check_readme_claims` · `check_artifacts` · `check_docs` · + `check_ci_integrity` · `check_regression` · `guard_constraints` · `validate_json`). +- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo). +- **Auto-score 4Big** : 96/100. diff --git a/ci/README.md b/ci/README.md index 5192802..16985a5 100644 --- a/ci/README.md +++ b/ci/README.md @@ -20,7 +20,7 @@ et manuellement (`workflow_dispatch`). Jobs statiques (+ une suite `unittest` pa | `check-artifacts` | `ci/check_artifacts.sh` | Reproductibilité : chaque `out/*.json` versionné == build frais | ✅ oui | | `check-regression` | `ci/check_regression.sh` | Fraîcheur : `qa/regression/out/regression_run.json` (`run`) == run frais + verdict PASS | ✅ oui | | `check-ci-integrity` | `ci/check_ci_integrity.sh` | Intégrité du câblage : `gate.needs` == tous les jobs non-manuels · chaque `ci/*.sh` câblé au gate | ✅ oui | -| `check-readme-claims` | `ci/check_readme_claims.sh` | Intégrité des chiffres des docs d'entrée : chaque nombre de `README.md` « État courant », de la fiche QA « Verdict agrégé » **et** de la colonne « Tests » par suite des tables de fiches **et** des agrégats en prose (Total CRM / RBAC / e-CF) **et** des comptes par workspace des portails (cartes/liens/rôles, ×2 docs) **et** des comptes RBAC des 3 volets (table Nb rôles/profil, ligne agrégée apply_plan, ventilation userperm) **et** des comptes de mots-clés SEO (total + fr/en/es, ×2 docs) **et** du pipeline vente CRM (états/transitions, ×2 docs + énumération des transitions à séparation des pouvoirs `{action | allow_self_approval=0}` recomputée de `workflow.json`) **et** de l'app mobile Expo (onglets/rôles/langues/identifiants a_confirmer + énumération ordonnée des onglets recomputée de `role_navigation`, bijection onglet⇔portail) **et** du DocType CONFOTUR (champs/données/sections/rôles/évènements de dépôt, ×2 docs) **et** de l'audit 5D (contrôles/dimensions ×2 docs + ventilation verdict pass/fail/à-confirmer + énumération des open items `control→dimension→propriétaire` recomputée de `audit_report.open_items`) **et** du run-book de déploiement VPS (phases ×2 dans la fiche devops + confirmations préalables count & énumération dans le README) **et** du run-sheet de démo (nombre de modules cités dans le diagramme du README) **et** du montage Chat OTOIA par portail (`Custom Block`/configs runtime/portails, tous = 5 aujourd'hui, ×2 docs) **et** du module de recette `qa/acceptance` (couple livrables de sprint + métriques succès MVP, ×2 docs de module au-delà du README d'entrée) **et** du barème de l'auditeur 4Big (nombre de critères en prose + poids par critère de la table + « generator (N critères) », recomputés de `quality_report.criteria`) **et** de la carte de renormalisation par archétype de l'auditeur 4Big (critères EXCLUS de `data_room`/`parser`/`contract`, recomputés des `checks` par archétype de `quality_report.modules`) **et** du contrat de format de l'e-NCF fiscal (`E + tipoeCF(2) + secuencia(10) = 13` : préfixe/longueurs des segments/longueur totale, ×3 README + fiche `erpnext_backend`, recomputés du bloc `e_ncf` d'`ecf_plan.json` + cohérences croisées `1+tipo+seq==longueur` & `|regex digits|==tipo+seq`) **et** de la cross-cohérence du barème de commissions `crm/commissions` (états brouillon exclus recomputés de `{doc_status=0}` de `workflow.json` + champs de base recomputés de `{base_field}` de `commission_plan.json` + invariant 3 « commissionne seulement des états soumis ») **et** du catalogue RBAC `rbac/fixtures_gen` (DocTypes `custom` à créer énumérés == `MANIFEST.custom_doctypes_a_confirmer` + séparation des pouvoirs : le rôle unique porteur de `set_user_permissions` == `custom_docperm.json`) **et** de la composition du graphe schema.org SEO (les `@type` du `@graph` — `Organization` racine + une `Residence` par projet — recomputés de `seo_schema_org.json` + bijection listing⇔projet & absence d'`offers`) **et** de la carte hreflang SEO (les langues de contenu `alternate` `FR/EN/ES` + `x-default` recomputées de `seo_hreflang.json` + cohérences croisées : carte uniforme sur toutes les pages · `x-default` présent partout · langue par défaut ∈ langues de contenu · `x-default`.href == `canonical` == href de la langue par défaut) **et** du catalogue de projets du DocType `crm/dossier_vente` (le Select `projet` — les 9 projets `P01..P09` avec libellés — **ancré sur** la section « ## Projets » de `CLAUDE.md`, recomputé de CLAUDE.md : ensemble + ordre == options du DocType byte-gaté, plage `P01..P09` + mot d'ancrage dans le README, catalogue contigu/sans-doublon en bonus) == artefact cité (#6) | ✅ oui | +| `check-readme-claims` | `ci/check_readme_claims.sh` | Intégrité des chiffres des docs d'entrée : chaque nombre de `README.md` « État courant », de la fiche QA « Verdict agrégé » **et** de la colonne « Tests » par suite des tables de fiches **et** des agrégats en prose (Total CRM / RBAC / e-CF) **et** des comptes par workspace des portails (cartes/liens/rôles, ×2 docs) **et** des comptes RBAC des 3 volets (table Nb rôles/profil, ligne agrégée apply_plan, ventilation userperm) **et** des comptes de mots-clés SEO (total + fr/en/es, ×2 docs) **et** du pipeline vente CRM (états/transitions, ×2 docs + énumération des transitions à séparation des pouvoirs `{action | allow_self_approval=0}` recomputée de `workflow.json`) **et** de l'app mobile Expo (onglets/rôles/langues/identifiants a_confirmer + énumération ordonnée des onglets recomputée de `role_navigation`, bijection onglet⇔portail) **et** du DocType CONFOTUR (champs/données/sections/rôles/évènements de dépôt, ×2 docs) **et** de l'audit 5D (contrôles/dimensions ×2 docs + ventilation verdict pass/fail/à-confirmer + énumération des open items `control→dimension→propriétaire` recomputée de `audit_report.open_items`) **et** du run-book de déploiement VPS (phases ×2 dans la fiche devops + confirmations préalables count & énumération dans le README) **et** du run-sheet de démo (nombre de modules cités dans le diagramme du README) **et** du montage Chat OTOIA par portail (`Custom Block`/configs runtime/portails, tous = 5 aujourd'hui, ×2 docs) **et** du module de recette `qa/acceptance` (couple livrables de sprint + métriques succès MVP, ×2 docs de module au-delà du README d'entrée) **et** du barème de l'auditeur 4Big (nombre de critères en prose + poids par critère de la table + « generator (N critères) », recomputés de `quality_report.criteria`) **et** de la carte de renormalisation par archétype de l'auditeur 4Big (critères EXCLUS de `data_room`/`parser`/`contract`, recomputés des `checks` par archétype de `quality_report.modules`) **et** du contrat de format de l'e-NCF fiscal (`E + tipoeCF(2) + secuencia(10) = 13` : préfixe/longueurs des segments/longueur totale, ×3 README + fiche `erpnext_backend`, recomputés du bloc `e_ncf` d'`ecf_plan.json` + cohérences croisées `1+tipo+seq==longueur` & `|regex digits|==tipo+seq`) **et** de la cross-cohérence du barème de commissions `crm/commissions` (états brouillon exclus recomputés de `{doc_status=0}` de `workflow.json` + champs de base recomputés de `{base_field}` de `commission_plan.json` + invariant 3 « commissionne seulement des états soumis ») **et** du catalogue RBAC `rbac/fixtures_gen` (DocTypes `custom` à créer énumérés == `MANIFEST.custom_doctypes_a_confirmer` + séparation des pouvoirs : le rôle unique porteur de `set_user_permissions` == `custom_docperm.json`) **et** de la composition du graphe schema.org SEO (les `@type` du `@graph` — `Organization` racine + une `Residence` par projet — recomputés de `seo_schema_org.json` + bijection listing⇔projet & absence d'`offers`) **et** de la carte hreflang SEO (les langues de contenu `alternate` `FR/EN/ES` + `x-default` recomputées de `seo_hreflang.json` + cohérences croisées : carte uniforme sur toutes les pages · `x-default` présent partout · langue par défaut ∈ langues de contenu · `x-default`.href == `canonical` == href de la langue par défaut) **et** du catalogue de projets du DocType `crm/dossier_vente` (le Select `projet` — les 9 projets `P01..P09` avec libellés — **ancré sur** la section « ## Projets » de `CLAUDE.md`, recomputé de CLAUDE.md : ensemble + ordre == options du DocType byte-gaté, plage `P01..P09` + mot d'ancrage dans le README, catalogue contigu/sans-doublon en bonus) **et** du catalogue des DocTypes `custom` à créer du portail `frontend/portails` (2e surface : le §hand-off VPS énumère `CONFOTUR Application`/`Faisabilité`/`Publiciste Log` == `MANIFEST.doctypes_custom_a_creer` recomputé, set-diff normalisé + cohérences croisées : catalogue trié/sans-doublon/non-vide · égal à l'UNION des `doctypes_custom` par workspace) == artefact cité (#6) | ✅ oui | | `gate` | — | Agrégat vert = gate qualité 4Big franchi | ✅ oui | Aucune dépendance réseau/marketplace hors `actions/checkout`. Tout tourne avec @@ -497,6 +497,27 @@ Faisabilite2`) · sous-ensemble (retrait `API Access`) · rôle nommé faux (`RB dans l'artefact (singleton cassé) · catalogue MANIFEST non trié (byte-déterminisme) ; restauré = vert. +Même classe enfin sur une **2ᵉ surface du README des portails** (`frontend/portails`) — +le §« Hand-off → agent ERPNext Backend » énumère **à la main** les DocTypes `custom` à +créer avant import « (`CONFOTUR Application`, `Faisabilité`, `Publiciste Log`) », un +catalogue DATA-DERIVED == `doctypes_custom_a_creer` d'`out/MANIFEST.json` (l'UNION des +`doctypes_custom` par workspace, eux-mêmes lus du flag `custom` du contrat RBAC pour les +seuls DocTypes **câblés** dans un portail — c'est donc un ensemble PROPRE au module, +sous-ensemble de celui de `rbac/fixtures_gen` : `API Access`, custom mais non câblé, en +est ABSENT). Le bloc portails ci-dessus ne gate que les **comptes** par workspace +(cartes/liens/rôles) — jamais ce catalogue de hand-off. AJOUTER une carte visant un +nouveau DocType custom au contrat, en RENOMMER un ou en RETIRER un du câblage laisse la +consigne périmée (l'agent Backend créerait le mauvais jeu de DocTypes → liens pointant +dans le vide, le risque même que la consigne prévient) — dérive qu'aucune suite `tests/` +n'attrape. On RECOMPUTE le catalogue depuis `MANIFEST` et on exige que la prose l'énumère +**exactement** (set-diff · accents/casse normalisés). Cohérences croisées en bonus : +catalogue **non vide, sans doublon et trié** (byte-déterminisme) · **égal à l'UNION** des +`doctypes_custom` par workspace (pas de DocType custom câblé mais oublié du hand-off, ni +l'inverse). 5 morsures vérifiées : prose renomme `Faisabilité → Faisabilite2` · prose +retire `Publiciste Log` (sous-ensemble) · énumération supprimée (INTROUVABLE) · MANIFEST +retire du catalogue un DocType encore câblé (incohérence interne : câblé hors catalogue) · +catalogue MANIFEST non trié (byte-déterminisme) ; restauré = vert. + Un claim **absent** échoue aussi (la dérive de formulation qui ferait disparaître un chiffre est elle-même une régression de traçabilité). stdlib pur (bash/git/python3), zéro réseau. diff --git a/ci/check_readme_claims.sh b/ci/check_readme_claims.sh index 39cf62e..be8a973 100755 --- a/ci/check_readme_claims.sh +++ b/ci/check_readme_claims.sh @@ -2080,6 +2080,88 @@ if cat is not None: bad(f"CRM projets · README dit {mp.group(1)}..{mp.group(2)} MAIS catalogue " f"CLAUDE.md va de {cat_codes[0]} à {cat_codes[-1]} → régénérer le README") +# ============================================================================ +# Portails frontend (2e surface) · CATALOGUE des DocTypes `custom` à CRÉER avant +# import — la consigne de hand-off VPS énumérée À LA MAIN, sans gate d'IDENTITÉ. +# ---------------------------------------------------------------------------- +# Le bloc portails ci-dessus recompute les COMPTES par workspace (cartes/liens/ +# rôles) et la table portail⇔workspace. Restait, dans le MÊME README, une surface +# data-derived DISTINCTE et jamais gatée : le §« Hand-off → agent ERPNext Backend » +# item 2 énumère « DocTypes custom à créer avant import : `CONFOTUR Application`, +# `Faisabilité`, `Publiciste Log` (… le reste est natif v15) ». Ce catalogue est +# DATA-DERIVED : c'est out/MANIFEST.json `doctypes_custom_a_creer` (byte-gaté par +# check_artifacts) = l'UNION des `doctypes_custom` par workspace, eux-mêmes lus du +# flag `custom` du contrat rbac_50_roles.json pour les seuls DocTypes visés par une +# carte/un lien du portail. C'est un ensemble PROPRE au module portails (sous- +# ensemble de celui de rbac/fixtures_gen : ici seuls comptent les DocTypes custom +# effectivement CÂBLÉS dans un workspace — API Access, custom mais non câblé, en est +# ABSENT). check_artifacts ne prouve QUE MANIFEST==build (byte-for-byte), et le +# build RECOMPUTE l'union — donc AJOUTER une carte visant un nouveau DocType custom +# au contrat (le MANIFEST rebâtit le catalogue) · en RENOMMER un · en RETIRER un du +# câblage laisse la prose de hand-off périmée pendant que l'artefact dit autre +# chose : l'agent Backend créerait le mauvais jeu de DocTypes (liens pointant dans +# le vide — le risque même que la consigne veut prévenir) — « vert trompeur » +# qu'aucune suite tests/ (qui teste des FONCTIONS de mapping, pas la prose de hand- +# off) n'attrape. Même patron que le catalogue custom de rbac/fixtures_gen. On +# RECOMPUTE le catalogue depuis MANIFEST (zéro duplication du contrat du générateur +# #6) et on exige que la prose l'énumère EXACTEMENT (absent ET en trop via set-diff · +# accents/casse normalisés). Cohérences croisées en bonus (mordent un MANIFEST +# INTERNEMENT incohérent) : catalogue NON VIDE, sans doublon et TRIÉ (byte- +# déterminisme) · top-level == UNION des `doctypes_custom` par workspace (pas de +# DocType custom câblé mais oublié du catalogue de hand-off, ni l'inverse). Un claim +# absent échoue AUSSI (traçabilité). +# ============================================================================ +PC_RD = f"{D}/frontend/portails/README.md" +def _pc_acc(s): + return "".join(c for c in unicodedata.normalize("NFKD", s) + if unicodedata.category(c) != "Mn").strip().lower() +try: + pc_man = load("frontend/portails/out/MANIFEST.json") + pc_cat = pc_man["doctypes_custom_a_creer"] + pc_union = sorted({d for w in pc_man["workspaces"] + for d in w.get("doctypes_custom", [])}) +except (OSError, KeyError, ValueError, TypeError) as e: + bad(f"Portails catalogue · MANIFEST illisible/inattendu : {e}"); pc_cat = None +if pc_cat is not None: + # bonus : le catalogue est un vrai ENSEMBLE trié non vide (byte-déterminisme) + if not pc_cat: + bad("Portails catalogue · doctypes_custom_a_creer VIDE → aucun DocType custom " + "câblé dans un workspace ? (contrat/spec suspect)") + elif pc_cat != sorted(set(pc_cat)): + bad(f"Portails catalogue · doctypes_custom_a_creer {pc_cat} non trié/dédupliqué " + f"→ générateur non déterministe (attendu {sorted(set(pc_cat))})") + else: + good(f"Portails catalogue · custom à créer {pc_cat} — ensemble trié, sans doublon") + # bonus : cohérence interne — catalogue == union des doctypes_custom par workspace + if sorted(set(pc_cat)) != pc_union: + miss = sorted(set(pc_union) - set(pc_cat)); extra = sorted(set(pc_cat) - set(pc_union)) + detail = (f" · câblés hors catalogue={miss}" if miss else "") + \ + (f" · au catalogue mais non câblés={extra}" if extra else "") + bad(f"Portails catalogue · doctypes_custom_a_creer {sorted(pc_cat)} ≠ UNION des " + f"doctypes_custom par workspace {pc_union}{detail} → MANIFEST incohérent") + else: + good(f"Portails catalogue · catalogue == union des doctypes_custom par workspace " + f"({pc_union})") + pc_rd = read_norm(PC_RD) + if pc_rd is not None: + # README §Hand-off item 2 — énumération du catalogue custom avant « (issus » + mp = re.search(r"custom à créer\*\* avant import[^:]*?: (.+?)\(issus", pc_rd) + if mp is None: + bad(f"Portails catalogue · {PC_RD} — énumération « DocTypes custom à créer … » " + f"INTROUVABLE (attendu {sorted(pc_cat)})") + else: + got = {_pc_acc(t) for t in re.findall(r"`([^`]+)`", mp.group(1))} + exp = {_pc_acc(x) for x in pc_cat} + if got == exp: + good(f"Portails catalogue · {PC_RD} — hand-off {sorted(pc_cat)} == " + f"MANIFEST.doctypes_custom_a_creer") + else: + miss = sorted(exp - got); extra = sorted(got - exp) + detail = (f" · absents={miss}" if miss else "") + \ + (f" · en trop={extra}" if extra else "") + bad(f"Portails catalogue · {PC_RD} — hand-off énumère {sorted(got)} MAIS " + f"MANIFEST {sorted(exp)}{detail} → régénérer le README") + sys.exit(1 if FAIL else 0) PY rc=$?