diff --git a/05_activity_log/2026-07-31.md b/05_activity_log/2026-07-31.md index 6cbd852..c568879 100644 --- a/05_activity_log/2026-07-31.md +++ b/05_activity_log/2026-07-31.md @@ -1,5 +1,70 @@ # Activity Log · 2026-07-31 · Claude Code DTP +## Session `20260731_160111` · Buffer S8 · Domaine Fiscal/e-CF : le **contrat de format de l'e-NCF** (« `E + tipoeCF(2) + secuencia(10)` » = **13**) était retranscrit À LA MAIN dans **QUATRE** endroits de prose (README ×3 + fiche agent `erpnext_backend`) sans AUCUN gate d'IDENTITÉ — le bloc racine ne gate que le COMPTE de tests e-CF (« 39 »). + +**Tâche** : **Sprint 8 · buffer L75** (DevOps CI/CD · QA). Roadmap fonctionnellement +close ; poursuite de la série anti-dérive (CLAUDE.md #6). Les sessions précédentes ont +gaté les domaines tests, portails, RBAC, SEO, CRM (pipeline + séparation des pouvoirs), +mobile (quadruplet + énumération onglets), Legal/CONFOTUR, DevOps, QA (audit 5D + 4Big), +Démo, Frontend/Chat OTOIA et recette. Cette session cible le **domaine Fiscal/e-CF DGII** +(`fiscal/ecf_dgii`) — le **contrat de format de l'e-NCF**, cœur du module (le « composeur +traçable »), dont les longueurs de segments citées dans quatre proses pointaient +l'artefact généré sans être recomputées par aucun gate. + +**Dérive silencieuse fermée** : +- `05_deliverables_mvp/fiscal/ecf_dgii/README.md` × **3** : résumé l.10 + (`` `E + tipoeCF(2) + secuencia(10)` ``), invariant 7 l.75 (`E+tipo(2)+seq(10)=13`, + **seul à porter la longueur totale**), composition l.84 (`e_ncf = "E" + tipo(2) + + secuencia(10)`). +- `03_agents/erpnext_backend/AGENT.md:33` : `` `E + tipoeCF(2) + secuencia(10)` ``. +- Source faisant autorité : le bloc `e_ncf` de `fiscal/ecf_dgii/out/ecf_plan.json` + (byte-gaté par `check_artifacts`) — `prefix` (« E »), `longueur` (13), `regex` + (`^E[0-9]{12}$`), `structure` (« E + tipoeCF (2 chiffres) + secuencia (10 + chiffres) »). Format e-NCF standard DGII (Ley 32-23) porté avec `source` — pas un + chiffre inventé (#6). +- Le bloc racine ne gate QUE le **compte de tests** e-CF (« 39 »). Aucun gate ne + comparait la LONGUEUR des segments : allonger la secuencia (rango DGII à 12 chiffres → + `longueur` 15, `regex` 14 digits) OU changer le préfixe ferait dériver les QUATRE + proses en silence pendant que l'`ecf_plan` byte-gaté dit autre chose — « vert + trompeur » qu'aucune suite `tests/` (qui teste les FONCTIONS `compose_encf`/ + `parse_encf`, pas la prose) n'attrape. +- **État courant** : **aucun segment périmé** — les 4 proses recoupent l'artefact + exactement (anti-invention #6, rien à réécrire). Le défaut est la **surface ungated**. + +**Nouveau bloc dans `ci/check_readme_claims.sh`** : recompute +`(prefix, tipo_len, seq_len, total)` depuis le SEUL bloc `e_ncf` (zéro duplication du +format du générateur · #6) et exige que **chaque** prose l'encode **exactement** (4 +sous-checks numérotés, patron du bloc DevOps). Cohérences croisées en bonus (mordent un +artefact **internement incohérent**) : `1 + tipo_len + seq_len == longueur` (1+2+10==13) +ET le nombre de chiffres du `regex` == `tipo_len + seq_len` (12 == 2+10). Un claim absent +échoue AUSSI (traçabilité). + +### Preuve (5 morsures vérifiées + morsure d'incohérence + restauration verte) + +| Injection | Résultat | +|---|---| +| artefact `secuencia 10→12` (longueur 15 · regex 14 · les 4 proses restent 10/13) | ✗ les **4** proses mordues : dit E/2/10 MAIS e_ncf tipo=2 seq=12 total=15 · exit 1 | +| README résumé `tipo 2→3` | ✗ dit E/3/10 MAIS e_ncf tipo=2 · exit 1 | +| README invariant total `13→14` (aveugle au segment) | ✗ dit E/2/10/14 MAIS e_ncf total=13 · exit 1 | +| fiche `erpnext_backend` `secuencia 10→9` | ✗ dit E/2/9 MAIS e_ncf seq=10 · exit 1 | +| README composition supprimée (reformulée) | ✗ format e-NCF INTROUVABLE · exit 1 | +| artefact `regex` désaligné (`{11}` vs tipo+seq=12) | ✗ incohérence artefact : regex 11 chiffres MAIS tipo+seq=12 · exit 1 | +| restauré | ✓ `E + tipo(2) + secuencia(10) = 13` == `e_ncf` (4 proses + 2 cohérences croisées) · exit 0 | + +- **`ci/README.md`** — surface ajoutée à la table récap du pipeline + paragraphe + descriptif (classe « contrat de format de l'e-NCF fiscal »). + +Suite CI complète re-verte (7 gates) : `guard_constraints` · `validate_json` · +`check_docs` · `check_artifacts` · `check_regression` · `check_ci_integrity` · +`check_readme_claims`. + +**Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ; +édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact). + +**Auto-score 4Big** : 96/100. + +--- + ## Session `20260731_150110` · Buffer S8 · Domaine Mobile (2e surface) : la ligne récap du README app Expo **énumérait** À LA MAIN ses **5 onglets** (`(Ventes/Construction/Achat/Compta/Direction)`) sans gate d'IDENTITÉ — le bloc mobile existant ne recompute que le COMPTE (« 5 onglets ») et son patron matche la parenthèse par `\([^)]*\)` en **ignorant son contenu**, aveugle à un **échange / retrait / réordre** de nom. **Tâche** : **Sprint 8 · buffer L75** (DevOps CI/CD · QA). Roadmap fonctionnellement diff --git a/ci/README.md b/ci/README.md index b17ba02..a9668ed 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`) == 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`) == 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 @@ -370,6 +370,33 @@ vérifiées : `data_room` « sans HANDOFF »→« sans CLI » (mauvais critère) exclusion `HANDOFF` retirée (sous-ensemble) · énumération `contract` supprimée ; restauré = vert. +Même classe enfin sur le **contrat de format de l'e-NCF** (livrable +`fiscal/ecf_dgii`) : le format de l'identifiant fiscal électronique DGII +« **E + tipoeCF(2) + secuencia(10)** » (= **13**) — préfixe, longueur du segment +tipo, longueur du segment secuencia et longueur totale — est le CŒUR du module (le +« composeur traçable ») et il est retranscrit **à la main** dans **quatre** endroits +de prose : le résumé du README (`E + tipoeCF(2) + secuencia(10)`), l'invariant 7 du +README (`E+tipo(2)+seq(10)=13`, seul à porter la longueur **totale**), la section +composition du README (`e_ncf = "E" + tipo(2) + secuencia(10)`) et la fiche agent +`erpnext_backend`. Le bloc racine ne gate que le **compte de tests** e-CF (« 39 ») ; +ce format n'était recomputé par AUCUN gate. Or il est **data-derived** du bloc +`e_ncf` d'`fiscal/ecf_dgii/out/ecf_plan.json` (byte-gaté par `check_artifacts`) : +`prefix` (« E »), `longueur` (13), `regex` (« `^E[0-9]{12}$` ») et `structure` +(« E + tipoeCF (2 chiffres) + secuencia (10 chiffres) »). Allonger la secuencia (un +rango DGII à 12 chiffres → `longueur` 15) ou changer le préfixe ferait dériver les +quatre proses en silence pendant que l'artefact dit autre chose — dérive qu'aucune +suite `tests/` (qui teste les FONCTIONS `compose_encf`/`parse_encf`, pas la prose) +n'attrape. On RECOMPUTE `(prefix, tipo_len, seq_len, total)` depuis le SEUL bloc +`e_ncf` (zéro duplication du format du générateur · #6) et on exige que **chaque** +prose l'encode exactement. Cohérences croisées en bonus (mordent un artefact +**internement incohérent**) : `1 + tipo_len + seq_len == longueur` (1+2+10 == 13) ET +le nombre de chiffres du `regex` == `tipo_len + seq_len` (12 == 2+10). 5 morsures +vérifiées : drift de l'artefact source (secuencia `10→12`, les 4 proses mordues) · +résumé README `tipo 2→3` · invariant README total `13→14` (aveugle au segment) · +fiche `erpnext_backend` `secuencia 10→9` · composition README supprimée +(INTROUVABLE) ; plus la morsure d'incohérence d'artefact (`regex` désaligné du +segment) ; 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 a84c7f0..cf8b836 100755 --- a/ci/check_readme_claims.sh +++ b/ci/check_readme_claims.sh @@ -84,6 +84,13 @@ # dans DEUX docs de module (le bloc racine ne gate que le README d'entrée) # → recompté depuis la matrice acceptance (kind sprint_deliverable / mvp_metric) # + cohérence croisée == MANIFEST.counts (compteur ⇔ matrice résumée) +# fiscal/ecf_dgii/README.md (×3) + 03_agents/erpnext_backend/AGENT.md (format e-NCF) : +# • le CONTRAT DE FORMAT de l'e-NCF « E + tipoeCF(2) + secuencia(10) = 13 » +# (préfixe · longueur segment tipo · longueur segment secuencia · longueur +# totale) transcrit À LA MAIN dans QUATRE endroits de prose → recompté depuis +# le SEUL bloc `e_ncf` d'ecf_plan.json (prefix/longueur/regex/structure, byte- +# gatés) + cohérences croisées (1+tipo+seq == longueur · |regex digits| == +# tipo+seq — mordent un artefact internement incohérent) # 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é). # @@ -1518,6 +1525,102 @@ if acc_fi is not None: bad(f"Recette · {ACC_FI} — dit {'/'.join(m.groups())} MAIS matrice dit " f"{'/'.join(acc_exp)} ({acc_src}) → régénérer la fiche") +# ============================================================================ +# Fiscal · e-CF DGII — le CONTRAT DE FORMAT de l'e-NCF (« E + tipoeCF(2) + +# secuencia(10) » = 13) transcrit À LA MAIN dans QUATRE endroits de prose (README +# ×3 + fiche agent erpnext_backend), sans AUCUN gate d'IDENTITÉ. +# ---------------------------------------------------------------------------- +# Le générateur ecf_dgii_gen émet out/ecf_plan.json (byte-gaté par check_artifacts +# ⇒ faisant autorité) dont le bloc `e_ncf` PORTE le format de l'identifiant +# électronique DGII : `prefix` (« E »), `longueur` (13), `regex` +# (« ^E[0-9]{12}$ ») et `structure` (« E + tipoeCF (2 chiffres) + secuencia +# (10 chiffres) »). Ce format — préfixe + longueur du segment tipo + longueur du +# segment secuencia + longueur totale — est le CŒUR du module (le « composeur +# traçable ») et il est recopié À LA MAIN dans QUATRE endroits de prose : +# • README:10 « composeur d'e-NCF traçable `E + tipoeCF(2) + secuencia(10)` » +# • README:75 (invariant 7) « E+tipo(2)+seq(10)=13 » (porte la longueur TOTALE) +# • README:84 (composition) « e_ncf = "E" + tipo(2) + secuencia(10) » +# • 03_agents/erpnext_backend/AGENT.md:33 « `E + tipoeCF(2) + secuencia(10)` » +# Le bloc racine de check_readme_claims ne gate QUE le COMPTE de tests e-CF +# (« 39 »). Aucun gate ne comparait la LONGUEUR des segments à l'artefact : allonger +# la secuencia (rango DGII à 12 chiffres → seq=12, longueur=15, regex 14 digits) +# OU changer le préfixe ferait dériver les QUATRE proses en silence pendant que +# l'ecf_plan byte-gaté dit autre chose — « vert trompeur » qu'aucune suite tests/ +# (qui teste des FONCTIONS ncf.compose/parse, pas la prose des docs) n'attrape. +# Même patron que l'énumération des onglets Mobile, la carte 4Big ou les open items +# 5D. On RECOMPUTE (prefix, tipo_len, seq_len, total) depuis le SEUL bloc `e_ncf` +# (zéro duplication du format du générateur #6) et on exige que CHAQUE prose +# l'encode EXACTEMENT. Cohérences croisées en bonus (mordent un artefact +# INTERNEMENT incohérent) : |prefix| + tipo_len + seq_len == longueur (1+2+10==13) +# ET le nombre de chiffres du `regex` == tipo_len + seq_len (12 == 2+10). Un claim +# absent échoue AUSSI (la disparition d'un format est une régression · traçabilité). +# ============================================================================ +ECF_RD = f"{D}/fiscal/ecf_dgii/README.md" +ECF_FI = "03_agents/erpnext_backend/AGENT.md" +try: + encf = load("fiscal/ecf_dgii/out/ecf_plan.json")["e_ncf"] + ecf_prefix = encf["prefix"] + ecf_total = int(encf["longueur"]) + ecf_seglens = [int(x) for x in + re.findall(r"\((\d+)\s*chiffres?\)", encf["structure"])] + ecf_mdig = re.search(r"\[0-9\]\{(\d+)\}", encf["regex"]) +except (OSError, KeyError, ValueError) as e: + bad(f"Fiscal · artefact ecf_plan illisible/inattendu : {e}"); encf = None +if encf is not None: + if len(ecf_seglens) != 2: + bad(f"Fiscal · e_ncf.structure malformée : {encf['structure']!r} " + f"(attendu 2 segments « (N chiffres) », trouvé {ecf_seglens})") + else: + ecf_tipo, ecf_seq = ecf_seglens + # Cohérences croisées : l'artefact doit être INTERNEMENT cohérent. + if len(ecf_prefix) != 1: + bad(f"Fiscal · incohérence artefact : prefix={ecf_prefix!r} n'est pas " + f"1 caractère") + if 1 + ecf_tipo + ecf_seq != ecf_total: + bad(f"Fiscal · incohérence artefact : 1+{ecf_tipo}+{ecf_seq}=" + f"{1 + ecf_tipo + ecf_seq} MAIS longueur={ecf_total}") + if ecf_mdig is None or int(ecf_mdig.group(1)) != ecf_tipo + ecf_seq: + got = ecf_mdig.group(1) if ecf_mdig else "?" + bad(f"Fiscal · incohérence artefact : regex porte {got} chiffres MAIS " + f"tipo+seq={ecf_tipo + ecf_seq}") + exp_ncf = f"{ecf_prefix} + tipo({ecf_tipo}) + secuencia({ecf_seq}) = {ecf_total}" + def ecf_check(tag, path, txt, m, with_total): + if m is None: + bad(f"Fiscal · {path} — {tag} : format e-NCF INTROUVABLE " + f"(attendu {exp_ncf})") + return + g = m.groups() + ok = (g[0] == ecf_prefix and int(g[1]) == ecf_tipo + and int(g[2]) == ecf_seq + and (not with_total or int(g[3]) == ecf_total)) + if ok: + good(f"Fiscal · {path} — {tag} : {exp_ncf} == e_ncf (artefact)") + else: + shown = "/".join(g) + bad(f"Fiscal · {path} — {tag} : dit {shown} MAIS e_ncf dit " + f"prefix={ecf_prefix} tipo={ecf_tipo} seq={ecf_seq} " + f"total={ecf_total} → régénérer le doc") + # 1) README:10 — résumé « composeur d'e-NCF traçable `E + tipoeCF(2) + secuencia(10)` » + ecf_rd = read_norm(ECF_RD) + if ecf_rd is not None: + ecf_check("résumé composeur", ECF_RD, ecf_rd, + re.search(r"`([A-Z])\s*\+\s*tipoeCF\((\d+)\)\s*\+\s*" + r"secuencia\((\d+)\)`", ecf_rd), False) + # 2) README:75 — invariant 7 « E+tipo(2)+seq(10)=13 » (porte la longueur totale) + ecf_check("invariant format", ECF_RD, ecf_rd, + re.search(r"\(([A-Z])\+tipo\((\d+)\)\+seq\((\d+)\)=(\d+)", + ecf_rd), True) + # 3) README:84 — composition « e_ncf = "E" + tipo(2) + secuencia(10) » + ecf_check("composition ncf.py", ECF_RD, ecf_rd, + re.search(r'e_ncf\s*=\s*"([A-Z])"\s*\+\s*tipo\((\d+)\)\s*\+\s*' + r"secuencia\((\d+)\)", ecf_rd), False) + # 4) Fiche agent erpnext_backend — « `E + tipoeCF(2) + secuencia(10)` » + ecf_fi = read_norm(ECF_FI) + if ecf_fi is not None: + ecf_check("fiche erpnext_backend", ECF_FI, ecf_fi, + re.search(r"`([A-Z])\s*\+\s*tipoeCF\((\d+)\)\s*\+\s*" + r"secuencia\((\d+)\)`", ecf_fi), False) + sys.exit(1 if FAIL else 0) PY rc=$?