diff --git a/docs/taxonomy/498-reconciliation-p1c-annotations.csv b/docs/taxonomy/498-reconciliation-p1c-annotations.csv new file mode 100644 index 00000000..7f389ebc --- /dev/null +++ b/docs/taxonomy/498-reconciliation-p1c-annotations.csv @@ -0,0 +1,8 @@ +fallacy_pk,family,fallacy_name,skos_signature,RA_scheme,attack_type,attacked_component,AIF_attackType,AIF_attackedNode,precedent_pk,confidence,justification +696,Erreur de raisonnement,Erreur de raisonnement,Direct=Logical_Conflict | Exception=- | skos:closeMatch,Logical_Conflict (CQ direct generique),undercut,inference (RA),undercut,RA-node,- (suffix-only; parente famille),MED,"SUFFIX-ONLY. Fallacie PARENTE de la famille Erreur de raisonnement (pas de sous-sous). desc_fr « votre these repose sur un raisonnement incoherent » = c'est le RAISONNEMENT (le pas inferentiel) qui est incoherent, pas les premisses prises isolement -> le defaut vise la regle d'inference -> RA-node (undercut). Distinction explicite avec pk777 Inconsistance (undermine, tranche-1b) : pk777 = premisses mutuellement contradictoires (OpposedCommitment/InconsistentCommitment -> au moins une premisse inacceptable, I-node) ; pk696 = Logical_Conflict generique visant la coherence du raisonnement lui-meme. Flag MED : parente generique, token generique ; l'alternative undermine (incoherence lue comme premisses contradictoires) ecartee car desc_fr cible « raisonnement » et non « affirmations qui se contredisent »." +697,Erreur de raisonnement,Causalite douteuse,Direct=OppositeConsequences_Conflict | Exception=PracticalReasoning_Inference | skos:broadMatch,PracticalReasoning_Inference (scheme exception) + OppositeConsequences_Conflict (CQ direct),undercut,inference (RA),undercut,RA-node,- (suffix-only; cf. in-set 707 CauseToEffect),HIGH,"SUFFIX-ONLY. Sous-famille Causalite douteuse. desc_fr « vous supposez un lien de cause a effet qui n'est pas demontre ou qui est incorrect » = le PAS INFERENTIEL causal (X donc Y) n'est pas justifie -> attaque de l'inference -> RA-node (undercut). Controle de coherence (pas heritage) : l'anchor in-set pk707 CauseToEffect_Inference est undercut (audit #770) — les defauts d'inference causale se typent undercut. Le CQ OppositeConsequences challenge la conclusion causale sur le terrain inferentiel, pas la verite d'une premisse." +705,Erreur de raisonnement,Pente glissante,"Direct=RequiredSteps_Conflict | Exception=PrecedentSlipperySlope_Inference, FullSlipperySlope_Inference, CausalSlipperySlope_Inference | skos:narrowMatch",slippery-slope inferences (Precedent/Full/Causal) + RequiredSteps_Conflict (CQ),undercut,inference (RA),undercut,RA-node,- (suffix-only; ancre in-set pk858 VerbalSlipperySlope),HIGH,"SUFFIX-ONLY mais ancrage semantique in-set fort. La pente glissante predit une sequence peu probable d'evenements negatifs (desc_fr) : la CHAINE inferentielle est le defaut — RequiredSteps_Conflict challenge un maillon de la chaine -> RA-node (undercut). Ancre in-set pk858 VerbalSlipperySlope_Inference -> undercut (audit #770) : le slippery-slope penche undercut sur precedent reel, pas seulement CQ-semantique. Contre-exemple canonique de la memoire aif-no-inherit (677/705 : ne PAS heriter l'undermine de l'anchor attack-only ; le modeling propre donne undercut)." +719,Erreur de raisonnement,Effet cigogne,Direct=OtherCausalFactorsInvolved_Conflict | Exception=CorrelationToCause_Inference | skos:closeMatch,CorrelationToCause_Inference (scheme exception) + OtherCausalFactorsInvolved_Conflict (CQ),undercut,inference (RA),undercut,RA-node,- (suffix-only; cf. in-set 707 CauseToEffect),HIGH,SUFFIX-ONLY. Sous-famille Effet cigogne (correlation != causalite). desc_fr « une correlation entre deux evenements implique necessairement un lien de causalite » = le PAS correlation=>causalite est injustifie (d'autres facteurs causaux interviennent) -> attaque de l'inference -> RA-node (undercut). CorrelationToCause_Inference est exactement l'inference challengee ; OtherCausalFactorsInvolved_Conflict est le CQ standard de Walton (autres facteurs causaux). Coherent avec pk697/in-set pk707 (defauts d'inference causale = undercut). +726,Erreur de raisonnement,Composition fautive,"Direct=Commitment_Conflict | Exception=Inference_Scheme, Deductive_Inference | skos:broadMatch",Deductive_Inference + Inference_Scheme (schemes) + Commitment_Conflict (CQ),undercut,inference (RA),undercut,RA-node,- (suffix-only; cf. in-set 727 ModusPonens),HIGH,"SUFFIX-ONLY. Sous-famille Mauvaise composition. desc_fr « vous combinez des propositions logiques de facon incorrecte, ce qui conduit a un raisonnement errone » = la COMBINAISON deductive (le pas inferentiel qui agrege les propositions) est invalide -> RA-node (undercut). Deductive_Inference + Inference_Scheme = le defaut est au niveau de l'inference. Ancre in-set pk727 ModusPonens_Inference -> undercut (audit #770) : les defauts deductifs se typent undercut. Le CQ Commitment reste secondaire (challenge l'engagement dans la combinaison)." +758,Erreur de raisonnement,Deduction invalide,Direct=Commitment_Conflict | Exception=Deductive_Inference | skos:broadMatch,Deductive_Inference (scheme exception) + Commitment_Conflict (CQ),undercut,inference (RA),undercut,RA-node,- (suffix-only; cf. in-set 727 ModusPonens),HIGH,"SUFFIX-ONLY. Sous-famille Mauvaise deduction. desc_fr « vous tirez des conclusions qui ne decoulent pas logiquement de vos premisses » = cas d'ecole de l'undercut : la GARANTIE / le pas deductif (premisses => conclusion) ne tient pas, sans que les premisses soient elles-memes fausses -> RA-node. Deductive_Inference = l'inference deductive est le defaut. Ancre in-set pk727 ModusPonens_Inference -> undercut (audit #770). Textbook undercut ASPIC+ (la conclusion ne suit pas de la regle d'inference)." +759,Erreur de raisonnement,Conclusion hative,Direct=OppositeConsequences_Conflict | Exception=- | skos:broadMatch,OppositeConsequences_Conflict (CQ direct),undercut,inference (RA),undercut,RA-node,- (suffix-only),MED,"SUFFIX-ONLY. Sous-famille Conclusion hative. desc_fr « vous tirez des conclusions trop vite, sans disposer de suffisamment de preuves » = SAUT inferentiel premature : la conclusion est tiree avant que l'inference soit suffisamment supportee -> RA-node (undercut). Meme token que pk697 (OppositeConsequences_Conflict, undercut) et meme famille inferentielle. Flag MED : lecture alternative undermine possible (« sans suffisamment de preuves » = insuffisance de premisses) ; ecartee car le defaut central de la conclusion hative (hasty generalization Walton) est le CARACTERE PREMATURE du pas inferentiel, la CQ de suffisance portant sur le support de l'inference plutot que sur la faussete d'une premisse. A confirmer par ai-01." diff --git a/docs/taxonomy/498-reconciliation-p1c.md b/docs/taxonomy/498-reconciliation-p1c.md new file mode 100644 index 00000000..2c90cd4b --- /dev/null +++ b/docs/taxonomy/498-reconciliation-p1c.md @@ -0,0 +1,132 @@ +# #498 — AIF two-layer reconciliation, P1 tranche-1c (SUFFIX-ONLY, famille « Erreur de raisonnement ») + +**Worker** po-2024 · **Date** 2026-07-11 · **Base** master `7406bb8e` (post-#771 + proposition #773 mergée ; tranche-1 en prod) · **Statut** proposition **gated** (0 write prod CSV dans ce PR) · **Track** GO ai-01 « suite SUFFIX-ONLY par sous-lots famille (autonome) » (`msg-20260711T030324-wgh0ks`), couvert par le pilote GO #498 (jsboige 2026-06-17). + +> Scope de ce PR : **docs + apply-script (dry-run)**. Aucune cellule du CSV de production n'est modifiée dans le diff. La sérialisation prod suit le flow #753/#760 (gate ai-01), après revue de cette proposition. + +--- + +## 0. TL;DR + +Suite de la réconciliation P1 (back-fill de la couche attack pour les 52 lignes skos-only). La **tranche-1** (#769/#771) a livré les **14 PRECEDENT** ; la **tranche-1b** (#773, en revue) livre les **2 PREC-TIE + 5 SUFFIX-ONLY**. Ce PR livre la **tranche-1c = 7 lignes SUFFIX-ONLY**, la **famille « Erreur de raisonnement » complète** : + +| pk | sophisme | sous-famille | +|---:|---|---| +| 696 | Erreur de raisonnement (parente) | — | +| 697 | Causalité douteuse | Causalité douteuse | +| 705 | Pente glissante | Causalité douteuse / Pétition de principe | +| 719 | Effet cigogne | Causalité douteuse / Effet cigogne | +| 726 | Composition fautive | Mauvaise composition | +| 758 | Déduction invalide | Mauvaise déduction | +| 759 | Conclusion hâtive | Mauvaise déduction / Conclusion hâtive | + +**Aucun** token de ces 7 lignes n'a de précédent exact in-set (vérifié précédent-check : tous `precedent_votes={}`). Modélisation Walton **au cas par cas** depuis le scheme propre + `desc_fr` (« que défait le CQ »). Le **prior de suffixe est proscrit** (contre-preuve in-set pk804 `_Conflict`→undercut, audit #770). + +Distribution : **7 undercut / 0 undermine / 0 rebut**. Cette **uniformité est la signature attendue d'une famille d'erreurs de raisonnement** — le défaut porte par nature sur le **pas inférentiel** (§3 : ce n'est pas un défaut par défaut, chaque ligne est modélisée et deux nuances sont flaguées MED). `attackType` = **jugement neuf** (0-risque token, pas 0-risque modélisation) ; **0 fabrication de token** (#677). `attackedNode` déterministe (#707§4 a) → tous **RA-node**. + +Back-fill **+7** : attack-typed **107 → 114** (baseline dynamique, §5). Reste après ce sous-lot : **24 SUFFIX-ONLY** (Tricherie 8, Abus de langage 7, Insuffisance 5, Erreur mathématique 4). + +--- + +## 1. Fondation (héritée de #769 + #770) + +- **Méthode** (#769 §2) : l'`attackType` de chaque ligne skos-only se dérive de **sa propre** signature skos, ancrée sur les **18 lignes fully-modeled** (ground truth token→type). Pas d'héritage mécanique du sous-sous anchor (0/19 alignées, #769 §1). +- **Audit des 18 anchors** (#770) : **16 CLEAN / 2 SOFT / 0 erreur franche, node-map 18/18 propre**. Trois trouvailles de l'audit **ancrent directement** ce sous-lot inférentiel : + 1. **suffixe non décisif** (pk804 `PropertyNotExistant_Conflict`→undercut) → le prior de suffixe est proscrit ; + 2. **slippery-slope penche undercut** (pk858 `VerbalSlipperySlope_Inference`→undercut) → ancre in-set directe pour **pk705** ; + 3. les **défauts d'inférence causale/déductive in-set sont undercut** (pk707 `CauseToEffect_Inference`, pk727 `ModusPonens_Inference`) → contrôle de cohérence pour 697/719 (causal) et 726/758 (déductif). + +Node déterministe (tous undercut ici) : + +| attackType | attackedNode | composant attaqué | +|---|---|---| +| undercut | `RA-node` | inférence (la règle ne s'applique pas) | +| undermine | `I-node` | prémisse (acceptabilité contestée) | +| rebut | `CA-node` | conclusion (contre-conclusion / blocage du dialogue) | + +--- + +## 2. SUFFIX-ONLY — famille « Erreur de raisonnement », modélisation Walton (7 lignes) + +Aucun token de ces lignes n'a de précédent in-set. On modélise **depuis le scheme propre + `desc_fr`** (« que défait le CQ »). **Prior de suffixe proscrit.** + +| pk | sophisme | scheme propre (signature skos) | `desc_fr` (abrégé) | → type | node | conf | +|---:|---|---|---|---|---|---| +| 696 | Erreur de raisonnement (parente) | `Logical_Conflict` | « votre thèse repose sur un raisonnement incohérent » | **undercut** | RA | MED | +| 697 | Causalité douteuse | `PracticalReasoning_Inference` + `OppositeConsequences_Conflict` | « lien de cause à effet non démontré ou incorrect » | **undercut** | RA | HIGH | +| 705 | Pente glissante | slippery-slope inferences (Precedent/Full/Causal) + `RequiredSteps_Conflict` | « prédisant une séquence peu probable d'événements négatifs » | **undercut** | RA | HIGH | +| 719 | Effet cigogne | `CorrelationToCause_Inference` + `OtherCausalFactorsInvolved_Conflict` | « une corrélation implique nécessairement un lien de causalité » | **undercut** | RA | HIGH | +| 726 | Composition fautive | `Deductive_Inference` + `Inference_Scheme` + `Commitment_Conflict` | « vous combinez des propositions logiques de façon incorrecte » | **undercut** | RA | HIGH | +| 758 | Déduction invalide | `Deductive_Inference` + `Commitment_Conflict` | « conclusions qui ne découlent pas logiquement de vos prémisses » | **undercut** | RA | HIGH | +| 759 | Conclusion hâtive | `OppositeConsequences_Conflict` | « conclusions trop vite, sans suffisamment de preuves » | **undercut** | RA | MED | + +- **696** — parente générique. `desc_fr` cible le **RAISONNEMENT** (le pas inférentiel) incohérent, pas les prémisses isolées → RA. **Distinction explicite avec pk777 Inconsistance** (undermine, tranche-1b) : pk777 = prémisses mutuellement contradictoires (`OpposedCommitment`/`InconsistentCommitment` → au moins une prémisse inacceptable, I-node) ; pk696 = `Logical_Conflict` générique visant la cohérence du raisonnement lui-même. **Flag MED** : alternative undermine écartée car `desc_fr` cible « raisonnement » et non « affirmations qui se contredisent ». +- **697** — le pas causal (X ⇒ Y) est non démontré/incorrect → attaque de l'inférence → RA. Contrôle (pas héritage) : in-set pk707 `CauseToEffect_Inference`→undercut. +- **705** — pente glissante : la **chaîne** inférentielle est le défaut (séquence improbable) ; `RequiredSteps_Conflict` challenge un maillon → RA. **Ancre in-set pk858** `VerbalSlipperySlope_Inference`→undercut (audit #770) ; contre-exemple canonique de la mémoire (677/705 : ne PAS hériter l'undermine de l'anchor attack-only ; le modeling propre donne undercut). +- **719** — corrélation ⇏ causalité : le pas est injustifié (autres facteurs causaux) → RA. `CorrelationToCause_Inference` est exactement l'inférence challengée. +- **726** — composition fautive : la **combinaison déductive** est invalide → RA. `Deductive_Inference` + `Inference_Scheme` ; in-set pk727 `ModusPonens_Inference`→undercut. +- **758** — déduction invalide : cas d'école de l'undercut, la **garantie** (prémisses ⇒ conclusion) ne tient pas sans que les prémisses soient fausses → RA. `Deductive_Inference` ; in-set pk727. +- **759** — conclusion hâtive : **saut inférentiel prématuré** → RA. Même token que pk697 (undercut). **Flag MED** : lecture alternative undermine possible (« sans suffisamment de preuves » = insuffisance de prémisses) ; écartée car le défaut central de la hasty generalization Walton est le **caractère prématuré du pas**, la CQ de suffisance portant sur le support de l'inférence. + +Détail complet en colonne `justification` de [`498-reconciliation-p1c-annotations.csv`](498-reconciliation-p1c-annotations.csv). + +--- + +## 3. Uniformité undercut — signature de famille, pas défaut par défaut + +Ce sous-lot ressort **7/7 undercut**. C'est **attendu et cohérent**, non un artefact de facilité : + +- **Sémantique** : « Erreur de raisonnement » regroupe les défauts du **pas inférentiel** (causalité douteuse, pente glissante, corrélation≠cause, composition/déduction fautives, conclusion hâtive). Le défaut porte structurellement sur la **règle d'inférence** (undercut/RA), pas sur l'acceptabilité d'une prémisse (undermine/I) ni sur une contre-conclusion/blocage de dialogue (rebut/CA). +- **Ancrage in-set** : **tous** les tokens d'inférence causale/déductive/slippery des 18 fully-modeled sont undercut (pk707, pk727, pk858 — audit #770). La famille hérite de cette signature par **cohérence vérifiée**, pas par prior de suffixe (proscrit). +- **Garde-fou** : chaque ligne est modélisée séparément et **deux nuances sont flaguées MED** avec leur alternative explicite — **696** (vs undermine si lu comme prémisses contradictoires ; écarté par `desc_fr`) et **759** (vs undermine si lu comme insuffisance de preuves ; écarté par la sémantique hasty-generalization). Les cinq autres (697/705/719/726/758) sont HIGH. + +**Les 2 MED-flag load-bearing à revoir en priorité par ai-01 : 696 et 759.** + +--- + +## 4. Justification par ligne + +Voir colonne `justification` du CSV machine-readable. Mécanisme unique de ce sous-lot : **la règle d'inférence ne tient pas (undercut/RA)** — + +- **inférence causale non justifiée** : 697 (cause⇏effet), 719 (corrélation⇏cause) ; +- **chaîne inférentielle faible** : 705 (pente glissante) ; +- **combinaison / déduction invalide** : 726 (composition), 758 (déduction) ; +- **saut inférentiel prématuré** : 759 (conclusion hâtive) ; +- **incohérence du raisonnement** : 696 (parente générique). + +--- + +## 5. Sérialisation (flow #753/#760) + +`tools/498-p1c-apply.py` — **gated, dry-run par défaut**, miroir de `tools/498-p1b-apply.py` : + +- lit `498-reconciliation-p1c-annotations.csv` et **re-vérifie** que sa carte interne concorde 7/7 (assertion load-bearing) ; +- splitters byte-exact (guillemets doublés + LF encadrés), cell-fill des seules colonnes `AIF_attackType`/`AIF_attackedNode` des 7 PK ; +- pre-state : les 7 PK **vides** (fill, pas overwrite) + **portent une skos** (back-fill skos-only) ; +- preuve de **byte-preservation** (0 mismatch hors les 2 cellules × 7 lignes), well-formedness 104 cols, BOM+CRLF préservés ; +- `--write` **gaté** (ai-01), backup `tmp/Fallacies-backup-pre-p1c.csv` avant écriture pour vérif indépendante. + +**Baseline dynamique (pilotée par les `--write`, pas par les merges).** Les 7 PK sont **disjoints** des 14 de la tranche-1 (#771) et des 7 de la tranche-1b (#773). La baseline dépend des **sérialisations prod `--write` effectuées**, pas des PR mergées : chaque proposition merge d'abord (docs + dry-run), son `--write` est une **étape gated séparée** (relais ai-01). Le script lit le compte **réel** et rapporte `total → total+7` sans jamais coder en dur la baseline : + +- si seul le `--write` tranche-1 (#771) a tourné : `107 → 114` ; +- si les `--write` tranche-1 **et** tranche-1b ont tourné : `114 → 121`. + +L'ordre est donc sans impact sur la correction (cellules disjointes). Dry-run actuel (master `7406bb8e`, proposition #773 **mergée** mais son `--write` prod **encore en attente** → baseline 107) : `107 → 114`, delta **+105 B**, **0 mismatch**, **1409×104**, CRLF+BOM préservés. + +```bash +python tools/498-p1c-apply.py # dry-run (0 write prod) — ce PR +python tools/498-p1c-apply.py --write # APPLY 7 cellules (GATÉ — relais ai-01) +``` + +--- + +## 6. Bornes du gate + +- ✅ **0 write prod CSV** dans ce PR (docs + apply-script dry-run uniquement — `git diff f34ac77c -- Cards/…/Taxonomy.csv` vide). +- ✅ **0 fabrication token #677** — aucun token AIF créé ; on type des lignes déjà skos-vettées. +- ✅ Code=truth : tokens/labels/`desc_fr` lus du CSV master `7406bb8e` ; précédents = les 18 fully-modeled (audités CLEAN, #770) ; SUFFIX-ONLY confirmé par précédent-check (tous `precedent_votes={}`). +- ✅ Prior de suffixe **proscrit** ; uniformité undercut = signature de famille vérifiée, chaque ligne modélisée, 2 MED flaguées. +- ❌ #674/#666/#596 non touchés (HOLD). Pas de self-merge — verdict QA ai-01. +- ⏸️ Sérialisation prod = étape suivante gated (relais ai-01), pas dans ce PR. + +🤖 Worker po-2024 — réconciliation P1 tranche-1c (famille « Erreur de raisonnement », 7 SUFFIX-ONLY). diff --git a/tools/498-p1c-apply.py b/tools/498-p1c-apply.py new file mode 100644 index 00000000..bce6be2b --- /dev/null +++ b/tools/498-p1c-apply.py @@ -0,0 +1,186 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""#498 P1 reconciliation tranche-1c APPLY — back-fill the 2 AIF attack columns +for 7 skos-only rows, the whole 'Erreur de raisonnement' family (GATED — dry-run +by default). + +Companion to `docs/taxonomy/498-reconciliation-p1c.md` (proposition) and its +machine-readable `docs/taxonomy/498-reconciliation-p1c-annotations.csv`. Mirrors +`tools/498-p1b-apply.py`: the 2 columns already exist post-#753, so this is +byte-exact CELL FILL, not column insertion. + +tranche-1c = 7 SUFFIX-ONLY rows (696, 697, 705, 719, 726, 758, 759). None has an +exact-token precedent among the 18 fully-modeled anchors (p1c-precedent-check: +all precedent_votes={}). They are the complete 'Erreur de raisonnement' family: +inference-level defects (dubious causality, slippery slope, correlation!=cause, +faulty composition, invalid deduction, hasty conclusion, incoherent reasoning). +Modeled per-row from each fallacy's OWN scheme + desc_fr (Walton "what does the CQ +defeat"): every one attacks the INFERENTIAL step -> all undercut/RA-node. This is +the expected signature of a reasoning-error family, coherence-checked against the +in-set anchors 707 CauseToEffect / 727 ModusPonens / 858 VerbalSlipperySlope (all +undercut, audit #770) — NOT a mechanical default. The suffix prior +(_Conflict/_Inference -> undermine) is PROSCRIBED (in-set disproof pk804 +_Conflict->undercut). attackType is a NEW judgment; NODE is the deterministic +ASPIC+ map (#707§4 Option a): undercut->RA, undermine->I, rebut->CA. No token +fabricated (#677): we TYPE rows that already carry a vetted skos. + +GATE (per pilote GO #498 jsboige 2026-06-17, relay ai-01): the proposition is +reviewed by ai-01 before prod write. `--write` is GATED until ai-01 relays the go. + +NOTE on baseline: the attack-typed total is driven by prod --write serializations, +NOT by PR merges (each tranche's proposition merges first; its --write is a separate +gated step relayed by ai-01). It is 107 if only tranche-1 (#771) is written, or 114 +if tranche-1b (#773) is also written. This script reads the ACTUAL current count and +reports total_now -> total_now+7 dynamically — it never hardcodes the baseline, so it +is correct regardless of write order (the 7 target PKs are disjoint from tranche-1's +14 and tranche-1b's 7). + + python tools/498-p1c-apply.py # dry-run, 0 prod write (this PR) + python tools/498-p1c-apply.py --write # APPLY 7 cells (GATED — ai-01 relay) +""" +import csv, io, sys, os +from collections import Counter + +PATH = "Cards/Fallacies/Argumentum Fallacies - Taxonomy.csv" +ANNOT = "docs/taxonomy/498-reconciliation-p1c-annotations.csv" +BACKUP = "tmp/Fallacies-backup-pre-p1c.csv" # saved before --write (independent verify) +NODE = {"undercut": "RA-node", "undermine": "I-node", "rebut": "CA-node"} +WRITE = "--write" in sys.argv + +# ── 7-row tranche-1c map (attackType). Load-bearing: re-verified below to match +# the annotation CSV 7/7 (the CSV is the human-reviewed source of truth). All +# SUFFIX-ONLY, whole 'Erreur de raisonnement' family -> all inference-level. ── +P1C_MAP = { + "696": "undercut", # Erreur de raisonnement (parent, raisonnement incoherent) + "697": "undercut", # Causalite douteuse (causal inference unwarranted) + "705": "undercut", # Pente glissante (slippery-slope chain; in-set anchor 858) + "719": "undercut", # Effet cigogne (correlation != causation) + "726": "undercut", # Composition fautive (faulty deductive combination) + "758": "undercut", # Deduction invalide (conclusions don't follow) + "759": "undercut", # Conclusion hative (premature inferential leap) +} +assert len(P1C_MAP) == 7 +assert Counter(P1C_MAP.values()) == {"undercut": 7} + +# ── byte-exact splitters (CSV-aware: doubled quotes + embedded LF) ───────────── +def split_logical_rows(text): + rows, cur, in_q = [], [], False + i, n = 0, len(text) + while i < n: + ch = text[i] + if ch == '"': + if in_q and i+1 < n and text[i+1] == '"': + cur.append('""'); i += 2 + else: + in_q = not in_q; cur.append(ch); i += 1 + elif ch == '\r' and not in_q and i+1 < n and text[i+1] == '\n': + rows.append(''.join(cur)); cur = []; i += 2 + else: + cur.append(ch); i += 1 + if cur: rows.append(''.join(cur)) + return rows + +def split_fields(row): + segs, cur, in_q = [], [], False + i, n = 0, len(row) + while i < n: + ch = row[i] + if ch == '"': + if in_q and i+1 < n and row[i+1] == '"': + cur.append('""'); i += 2 + else: + in_q = not in_q; cur.append(ch); i += 1 + elif ch == ',' and not in_q: + segs.append(''.join(cur)); cur = []; i += 1 + else: + cur.append(ch); i += 1 + segs.append(''.join(cur)) + return segs + +# ── load-bearing: re-verify P1C_MAP vs the annotation CSV (human-reviewed source) ─ +annot_map = {} +with open(ANNOT, encoding="utf-8-sig") as fh: + for row in csv.DictReader(fh): + annot_map[row["fallacy_pk"].strip()] = row["AIF_attackType"].strip() + assert row["AIF_attackedNode"].strip() == NODE[row["AIF_attackType"].strip()], \ + f"annotation node/type inconsistent at pk {row['fallacy_pk']}" +assert set(annot_map) == set(P1C_MAP), ( + f"P1C_MAP vs annotation-CSV PK mismatch\n" + f" only CSV: {set(annot_map)-set(P1C_MAP)}\n only MAP: {set(P1C_MAP)-set(annot_map)}") +assert all(annot_map[pk] == P1C_MAP[pk] for pk in P1C_MAP), "attack_type mismatch vs annotation CSV" + +# ── read current CSV ─────────────────────────────────────────────────────────── +raw = open(PATH, "rb").read() +bom = raw[:3] == b'\xef\xbb\xbf' +text = (raw[3:] if bom else raw).decode('utf-8') +ended_crlf = text.endswith('\r\n') +rows = split_logical_rows(text) +header = split_fields(rows[0]) +NCOL = len(header) +ATI = header.index('AIF_attackType') +ANI = header.index('AIF_attackedNode') +PKI = 0 # uppercase 'PK' +assert header[ATI-1] == 'AIF_skosMappingType', "AIF col block moved?" + +# ── pre-state: every target PK must be empty (fill, not overwrite) + carry skos ─ +DIR = header.index('AIF_skosDirectRef'); EXC = header.index('AIF_skosExceptionRef') +OTH = header.index('AIF_skosOther') +pre = {} +for r in rows[1:]: + s = split_fields(r); pk = s[PKI].strip() + if pk in P1C_MAP: + pre[pk] = (s[ATI].strip(), s[ANI].strip(), + bool(s[DIR].strip() or s[EXC].strip() or s[OTH].strip())) +assert set(pre) == set(P1C_MAP), f"target PKs missing in CSV: {set(P1C_MAP)-set(pre)}" +not_empty = {pk for pk,(at,an,_) in pre.items() if at or an} +assert not not_empty, f"ABORT: target PKs not empty (would overwrite): {not_empty}" +no_skos = {pk for pk,(_,_,hs) in pre.items() if not hs} +assert not no_skos, f"ABORT: target PKs lack skos (not skos-only back-fill): {no_skos}" + +# ── apply: cell-fill byte-exact (only ATI/ANI of the 7 PK change) ───────────── +new_rows = [rows[0]] +filled = Counter() +for rtext in rows[1:]: + s = split_fields(rtext); pk = s[PKI].strip() + if pk in P1C_MAP: + at = P1C_MAP[pk]; s[ATI] = at; s[ANI] = NODE[at]; filled[at] += 1 + new_rows.append(",".join(s)) +new_text = "\r\n".join(new_rows) + ("\r\n" if ended_crlf else "") + +# ── byte-preservation proof (only ATI/ANI of the 7 PK may differ) ───────────── +new_rows2 = split_logical_rows(new_text) +mismatches = 0 +for i in range(len(rows)): + o = split_fields(rows[i]); n = split_fields(new_rows2[i]) + assert len(o) == len(n) == NCOL, f"row {i} col count drift" + for j in range(NCOL): + if o[j] != n[j] and not (i > 0 and j in (ATI, ANI) and o[PKI].strip() in P1C_MAP): + mismatches += 1 + if mismatches <= 3: + print(f" MISMATCH row {i} pk {o[PKI].strip()!r} col {j}: {o[j]!r} -> {n[j]!r}") + +# re-parse well-formedness +chk = list(csv.reader(io.StringIO(new_text))) +assert len(chk) == len(rows) and all(len(r) == NCOL for r in chk), "well-formedness" + +# ── report ───────────────────────────────────────────────────────────────────── +total_now = sum(1 for r in rows[1:] if split_fields(r)[ATI].strip()) +baseline_note = {107: "107 (tranche-1 written; tranche-1b write pending)", 114: "114 (tranche-1 + tranche-1b written)"}.get(total_now, str(total_now)) +print("="*72) +print(f"#498 P1 RECONCILIATION tranche-1c APPLY (write={WRITE})") +print("="*72) +print(f"annotation CSV re-verified 7/7 vs P1C_MAP: OK") +print(f"pre-state: all 7 target PKs empty + carry skos (skos-only back-fill): OK") +print(f"apply_set: 7 PKs -> distribution: {dict(filled)} (whole 'Erreur de raisonnement' family)") +print(f"byte-preservation mismatches: {mismatches} (must be 0)") +print(f"well-formedness: {len(chk)} rows x {NCOL} cols, CRLF({ended_crlf})+BOM({bom}) preserved") +print(f"delta if written: {len(new_text)-len(text)} bytes") +print(f"CSV attack-typed total: {total_now} -> {total_now + 7} [baseline {baseline_note}; disjoint from tranche-1's 14 + tranche-1b's 7]") +if WRITE: + os.makedirs("tmp", exist_ok=True) + open(BACKUP, "wb").write(raw) + open(PATH, "wb").write((b'\xef\xbb\xbf' if bom else b'') + new_text.encode('utf-8')) + print(f">>> WRITTEN (7 cells filled). Backup saved to {BACKUP} for independent verify.") + print(f" GATE lifted by ai-01 relay.") +else: + print(">>> DRY-RUN (pass --write to APPLY; GATED until ai-01 relays go).")