diff --git a/tools/gen-rules.py b/tools/gen-rules.py index 06076dc..060b5ed 100755 --- a/tools/gen-rules.py +++ b/tools/gen-rules.py @@ -25,6 +25,7 @@ RACINE = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) SORTIE = os.path.join(RACINE, 'doc-generated', 'RULES.md') EXTERNES = os.path.join(RACINE, 'tools', 'rules-external.txt') RETIRES = os.path.join(RACINE, 'tools', 'rules-retired.txt') +CADRAGE = os.path.join(RACINE, 'tools', 'rules-noncode.txt') # --- Grammaire --------------------------------------------------------------------- # @@ -112,11 +113,19 @@ def scanner(): return defs, rows, cites -def etat(c): - """DÉRIVÉ de l'endroit où l'identifiant apparaît — aucune tenue à la main.""" +def etat(c, estCadrage=False): + """DÉRIVÉ de l'endroit où l'identifiant apparaît — aucune tenue à la main. + + Seule exception : « cadrage », déclaré dans tools/rules-noncode.txt. Ce n'est pas un + état de preuve mais une NATURE — une règle négative s'applique par absence de code, et + la compter comme un trou de couverture invite à boucher un trou qui n'existe pas. Le + garde-fou vit dans main() : une règle de cadrage citée par du code fait ÉCHOUER la + génération, parce que la classification est alors périmée ou inutile. + """ if c.get('releve'): return 'vérifiée sur machine' if c.get('test'): return 'citée par un test' if c.get('code'): return 'citée par le code' + if estCadrage: return 'cadrage (aucun porteur attendu)' return 'spécifiée seule' @@ -133,6 +142,7 @@ def main(): externes = lire(EXTERNES) retires = lire(RETIRES) + cadrage = lire(CADRAGE) defs, rows, cites = scanner() # Un numéro RETIRÉ est connu : il n'est ni orphelin ni à redéfinir. @@ -157,7 +167,7 @@ def main(): '> est nommé à un endroit, pas qu\'il y est éprouvé. Le lecteur juge sur pièce —\n' '> c\'est délibéré : un ✅ automatique mentirait comme les tables qu\'il remplace.\n') - par_etat = collections.Counter(etat(cites[r]) for r in defs) + par_etat = collections.Counter(etat(cites[r], r in cadrage) for r in defs) out.append('\n| | |\n|---|---|') out.append('| Règles définies | **%d** |' % len(defs)) for e, n in par_etat.most_common(): @@ -183,7 +193,9 @@ def main(): c = cites[rid] out.append('\n## %s {#%s}\n' % (rid, rid)) out.append('%s\n' % enonce) - out.append('**Domicile** : `%s:%d` · **État** : %s\n' % (rel(f), i, etat(c))) + out.append('**Domicile** : `%s:%d` · **État** : %s\n' % (rel(f), i, etat(c, rid in cadrage))) + if rid in cadrage and cadrage[rid]: + out.append('- *Cadrage* : %s' % cadrage[rid][0]) for fam, titre in (('code', 'Implémentée — cité par'), ('test', 'Éprouvée — cité par'), ('releve', 'Machine — cité par'), @@ -205,6 +217,21 @@ def main(): pb.append('%d identifiant(s) présent(s) UNIQUEMENT en ligne de tableau : une étiquette ' 'de trois mots n\'est pas un énoncé, et ne peut pas valoir domicile :\n %s' % (len(sans_enonce), ' '.join(sans_enonce))) + # Une règle de CADRAGE citée par du code : la classification ne tient plus. Soit la règle + # a été implémentée depuis, soit elle est citée pour expliquer une absence — dans les deux + # cas elle a désormais un porteur lisible, et « citée par le code » est plus vrai que + # « cadrage ». La réparation est toujours la même : retirer la ligne. La liste ne peut donc + # que rétrécir, elle ne sait pas mentir dans le sens qui compte. + cadrage_porte = sorted(r for r in cadrage + if cites.get(r, {}).get('code') or cites.get(r, {}).get('test')) + if cadrage_porte: + pb.append('%d règle(s) déclarée(s) CADRAGE mais citée(s) par du code ou un test — la ' + 'classification ne tient plus, RETIRER la ligne de tools/rules-noncode.txt :' + '\n%s' % (len(cadrage_porte), '\n'.join( + ' %-14s cité par %s' % (r, ', '.join( + '`%s`' % x for fam in ('code', 'test') + for x in cites[r].get(fam, []))) + for r in cadrage_porte))) if ressuscites: pb.append('%d numéro(s) RETIRÉ(S) redéfini(s) — un identifiant retiré ne se réattribue ' 'pas : la même référence désignerait deux exigences selon la date de lecture :' diff --git a/tools/rules-noncode.txt b/tools/rules-noncode.txt new file mode 100644 index 0000000..b130890 --- /dev/null +++ b/tools/rules-noncode.txt @@ -0,0 +1,62 @@ +# Règles de CADRAGE — celles dont on n'ATTEND aucun porteur de code. +# +# POURQUOI CETTE LISTE. Sans elle, « spécifiée seule » est un nombre qui ne veut rien +# dire : il mélange « pas encore construite » avec « rien ne pourra jamais la citer ». +# Une règle NÉGATIVE — « un thermostat n'est PAS une charge », « aucune ThingClass n'est +# exposée » — s'applique par ABSENCE de code : il n'existe aucune ligne à pointer. La +# compter comme un trou de couverture invite à boucher un trou qui n'existe pas. +# +# CE QUE LA LISTE NE DIT PAS. Elle ne dit pas « aucun code ne peut la citer », mais +# « on n'attend pas de porteur ». La nuance décide du garde-fou ci-dessous. +# +# ── GARDE-FOU : le générateur CASSE si une règle d'ici acquiert une citation de code ── +# +# C'est ce qui empêche une liste tenue à la main de dériver — même mécanique que le refus +# de redéfinir un numéro retiré. Deux cas la déclenchent, et la réparation est la même : +# +# · la règle a été implémentée depuis → la classification est PÉRIMÉE ; +# · la règle est citée pour expliquer une ABSENCE (« LM-202 : un thermostat n'est pas +# une charge, donc rien ici ») → la citation est légitime, et la règle a désormais +# un porteur lisible. +# +# Dans les deux cas : RETIRER la ligne. L'index la comptera « citée par le code », ce qui +# est vrai et plus utile que « cadrage ». La liste ne peut donc que RÉTRÉCIR — elle ne +# sait pas mentir dans le sens qui compte. +# +# NE PAS y mettre une règle simplement « pas encore faite ». §10, §14, ECS-5xx et ECS-6xx +# n'y sont pas : ils sont en attente de construction, et c'est ce que « spécifiée seule » +# doit continuer de dire. +# +# format : IDENTIFIANTmotif + +# — Cadre du chantier ECS : constats résolus, frontières tenues par absence, hors périmètre +ECS-001 constat résolu, négatif — reconfirmé contre le libnymea du build +ECS-002 négatif : ni integration-plugin, ni dépôt, ni ThingClass +ECS-003 découpage documentaire, pas une exigence de comportement +ECS-007 interdiction — aucune couche ECS ne décide de sa part du surplus +ECS-008 contrat d'un AUTRE produit (optimiseur ETM consomme, l'ECS publie) +ECS-203 CLOS : aucune ThingClass exposée, donc aucune interface à implémenter + +# — §1 : ce que le modèle EST, avant tout mécanisme +LM-100 structure du modèle — deux couches indépendantes +LM-101 structure du modèle — une charge choisit un domaine et un mécanisme +LM-102 négatif : aucune des deux couches ne répartit de budget + +# — Négatifs et hors moule +LM-202 négatif : un thermostat de zone n'est PAS une charge +LM-403 négatif : le mode est une sortie, jamais une entrée +LM-405 hors périmètre : la décomposition en facettes vit côté ems +LM-406 négatif : sans chauffe-eau distinct, il n'y a pas de charge à créer +LM-700 hors moule : la batterie n'est pas une charge à rang (renvoi §14) +LM-701 hors moule : le smarthome ne se module pas en watts + +# — §5/§6 : définitions et conventions de nommage +LM-500 définition — un groupe est une clé de tri, jamais un détenteur de budget +LM-503 définition — ce qu'un rang n'est pas +LM-504 convention de nommage du domaine hvac +LM-601 souhait de renommage, pas une exigence + +# — Justifications et contrats clients +LM-1204 justification d'un ordre de travail, pas une exigence +LM-1205 constat — la machinerie d'échéance existe déjà +LM-1209-d contrat d'AFFICHAGE côté client ; le moteur n'a rien à porter