[DTP-Worker] Sprint 8 · buffer · Fiscal/e-CF DGII : l'IDENTITÉ du RÔLE émetteur e-CF — le SEUL rôle habilité à ÉMETTRE un comprobante fiscal électronique, nommé en prose par le bullet role_id du README (fiscal/ecf_dgii/README.md:51-54) via TROIS attributs data-derived (role_id compta-fiscaliste-ecf · erpnext_role_name OTO Compta Fiscaliste eCF · portail compta) — était HORS de tout gate d'IDENTITÉ.
Le bloc e-NCF gate le FORMAT de l'identifiant ; le bloc cross-cohérence (2e surface) gate états émetteurs/champs de base/FormaPago ; mais AUCUN ne comparait l'IDENTITÉ de ce rôle à ses DEUX sources faisant autorité : (a) out/ecf_plan.json (byte-gaté) — chaque emission_events[] porte role_id + erpnext_role_name ; (b) rbac_50_roles.json (contrat) — le rôle porte portail:"compta". Le `portail` n'est PAS dans l'artefact ; il n'est vérifiable QUE contre le contrat. Même classe d'IDENTITÉ que la Fiche Mobile / les Fiches Faisabilité ou les gardes CRM/workflow_vente, appliquée à une 3e surface distincte du MÊME README fiscal. Piège : la seule vérification existante (tests/) teste des FONCTIONS (ncf/résolution), jamais la prose. RÉAFFECTER l'émission à un autre rôle, le RENOMMER, ou DÉPLACER le rôle hors du portail compta (e-CF émis HORS Compta — séparation cassée) laisse la prose périmée pendant que l'artefact/contrat disent autre chose → l'agent ERPNext Backend câblerait le mauvais rôle émetteur. Gate ajouté (ci/check_readme_claims.sh, bloc « Fiscal rôle émetteur ») : role_id/erpnext_role_name recomputés de emission_events + portail/nom Frappe du contrat, prose EXACTE ; cohérences croisées : tous les emission_events pointent UN SEUL rôle (fonction · non vide) · role_id unique au contrat · nom artefact == contrat (zéro-dup) · portail == compta. Un claim absent échoue AUSSI. 6 morsures vérifiées (README renomme role_id/nom/portail · artefact réaffecte les 2 events à ventes-directeur (5 mords cascade) · contrat déplace le rôle vers ventes · artefact diverge du contrat sur le nom), restauré vert, 7 gates re-verts. ci/README.md mis à jour. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+25
-1
File diff suppressed because one or more lines are too long
@@ -2062,6 +2062,78 @@ if ecf_ev is not None:
|
||||
bad(f"Fiscal cross-cohérence · {ECF_RD2} — FormaPago défaut sans ancrage "
|
||||
f"Cardnet (#10) à proximité → régénérer le README")
|
||||
|
||||
# ============================================================================
|
||||
# Fiscal · e-CF DGII (3e surface du MÊME README) · l'IDENTITÉ du RÔLE émetteur —
|
||||
# le seul rôle habilité à ÉMETTRE un e-CF, transcrit À LA MAIN sans gate d'IDENTITÉ.
|
||||
# ----------------------------------------------------------------------------
|
||||
# La section « Cross-cohérence » du README (bullet `role_id`, README:51-54) NOMME
|
||||
# le rôle émetteur par TROIS attributs data-derived :
|
||||
# • README:54 « le rôle dédié `compta-fiscaliste-ecf` » — le `role_id` que TOUS
|
||||
# les emission_events d'ecf_plan.json (byte-gaté) référencent.
|
||||
# • README:54 « (_OTO Compta Fiscaliste eCF_) » — son `erpnext_role_name`.
|
||||
# • README:53 « appartenir au portail `compta` » — le `portail` de CE rôle dans
|
||||
# le contrat rbac_50_roles.json (schéma-validé · byte-gaté par les 3 MANIFEST).
|
||||
# Le bloc e-NCF gate le FORMAT de l'identifiant, le bloc cross-cohérence gate les
|
||||
# ÉTATS émetteurs / champs de base / FormaPago — mais AUCUN ne compare l'IDENTITÉ
|
||||
# de ce rôle à ses deux sources. RÉAFFECTER l'émission à un autre rôle (dans le
|
||||
# plan), le RENOMMER, ou DÉPLACER le rôle `compta-fiscaliste-ecf` du portail
|
||||
# `compta` vers un autre portail (dans le contrat : l'e-CF serait alors émis HORS
|
||||
# Compta — la séparation même que la cross-cohérence pose) laisse la prose périmée
|
||||
# en silence pendant que l'artefact/le contrat disent autre chose ⇒ l'agent ERPNext
|
||||
# Backend câblerait le mauvais rôle émetteur (le risque même que #6 interdit).
|
||||
# Aucune suite tests/ (qui teste des FONCTIONS ncf/résolution, pas la prose) ne
|
||||
# l'attrape — même patron d'IDENTITÉ que la Fiche Mobile ou les gardes CRM. On
|
||||
# RECOMPUTE `role_id`/`erpnext_role_name` de l'artefact et `portail`/nom Frappe du
|
||||
# contrat (zéro duplication) et on exige la prose EXACTE. Cohérences croisées bonus
|
||||
# (mordent un plan/contrat INTERNEMENT incohérent) : tous les emission_events
|
||||
# pointent UN SEUL rôle (fonction · non vide) · ce role_id existe UNE fois au
|
||||
# contrat · le nom résolu dans l'artefact == le contrat (zéro-dup) · portail ==
|
||||
# `compta`. Un claim absent échoue AUSSI (traçabilité #6).
|
||||
# ============================================================================
|
||||
ECF_RD3 = f"{D}/fiscal/ecf_dgii/README.md"
|
||||
try:
|
||||
ecfp3 = load("fiscal/ecf_dgii/out/ecf_plan.json")
|
||||
ecf_ev3 = ecfp3["emission_events"]
|
||||
ecf_rids = {e["role_id"] for e in ecf_ev3}
|
||||
ecf_rnames = {e["erpnext_role_name"] for e in ecf_ev3}
|
||||
ecf_contract = load("rbac/rbac_50_roles.json")["roles"]
|
||||
except (OSError, KeyError, ValueError, TypeError) as e:
|
||||
bad(f"Fiscal rôle émetteur · ecf_plan / rbac_50_roles illisible/inattendu : {e}"); ecf_ev3 = None
|
||||
if ecf_ev3 is not None:
|
||||
ecf_rd3 = read_norm(ECF_RD3)
|
||||
# bonus : tous les emission_events pointent UN SEUL rôle (fonction · non vide)
|
||||
if len(ecf_rids) != 1 or len(ecf_rnames) != 1:
|
||||
bad(f"Fiscal rôle émetteur · emission_events référencent {sorted(ecf_rids)} role_id / "
|
||||
f"{sorted(ecf_rnames)} noms (attendu 1 rôle émetteur unique) → plan incohérent")
|
||||
elif ecf_rd3 is not None:
|
||||
ecf_rid = next(iter(ecf_rids)); ecf_art_name = next(iter(ecf_rnames))
|
||||
ecf_match = [r for r in ecf_contract if r.get("id") == ecf_rid]
|
||||
if len(ecf_match) != 1:
|
||||
bad(f"Fiscal rôle émetteur · le contrat porte {len(ecf_match)} rôle(s) `{ecf_rid}` "
|
||||
f"(attendu exactement 1) → rôle absent/dupliqué du contrat rbac_50_roles.json")
|
||||
else:
|
||||
rc = ecf_match[0]
|
||||
ecf_ct_name = rc.get("erpnext_role_name"); ecf_portail = rc.get("portail")
|
||||
# bonus zéro-duplication : le nom résolu dans l'artefact == le contrat
|
||||
if ecf_ct_name != ecf_art_name:
|
||||
bad(f"Fiscal rôle émetteur · ecf_plan dit erpnext_role_name={ecf_art_name!r} MAIS "
|
||||
f"contrat dit {ecf_ct_name!r} pour `{ecf_rid}` → régénérer l'artefact")
|
||||
# bonus séparation des pouvoirs : ce rôle est bien du portail compta (README:53)
|
||||
if ecf_portail != "compta":
|
||||
bad(f"Fiscal rôle émetteur · le rôle `{ecf_rid}` est du portail {ecf_portail!r} "
|
||||
f"≠ `compta` au contrat → l'e-CF serait émis HORS Compta (séparation cassée)")
|
||||
# prose : 3 claims data-derived recomputés (role_id · nom Frappe · portail)
|
||||
for label, pat, exp in [
|
||||
("role_id", rf"rôle dédié `{re.escape(ecf_rid)}`", ecf_rid),
|
||||
("nom d'affichage ERPNext", rf"\(_{re.escape(ecf_ct_name)}_\)", ecf_ct_name),
|
||||
("portail", rf"portail `{re.escape(ecf_portail)}`", ecf_portail),
|
||||
]:
|
||||
if re.search(pat, ecf_rd3):
|
||||
good(f"Fiscal rôle émetteur · {label} « {exp} » == ecf_plan/contrat rbac")
|
||||
else:
|
||||
bad(f"Fiscal rôle émetteur · {ECF_RD3} — {label} : la prose ne cite pas "
|
||||
f"« {exp} » (recomputé de emission_events + rbac_50_roles) → régénérer")
|
||||
|
||||
# ============================================================================
|
||||
# CRM/Commissions · barème vendeurs — la CROSS-COHÉRENCE (« cœur du livrable »)
|
||||
# transcrite À LA MAIN dans le README sans AUCUN gate d'IDENTITÉ.
|
||||
|
||||
Reference in New Issue
Block a user