From 152a1388617a2e7b4a878a6ca4832e83304caa51 Mon Sep 17 00:00:00 2001 From: Claude Code DTP Worker Date: Fri, 31 Jul 2026 10:05:43 +0000 Subject: [PATCH] =?UTF-8?q?[DTP-Worker]=20Sprint=208=20=C2=B7=20buffer=20L?= =?UTF-8?q?75=20=C2=B7=20Domaine=20mobile=20:=20la=20ligne=20r=C3=A9cap=20?= =?UTF-8?q?de=20l'app=20Expo=20(=C2=AB=205=20onglets=20=C2=B7=2044=20r?= =?UTF-8?q?=C3=B4les=20couverts=20=C2=B7=203=20langues=20=C2=B7=2013=20ide?= =?UTF-8?q?ntifiants=20a=5Fconfirmer=20=C2=BB)=20=C3=A9tait=20transcrite?= =?UTF-8?q?=20=C3=80=20LA=20MAIN=20dans=20le=20README=20du=20module=20mobi?= =?UTF-8?q?le/app=5Fconfig=20sans=20AUCUN=20gate=20=E2=80=94=20ajouter=20u?= =?UTF-8?q?n=20r=C3=B4le/portail=20au=20contrat=20RBAC,=20une=20langue=20a?= =?UTF-8?q?u=20site=20ou=20un=20credential=20store=20ferait=20d=C3=A9river?= =?UTF-8?q?=20le=20doc=20en=20silence=20pendant=20que=20mobile/app=5Fconfi?= =?UTF-8?q?g/out/MANIFEST.json=20(byte-gat=C3=A9=20par=20check=5Fartifacts?= =?UTF-8?q?,=20counts=20recompt=C3=A9s=20de=20rbac=5F50=5Froles=20+=20seo?= =?UTF-8?q?=5Fspec=20+=20liste=20a=5Fconfirmer)=20dit=20autre=20chose=20?= =?UTF-8?q?=E2=86=92=20nouveau=20bloc=20dans=20check=5Freadme=5Fclaims=20r?= =?UTF-8?q?ecomputant=20le=20quadruplet=20(onglets=20+=20roles=5Fcouverts?= =?UTF-8?q?=20+=20langues=20+=20identifiants=5Fa=5Fconfirmer)=20depuis=20M?= =?UTF-8?q?ANIFEST.counts=20et=20exigeant=20l'=C3=A9galit=C3=A9=20(5=20mor?= =?UTF-8?q?sures=20v=C3=A9rifi=C3=A9es=20:=20d=C3=A9rive=20de=20chacun=20d?= =?UTF-8?q?es=204=20volets=20capt=C3=A9e=20l=C3=A0=20o=C3=B9=20un=20seul?= =?UTF-8?q?=20nombre=20resterait=20aveugle=20=C2=B7=20claim=20disparu=20?= =?UTF-8?q?=3D=20red)?= 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 | 55 +++++++++++++++++++++++++++++++++++ ci/README.md | 13 ++++++++- ci/check_readme_claims.sh | 50 +++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+), 1 deletion(-) diff --git a/05_activity_log/2026-07-31.md b/05_activity_log/2026-07-31.md index c18c025..9278e89 100644 --- a/05_activity_log/2026-07-31.md +++ b/05_activity_log/2026-07-31.md @@ -1176,3 +1176,58 @@ les MANIFEST byte-gatés ; les comptes des READMEs **découlent** du contrat RBA **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo). **Auto-score 4Big** : 96/100. + +--- + +## Sprint 8 · buffer L75 · Gate claims mobile — récap app Expo non gatée (onglets/rôles/langues/identifiants) + +### Diagnostic + +- **Symptôme** : le générateur `mobile/app_config` émet `out/MANIFEST.json` dont + `counts` porte **4 nombres** recomptés à chaque build depuis les sources faisant + autorité — `rbac_50_roles.json` (portails métier + rôles → `onglets`, + `roles_couverts`), `seo/seo_spec.json` (`langues`) et la liste `a_confirmer` + (credentials EAS/App Store #32/Play Store jamais fabriqués · #6/#8 → + `identifiants_a_confirmer`). Le `out/` est byte-gaté par `check_artifacts` + (build frais == commité) ⇒ artefact faisant autorité. +- **Contradiction latente** : la ligne récap du README du module + (`05_deliverables_mvp/mobile/app_config/README.md` L66-67 — « **5 onglets** · + **44 rôles** couverts · **3 langues** · **13 identifiants `a_confirmer`** ») + recopiait ce quadruplet **à la main**, sans **AUCUN** gate. Ajouter un + rôle/portail au contrat RBAC, une langue au site, ou un credential store ferait + dériver le doc en silence pendant que le MANIFEST (byte-gaté) dit autre chose — + même classe de « vert trompeur » que les mots-clés SEO (total + fr/en/es), les + triplets par workspace des portails, ou le pipeline vente CRM. +- **État au démarrage** : les 4 nombres étaient **justes** (5/44/3/13 == MANIFEST) + — le défaut est l'**absence de garde**, pas une valeur périmée. + +### Fix (1 fichier gate + 1 doc CI · aucun chiffre à corriger) + +- **`ci/check_readme_claims.sh`** — nouveau bloc Mobile recomputant le quadruplet + `(onglets, roles_couverts, langues, identifiants_a_confirmer)` depuis + `mobile/app_config/out/MANIFEST.json · counts` (zéro duplication du compteur du + générateur · #6) et exigeant l'égalité avec ce que le README ÉCRIT. Recomputer + **les 4** volets (et pas le seul total de rôles) mord une dérive de N'IMPORTE + lequel là où un seul nombre resterait aveugle. Un claim absent échoue AUSSI + (INTROUVABLE = régression de traçabilité). +- **`ci/README.md`** — surface ajoutée à la table récap du pipeline + paragraphe + descriptif (classe « app mobile Expo »). + +### Preuve (4 morsures vérifiées + restauration verte) + +| Injection | Résultat | +|---|---| +| onglets 5→6 | ✗ dit 6/44/3/13 MAIS MANIFEST 5/44/3/13 · exit 1 | +| rôles 44→43 | ✗ dit 5/43/3/13 MAIS MANIFEST 5/44/3/13 · exit 1 | +| langues 3→2 | ✗ dit 5/44/2/13 MAIS MANIFEST 5/44/3/13 · exit 1 | +| identifiants 13→12 | ✗ dit 5/44/3/12 MAIS MANIFEST 5/44/3/13 · exit 1 | +| claim supprimé | ✗ récap app INTROUVABLE · exit 1 | +| restauré | ✓ 5/44/3/13 == MANIFEST · exit 0 | + +Suite CI complète re-verte : `check_artifacts` · `check_readme_claims` · +`check_docs` · `check_regression` · `check_ci_integrity` (gates statiques +verrouillés) · `guard_constraints`. + +**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 e0875ce..b4020f9 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) == 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) **et** de l'app mobile Expo (onglets/rôles/langues/identifiants a_confirmer) == 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 @@ -198,6 +198,17 @@ dérive du 2ᵉ nombre là où le compte d'états seul (repris dans `dossier_ven resterait aveugle — ajouter un état/une transition ferait sinon dériver **les deux** docs en silence. +Même classe encore sur l'**app mobile Expo** : la ligne récap du README du module +`mobile/app_config` (« **5 onglets** · **44 rôles** couverts · **3 langues** · +**13 identifiants `a_confirmer`** ») transcrivait **à la main** un quadruplet dont +chaque nombre est recompté à chaque build depuis `rbac_50_roles.json` (portails +métier + rôles), `seo_spec.json` (langues) et la liste `a_confirmer` (credentials +EAS/stores jamais fabriqués). Les quatre sont recomputés depuis +`mobile/app_config/out/MANIFEST.json` (`counts.{onglets,roles_couverts,langues, +identifiants_a_confirmer}`, byte-gaté par `check_artifacts`). Recomputer **les +quatre** volets mord une dérive de n'importe lequel (ajouter un rôle/portail RBAC, +une langue, un credential store) là où un seul nombre resterait aveugle. + 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 635d0ed..22c38bd 100755 --- a/ci/check_readme_claims.sh +++ b/ci/check_readme_claims.sh @@ -62,6 +62,11 @@ # Profile · 4 DocType custom + 5 Company + 4 rôles » → apply_plan MANIFEST # • ventilation « 28 entite · 16 groupe · 2 own · 4 equipe » → userperm MANIFEST # (les 3 MANIFEST recalculés du contrat rbac_50_roles.json, byte-gatés) +# mobile/app_config/README.md (ligne récap de l'app Expo) : +# • quadruplet « 5 onglets · 44 rôles couverts · 3 langues · 13 identifiants +# a_confirmer » transcrit À LA MAIN → app_config +# MANIFEST.counts (onglets/roles_couverts/langues/identifiants_a_confirmer, +# recomptés de rbac_50_roles + seo_spec + a_confirmer, byte-gatés) # 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é). # @@ -690,6 +695,51 @@ if wf_states is not None: bad(f"CRM pipeline · {DV_RD} — dit états={m.group(1)} MAIS MANIFEST dit " f"états={wf_states} → régénérer le README") +# ============================================================================ +# Mobile · récap de l'app Expo (onglets / rôles couverts / langues / identifiants +# a_confirmer) — même classe de « vert trompeur », transcrite À LA MAIN. +# ---------------------------------------------------------------------------- +# Le générateur app_config émet out/MANIFEST.json dont `counts` porte onglets, +# roles_couverts, langues et identifiants_a_confirmer, RECOMPTÉS à chaque build +# depuis rbac_50_roles.json (portails métier + rôles), seo_spec.json (langues) et +# la liste `a_confirmer` (identifiants EAS/stores jamais fabriqués · byte-gaté par +# check_artifacts ⇒ artefact faisant autorité). Ce quadruplet est recopié À LA +# MAIN dans la ligne récap du README du module (05_deliverables_mvp/mobile/ +# app_config/README.md — « **5 onglets** … · **44 rôles** couverts · **3 langues** +# · **13 identifiants `a_confirmer`** »). Aucun gate ne le couvrait : ajouter un +# rôle/portail au contrat RBAC, une langue au site ou un credential store ferait +# dériver le doc en silence pendant que l'artefact dit autre chose — même piège +# que les mots-clés SEO ou les triplets par workspace des portails. On RECOMPUTE +# ici les 4 nombres depuis MANIFEST.counts (déjà byte-gaté · zéro duplication du +# compteur du générateur) et on exige l'égalité. Un claim absent échoue AUSSI +# (traçabilité). Recomputer les 4 (et pas le seul total de rôles) mord une dérive +# de N'IMPORTE lequel des volets là où un seul nombre resterait aveugle. +# ============================================================================ +MOB_RD = f"{D}/mobile/app_config/README.md" +try: + mob_c = load("mobile/app_config/out/MANIFEST.json")["counts"] + mob_exp = (str(mob_c["onglets"]), + str(mob_c["roles_couverts"]), + str(mob_c["langues"]), + str(mob_c["identifiants_a_confirmer"])) +except (OSError, KeyError, ValueError) as e: + bad(f"Mobile · MANIFEST app_config illisible/inattendu : {e}"); mob_exp = None +if mob_exp is not None: + mob_src = (f"onglets={mob_exp[0]} rôles={mob_exp[1]} langues={mob_exp[2]} " + f"identifiants={mob_exp[3]}") + mob_pat = (r"\*\*(\d+) onglets\*\* \([^)]*\) · \*\*(\d+) rôles\*\* couverts · " + r"\*\*(\d+) langues\*\* · \*\*(\d+) identifiants") + mob_txt = read_norm(MOB_RD) + if mob_txt is not None: + m = re.search(mob_pat, mob_txt) + if m is None: + bad(f"Mobile · {MOB_RD} — récap app INTROUVABLE (attendu {mob_src})") + elif m.groups() == mob_exp: + good(f"Mobile · {MOB_RD} — {'/'.join(m.groups())} == MANIFEST ({mob_src})") + else: + bad(f"Mobile · {MOB_RD} — dit {'/'.join(m.groups())} MAIS MANIFEST dit " + f"{'/'.join(mob_exp)} ({mob_src}) → régénérer le README") + sys.exit(1 if FAIL else 0) PY rc=$?