ADR
ADR-0001 — Multi-Type Acquisition & Canonical Projection Boundary
Sur cette page
- Statut :
Accepté - Date : 2026-08-30
- Portée : EPIC 1 (B0) et toute acquisition de source ultérieure
- Remplace : la S1.4 « AcquisitionTransformer /
BukhariHadithTransformer» de la roadmap v1.1 - Références :
planning/epics/scientific-knowledge-layer-roadmap.md§2,docs/scientific/knowledge-model.md,docs/architecture/domain-layer.md,planning/epics/b0-persistence-mapping-decision.md,planning/audits/knowledge-acquisition-contracts-audit.md
1. Contexte#
ISNAD n'est pas un simple moteur d'import de recueils de hadith. La base contiendra plusieurs familles documentaires de structure scientifique différente : recueils de hadith (Bukhārī, Muslim, Sunan, Jāmiʿ, Musnad, Muwaṭṭaʾ), ouvrages de rijāl (Taqrīb, Tahdhīb al-Kamāl, al-Jarḥ wa-l-Taʿdīl, Mīzān, Ṭabaqāt), commentaires (Fatḥ al-Bārī, Sharḥ al-Nawawī), le Coran, les ouvrages de terminologie (muṣṭalaḥ al-ḥadīth), et d'autres familles à venir.
L'état du code au moment de cette décision :
src/domain/knowledge-acquisition/: 10 contrats d'acquisition matures et corpus-agnostiques (CorpusDocument,CorpusChain,CorpusNode,CorpusCommentary,CorpusEdition,CorpusProvenance,CorpusImportResult,CorpusAdapter,KnowledgeAcquisitionEngine). AuditKNOWLEDGE_ACQUISITION_CONTRACTS_AUDIT_V1.md: PASS 14 axes.src/infrastructure/knowledge-acquisition/bukhari/BukhariAdapter.ts: premierCorpusAdapter, lit le corpus Bukhārī legacy (hadiths/hadith_chains/hadith_chain_nodes) et le neutralise enCorpus*. Aucune logique métier, aucune résolution.KnowledgeAcquisitionEngine: interface déjà présente danssrc/domain/knowledge-acquisition/.- Persistance KM V1 (
knowledge_sources,source_editions,knowledge_entries) : déjà créée en S1.3 (supabase/migrations/20260829143612_...sql).
La roadmap v1.1 prévoyait pour S1.4 :
T1.4.2— ImplémenterBukhariHadithTransformer:CorpusDocument HADITH → hadiths + hadith_chains + hadith_chain_nodes.
Deux problèmes :
- Nom lié à un corpus après la frontière de neutralisation. Les contrats
Corpus*existent précisément pour qu'après l'Adapter le système ne sache plus s'il traite Bukhārī ou Muslim. Une classeBukhariHadithTransformerréintroduit cette information. - Couplage projection ↔ persistance. «
→ hadiths + hadith_chains + hadith_chain_nodes» invite à écrire du SQL dans le transformer, couplé au schéma Supabase, court-circuitant les ports de persistance du domaine.
Un troisième constat : les contrats d'acquisition sont aujourd'hui plus expressifs que les entités canoniques et que le schéma SQL (co-narration CorpusNode.mentionsRaw[] vs hadith_chain_nodes unique (chain_id, position) ; taḥwīl CorpusChain.sharedTailOfChainId / divergesAtPosition sans équivalent ; suspended / elided ; LocalizedText[] vs text_ar / text_fr). Une projection naïve serait destructrice.
2. Décision#
2.1 Doctrine (8 règles)#
- Source specificity ends at the Adapter — as behavior. Après l'Adapter, tout branchement conditionné par le nom du corpus (
if (corpus === "bukhari")) est interdit. - Source identity and provenance cross the boundary — as data.
corpusId,editionId,externalRefs, l'identité du document et laprovenancerestent explicitement disponibles en aval : ce sont des données scientifiques, pas des dépendances architecturales. - Transformation varies by scientific content type, not by book/source. Un Projector par famille scientifique (HADITH, RIJĀL, COMMENTARY, QURAN, TERMINOLOGY), jamais un transformer par livre.
- Projectors are pure. Aucun SQL, Supabase,
pg, HTTP, filesystem, ni résolution d'identité. Entrée : contrats d'acquisition. Sortie : commandes de création. - Projectors emit creation commands / aggregates, not persisted entities. Aucun identifiant SQL fabriqué ; clés de corrélation temporaires pour relier document → chaînes → nœuds.
- Persistence occurs exclusively through domain ports implémentés par des adapters d'infrastructure.
- Aggregate persistence is transactional and idempotent. Un hadith et ses chaînes forment une unité ; un réimport ne crée pas de doublon.
- Identity resolution, grading, reconciliation and reasoning are downstream — jamais des préoccupations d'acquisition.
2.2 Propriété des champs (invariant)#
| Propriétaire | Exemples de champs |
|---|---|
| Acquisition | texte source, mention brute, formule de transmission, référence, position, provenance, contentHash |
| Résolution (aval) | narratorId, identityConfidence, resolutionStatus |
| Traitement scientifique (aval) | grading, Assertion, Evidence, décisions de conflit, validation humaine, conclusions de reasoning |
Un réimport peut mettre à jour ce que l'acquisition possède. Il ne peut jamais remettre à NULL un champ produit en aval. Si une donnée source dont dépend un enrichissement change réellement : politique future d'invalidation / review / supersession, jamais un reset aveugle.
2.3 Identité vs version#
- Identité = « de quel objet source parle-t-on ? ». Vérifier d'abord si
document.id/chain.id(déterministes, produits par l'Adapter) suffisent comme clés stables avant d'introduire un nouveau concept (sourceDocumentKey). Si un nouveau concept est nécessaire, documenter précisément pourquoi. - Version =
contentHash: « le contenu a-t-il changé ? ». Une correction typographique dans une édition ne doit pas fabriquer un nouveau hadith scientifique. - Éditions : le même hadith présent dans deux éditions de Bukhārī ne devient pas deux hadiths distincts. Documenter le problème ; prendre la décision minimale nécessaire au vertical slice HADITH ; ne pas lancer une refonte universelle
Work / Edition.
2.4 Politique de perte — trois issues seulement#
Chaque information d'acquisition, dans la matrice de fidélité, reçoit exactement un statut :
- MAPPED — atterrit dans un champ canonique / une colonne.
- PRESERVED — conservée verbatim dans le témoin d'acquisition (JSONB), rejouable ultérieurement.
- HARD_FAIL — la projection refuse de produire l'agrégat.
Plus un drapeau scientific-critical: oui / non. Règle : critique + non MAPPED ⇒ obligatoirement PRESERVED ou HARD_FAIL, jamais un simple warning. Aucune taxonomie plus complexe en V1.
Mieux vaut refuser une projection que produire silencieusement une représentation historiquement fausse de l'isnād.
2.5 Vertical slices & généralisation#
- Chaque famille documentaire est introduite par un vertical slice sur une source réelle.
- Aucun framework générique, modèle universel ou abstraction commune créé uniquement pour anticiper des familles futures.
- Une abstraction n'est généralisée qu'après démonstration par au moins deux cas réels structurellement différents.
2.6 Ce qui n'est PAS conçu maintenant#
BiographyEntryProjector, CommentaryProjector, QuranProjector, TerminologyProjector, registry universel de Projectors, UnitOfWork générique, SourceReader universel obligatoire, modèle universel Work → Edition, framework de raw witnesses, architecture pour des sources non intégrées.
2.7 Architecture cible#
SOURCE / FORMAT (DB, JSON, XML, Shamela, API…)
│
▼
Source Reader / Corpus Adapter ── Anti-Corruption Layer (spécifique à l'œuvre)
│
▼ (identité + édition + externalRefs + provenance conservées)
Contrats d'acquisition
CorpusDocument / CorpusChain / CorpusNode / CorpusCommentary / CorpusProvenance
+ HadithAcquisitionBundle (S1.4)
│
▼
Canonical Projector — par nature scientifique du contenu — PUR, sans DB
(HADITH d'abord : projectHadithBundle / HadithCorpusProjector)
│
▼
Commandes canoniques : NewHadithAggregate { hadith, chains[], nodes[] }
(aucune id SQL — clés de corrélation ; nodes: narrator_id = null, UNRESOLVED)
│
▼
Ports applicatifs : HadithAggregateWriter (transactionnel, idempotent)
│
▼
Infrastructure : SupabaseHadithAggregateWriter
│
▼
PostgreSQL / Supabase
│
▼
AVAL (jamais de l'acquisition) : Narrator Resolver · Assertions · Evidence
· Confidence · Conflict/Reconciliation · Reasoning
2.8 NewHadithAggregate (référence indicative)#
HadithAcquisitionBundle {
document: CorpusDocument // kind === 'HADITH'
ancestry: { work, book, chapter } // CorpusDocument
chains: CorpusChain[] // nodes portés par CorpusChain — ne pas dupliquer
provenance: CorpusProvenance
}
NewHadithAggregate {
hadith // clé de corrélation, ex. "doc:<id>"
chains[] // clé "doc:<id>:chain:<n>"
nodes[] // clé "doc:<id>:chain:<n>:node:<p>" ; narrator_id = null ; resolution_status = UNRESOLVED
}
Le repository transforme les clés de corrélation en identifiants persistants.
3. Conséquences#
Positives#
- Ajouter Muslim, Tirmidhī, un extrait Shamela, un ouvrage de rijāl… se fait par un nouvel Adapter sans toucher au cœur.
- Remplacer Supabase par un autre stockage ne change pas le moteur d'acquisition (ports).
- Aucune perte scientifique silencieuse ; l'isnād n'est jamais réécrit de façon historiquement fausse.
- Un réimport de corpus ne détruit pas la résolution, la validation ou le grading produits en aval.
Coûts / risques acceptés#
- Un
HadithAcquisitionBundle+ des types de commande spécifiques au chemin HADITH (pas d'entitésHadith/Chainréutilisées telles quelles — elles portent uneidobligatoire). - Le cas Bukhārī est un aller-retour (
hadith_*→ Adapter →Corpus*→ Projector →hadith_*) : il valide la plomberie, pas l'acquisition d'une source neuve. D'où le jalon S1.6 Independent Source Proof. - Certaines informations critiques (co-narration, taḥwīl, suspended/elided) devront être
PRESERVEDou déclencherHARD_FAILtant que le schéma ne les représente pas ; les migrations correspondantes sont décidées en S1.4, exécutées en S1.5. - Un guardrail CI est nécessaire pour rendre la règle « projectors purs » exécutable.
Neutres#
- La roadmap est renumérotée E1–E9 ; B0 devient « Multi-Type Acquisition Foundation ».
KnowledgeAcquisitionEngineexistant est étendu en S1.5 — aucune seconde interface.
4. Alternatives rejetées#
| Alternative | Raison du rejet |
|---|---|
BukhariHadithTransformer (+ un transformer par corpus) | Réintroduit l'identité du corpus après l'ACL ; recrée des silos par livre ; dette qui explose à N œuvres. |
Transformer écrivant directement dans hadiths / hadith_chains / hadith_chain_nodes | Couple la projection au schéma SQL ; court-circuite les ports ; non testable sans DB. |
Modèle canonique universel Book → Chapter → Hadith → Chain → Node pour toutes les familles | Faux pour RIJĀL, QURAN, COMMENTARY, TERMINOLOGY ; dette scientifique. |
| Port de persistance unique pour toute la plateforme | Ne respecte pas les frontières d'agrégat ; une écriture partielle laisse la base incohérente. Retenu : un writer par agrégat, UnitOfWork seulement si une opération métier coordonne plusieurs agrégats. |
| Taxonomie de perte à 5 valeurs + criticité à 3 niveaux | Sur-spécification : 15 cases de politique pour ~10 lignes de matrice en V1. Retenu : MAPPED / PRESERVED / HARD_FAIL + criticité binaire. |
| Concevoir tous les Projectors (Biography, Commentary, Quran, Terminology) dès E1 | Overengineering ; abstraction non validée. Retenu : HadithCorpusProjector réel ; les autres = vertical slices futurs. |
Tables knowledge_documents / knowledge_chains / knowledge_nodes / knowledge_import_results / knowledge_provenance | Déjà rejetées en S1.2 (planning/epics/b0-persistence-mapping-decision.md §9). Duplication du Corpus Engine et de import_batches / processing_runs. |
5. Décisions ouvertes — à trancher en S1.4 (planning/audits/b0-hadith-fidelity-matrix.md)#
document.id/chain.idsuffisent-ils comme identité stable, ou faut-il une clé dédiée (et pourquoi) ?- Identité édition-indépendante (citation canonique) ou édition-scopée (⇒ table de concordance) ?
- Conversion de position : contrat 0-based ↔
hadith_chain_nodes.position >= 1. - Co-narration :
CorpusNode.mentionsRaw[]vsmention_arsingulier +unique (chain_id, position)→MAPPED(nouveau schéma),PRESERVED, ouHARD_FAIL? - taḥwīl (
sharedTailOfChainId/divergesAtPosition) : idem. suspended/elided:resolution_statusn'a pas de valeur pour « structurellement suspendu ».LocalizedText[](n langues / n éditions) vstext_ar/text_frfigés.CorpusChain.endpoint/completeness:MAPPEDouPRESERVED?- Clé d'idempotence côté repository :
source_keyexistant vs(knowledge_source, edition, external_ref)vscontentHash. - Édition : décision minimale pour le slice HADITH — réutiliser / étendre
source_editions, sans refonteWork / Edition.
Ces décisions produisent des spécifications de migration (texte) consommées par S1.5. S1.4 n'exécute aucune migration.