diff --git a/CHANGELOG.md b/CHANGELOG.md index cb617c9..80e678c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ### Added +- Assistant de création de personne en sept étapes séparant la révision OCR, + la projection facultative et les relations factuelles avant confirmation. - Projection contrôlée des champs OCR confirmés vers les attributs structurés d’une personne, avec provenance append-only, conflits explicites et rollback. - Saisie et consultation, depuis la fiche preuve, d’un historique immuable @@ -40,6 +42,12 @@ ### Fixed +- Schéma de création directe aligné sur la V19, avec champs structurés, + provenance des projections et garde SQLite identique à la migration V19. +- Migration V20 pour l’évaluation humaine append-only de l’usage d’identité, + distincte de l’authenticité et sans automatisme OCR. +- Consultation V20 sur la fiche preuve et test GTK des relations et champs + structurés affichés sur la fiche Person. - Rafraîchissement immédiat de Workspace après import ou révision OCR et persistance des données sur l’UUID définitif de la preuve, y compris après fermeture et réouverture de SQLite. diff --git a/Makefile b/Makefile index 1de40e6..3654853 100644 --- a/Makefile +++ b/Makefile @@ -198,6 +198,8 @@ TEST_WORKSPACE_IDENTITY_OCR_GTK := tests/test_workspace_identity_ocr_gtk TEST_DIALOG_GEOMETRY_GTK := tests/test_dialog_geometry_gtk TEST_PERSON_FACTUAL_RELATION_EDITOR_GTK := tests/test_person_factual_relation_editor_gtk TEST_DOCUMENT_AUTHENTICITY_EDITOR_GTK := tests/test_document_authenticity_editor_gtk +TEST_DOCUMENT_IDENTITY_MISUSE_EDITOR_GTK := tests/test_document_identity_misuse_editor_gtk +TEST_PERSON_DETAILS_TRACEABILITY_GTK := tests/test_person_details_traceability_gtk TEST_PERSON_OCR_PROJECTION := tests/test_person_ocr_projection TEST_PERSON_OCR_PROJECTION_EDITOR_GTK := tests/test_person_ocr_projection_editor_gtk FAKE_DOCUMENT_TOOL := tests/fake_document_tool @@ -238,6 +240,26 @@ $(TEST_DOCUMENT_AUTHENTICITY_EDITOR_GTK): \ src/database/error.c src/core/relation_type_normalizer.c $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS) +$(TEST_DOCUMENT_IDENTITY_MISUSE_EDITOR_GTK): \ + tests/test_document_identity_misuse_editor_gtk.c \ + src/views/document_identity_misuse_editor.c \ + src/core/document_identity_misuse_service.c src/models/identity_traceability.c \ + src/models/identity_ocr.c src/dao/identity_ocr_dao.c \ + src/dao/identity_traceability_dao.c src/database/database.c \ + src/database/schema.c src/database/statement.c src/database/transaction.c \ + src/database/error.c src/core/relation_type_normalizer.c + $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS) + +$(TEST_PERSON_DETAILS_TRACEABILITY_GTK): \ + tests/test_person_details_traceability_gtk.c \ + src/widgets/entity_details_panel.c src/models/entity_record.c \ + src/models/evidence_record.c src/models/identity_traceability.c \ + src/models/person_role_assignment.c src/views/person_vocabulary_adapter.c + $(CC) $(CFLAGS) $^ src/dao/identity_traceability_dao.c \ + src/database/database.c src/database/schema.c src/database/statement.c \ + src/database/transaction.c src/database/error.c \ + src/core/relation_type_normalizer.c -o $@ $(LDFLAGS) + PERSON_OCR_PROJECTION_SOURCES := src/models/person_ocr_projection.c \ src/core/person_ocr_projection_mapping.c src/core/person_ocr_projection_service.c \ src/dao/person_ocr_projection_dao.c src/dao/identity_ocr_dao.c \ @@ -273,6 +295,7 @@ $(TEST_IDENTITY_OCR_PREPROCESSOR): tests/test_identity_ocr_preprocessor.c \ $(TEST_IDENTITY_TRACEABILITY): tests/test_identity_traceability.c \ src/models/identity_traceability.c src/dao/identity_traceability_dao.c \ src/core/document_authenticity_service.c \ + src/core/document_identity_misuse_service.c \ src/views/person_vocabulary_adapter.c \ src/views/person_factual_relation_editor.c \ src/models/person_role_assignment.c \ @@ -1238,6 +1261,8 @@ test: \ $(TEST_DIALOG_GEOMETRY_GTK) \ $(TEST_PERSON_FACTUAL_RELATION_EDITOR_GTK) \ $(TEST_DOCUMENT_AUTHENTICITY_EDITOR_GTK) \ + $(TEST_DOCUMENT_IDENTITY_MISUSE_EDITOR_GTK) \ + $(TEST_PERSON_DETAILS_TRACEABILITY_GTK) \ $(TEST_PERSON_OCR_PROJECTION) \ $(TEST_PERSON_OCR_PROJECTION_EDITOR_GTK) @echo "Exécution des tests..." @@ -1337,6 +1362,8 @@ test: \ @$(TEST_DIALOG_GEOMETRY_GTK) @$(TEST_PERSON_FACTUAL_RELATION_EDITOR_GTK) @$(TEST_DOCUMENT_AUTHENTICITY_EDITOR_GTK) + @$(TEST_DOCUMENT_IDENTITY_MISUSE_EDITOR_GTK) + @$(TEST_PERSON_DETAILS_TRACEABILITY_GTK) @$(TEST_PERSON_OCR_PROJECTION) @$(TEST_PERSON_OCR_PROJECTION_EDITOR_GTK) @echo "Tous les tests sont valides." @@ -1440,6 +1467,8 @@ clean: $(TEST_DIALOG_GEOMETRY_GTK) \ $(TEST_PERSON_FACTUAL_RELATION_EDITOR_GTK) \ $(TEST_DOCUMENT_AUTHENTICITY_EDITOR_GTK) \ + $(TEST_DOCUMENT_IDENTITY_MISUSE_EDITOR_GTK) \ + $(TEST_PERSON_DETAILS_TRACEABILITY_GTK) \ $(TEST_PERSON_OCR_PROJECTION) \ $(TEST_PERSON_OCR_PROJECTION_EDITOR_GTK) \ $(FAKE_DOCUMENT_TOOL) diff --git a/README.md b/README.md index 7a0b5a7..93e7648 100644 --- a/README.md +++ b/README.md @@ -390,6 +390,18 @@ make clean Le projet est compilé en C17 avec les avertissements traités comme des erreurs. +L’assistant de création d’une personne suit sept étapes distinctes : +`Personne`, `Rôles`, `Preuves`, `OCR identité`, `Projection OCR`, +`Relations factuelles`, puis `Confirmation`. La révision OCR conserve les +valeurs brute, normalisée, corrigée et confirmée avant toute projection. La +projection et les relations sont facultatives, vides par défaut et résumées +séparément avant l’unique écriture transactionnelle finale. + +Le schéma SQLite courant est la V20. Une création neuve installe directement +les extensions V19 et V20 ; une base V19 utilise `database/schema_v20.sql`. +La fiche de preuve sépare l’authenticité de l’évaluation humaine de l’usage +d’identité, dont l’historique est append-only et jamais produit par l’OCR. + --- ## Tests @@ -405,7 +417,8 @@ Vérifications recommandées avant chaque commit : ```bash make clean make -j8 -make -j8 test +make check-source-size +DISPLAY="$DISPLAY" WAYLAND_DISPLAY="$WAYLAND_DISPLAY" make -j8 test git diff --check ``` @@ -427,6 +440,9 @@ G_DEBUG=fatal-criticals timeout 30s ./tests/test_evidence_identity_import_gtk G_DEBUG=fatal-criticals timeout 30s ./tests/test_workspace_identity_ocr_gtk G_DEBUG=fatal-criticals timeout 30s ./tests/test_evidence_preview_widget_gtk G_DEBUG=fatal-criticals timeout 30s ./tests/test_dialog_geometry_gtk +G_DEBUG=fatal-criticals timeout 30s ./tests/test_person_factual_relation_editor_gtk +G_DEBUG=fatal-criticals timeout 30s ./tests/test_document_authenticity_editor_gtk +G_DEBUG=fatal-criticals timeout 30s ./tests/test_person_ocr_projection_editor_gtk ``` Les tests emploient uniquement des documents `SPECIMEN`, des fichiers diff --git a/database/schema_current.sql b/database/schema_current.sql index a58c7fc..de1287a 100644 --- a/database/schema_current.sql +++ b/database/schema_current.sql @@ -1,7 +1,7 @@ /****************************************************************************** * Labfy Investigation * - * Extensions idempotentes du schéma SQLite courant V9 + * Extensions idempotentes du schéma SQLite courant V20 ******************************************************************************/ /* @@ -436,3 +436,64 @@ BEFORE UPDATE ON identity_field_observations BEGIN WHEN NEW.confirmation_state='human_confirmed' AND NEW.confirmed_value IS NULL THEN RAISE(ABORT,'confirmed value required') END; END; + +CREATE TABLE IF NOT EXISTS person_profile_fields( + person_id TEXT NOT NULL,field_code TEXT NOT NULL,value TEXT NOT NULL, + updated_at TEXT NOT NULL CHECK(length(updated_at)=20), + PRIMARY KEY(person_id,field_code), + CHECK(field_code IN('declared_name','surname','given_names','birth_date', + 'birth_place','nationality','sex_as_printed','address_as_printed')), + CHECK(length(trim(value))>0), + FOREIGN KEY(person_id) REFERENCES entites(id) ON DELETE CASCADE); +CREATE TABLE IF NOT EXISTS person_ocr_field_projections( + id TEXT PRIMARY KEY,person_id TEXT NOT NULL,person_field_code TEXT NOT NULL, + previous_value TEXT,new_value TEXT NOT NULL,evidence_id TEXT NOT NULL, + ocr_run_id TEXT NOT NULL,ocr_field_id TEXT NOT NULL,ocr_field_code TEXT NOT NULL, + value_quality TEXT NOT NULL CHECK(value_quality IN('complete','partial')), + review_status TEXT NOT NULL CHECK(review_status IN('accepted','modified')), + strategy TEXT NOT NULL CHECK(strategy IN('fill_empty','replace_existing')), + projected_at TEXT NOT NULL CHECK(length(projected_at)=20), + origin TEXT NOT NULL CHECK(origin='human'), + FOREIGN KEY(person_id) REFERENCES entites(id) ON DELETE RESTRICT, + FOREIGN KEY(evidence_id) REFERENCES preuves(id) ON DELETE RESTRICT, + FOREIGN KEY(ocr_run_id) REFERENCES identity_ocr_runs(id) ON DELETE RESTRICT, + FOREIGN KEY(ocr_field_id) REFERENCES identity_field_observations(id) + ON DELETE RESTRICT); +CREATE INDEX IF NOT EXISTS idx_person_projection_person + ON person_ocr_field_projections(person_id,projected_at,id); +CREATE TRIGGER IF NOT EXISTS projection_v19_consistency +BEFORE INSERT ON person_ocr_field_projections BEGIN + SELECT CASE WHEN NOT EXISTS(SELECT 1 FROM identity_field_observations f + WHERE f.id=NEW.ocr_field_id AND f.ocr_run_id=NEW.ocr_run_id + AND f.evidence_id=NEW.evidence_id AND f.field_code=NEW.ocr_field_code + AND f.confirmation_state='human_confirmed' + AND length(trim(f.confirmed_value))>0 + AND f.review_status IN('accepted','modified') + AND f.value_quality IN('complete','partial') + AND f.confirmed_value=NEW.new_value) + THEN RAISE(ABORT,'OCR field is no longer projectable') END; +END; + +/* V20 — évaluations humaines distinctes de l’usage abusif d’identité. */ +CREATE TABLE IF NOT EXISTS document_identity_misuse_assessments( + id TEXT PRIMARY KEY,evidence_id TEXT NOT NULL,ocr_run_id TEXT, + status TEXT NOT NULL CHECK(status IN('indeterminate','presumed','confirmed')), + justification TEXT,assessed_at TEXT NOT NULL CHECK(length(assessed_at)=20), + previous_assessment_id TEXT,origin TEXT NOT NULL CHECK(origin='human'), + CHECK(status='indeterminate' OR length(trim(justification))>0), + FOREIGN KEY(evidence_id) REFERENCES preuves(id) ON DELETE RESTRICT, + FOREIGN KEY(ocr_run_id) REFERENCES identity_ocr_runs(id) ON DELETE RESTRICT, + FOREIGN KEY(previous_assessment_id) REFERENCES document_identity_misuse_assessments(id) ON DELETE RESTRICT); +CREATE INDEX IF NOT EXISTS idx_identity_misuse_evidence_history + ON document_identity_misuse_assessments(evidence_id,assessed_at,id); +CREATE UNIQUE INDEX IF NOT EXISTS idx_identity_misuse_previous + ON document_identity_misuse_assessments(previous_assessment_id) + WHERE previous_assessment_id IS NOT NULL; +CREATE TRIGGER IF NOT EXISTS identity_misuse_consistency BEFORE INSERT ON document_identity_misuse_assessments BEGIN + SELECT CASE WHEN NEW.ocr_run_id IS NOT NULL AND NOT EXISTS(SELECT 1 FROM identity_ocr_runs r WHERE r.id=NEW.ocr_run_id AND r.evidence_id=NEW.evidence_id) THEN RAISE(ABORT,'OCR run does not belong to evidence') END; + SELECT CASE WHEN NEW.previous_assessment_id IS NOT NULL AND NOT EXISTS(SELECT 1 FROM document_identity_misuse_assessments p WHERE p.id=NEW.previous_assessment_id AND p.evidence_id=NEW.evidence_id) THEN RAISE(ABORT,'previous assessment does not belong to evidence') END; + SELECT CASE WHEN EXISTS(SELECT 1 FROM document_identity_misuse_assessments p WHERE p.evidence_id=NEW.evidence_id) AND NEW.previous_assessment_id IS NULL THEN RAISE(ABORT,'previous assessment is required') END; + SELECT CASE WHEN NEW.previous_assessment_id IS NOT NULL AND EXISTS(SELECT 1 FROM document_identity_misuse_assessments n WHERE n.previous_assessment_id=NEW.previous_assessment_id) THEN RAISE(ABORT,'previous assessment is not current') END; +END; +CREATE TRIGGER IF NOT EXISTS identity_misuse_append_only_update BEFORE UPDATE ON document_identity_misuse_assessments BEGIN SELECT RAISE(ABORT,'identity misuse history is append-only'); END; +CREATE TRIGGER IF NOT EXISTS identity_misuse_append_only_delete BEFORE DELETE ON document_identity_misuse_assessments BEGIN SELECT RAISE(ABORT,'identity misuse history is append-only'); END; diff --git a/database/schema_v20.sql b/database/schema_v20.sql new file mode 100644 index 0000000..7b7db43 --- /dev/null +++ b/database/schema_v20.sql @@ -0,0 +1,44 @@ +/* V20 — évaluations humaines distinctes de l’usage abusif d’identité. */ +CREATE TABLE document_identity_misuse_assessments( + id TEXT PRIMARY KEY, + evidence_id TEXT NOT NULL, + ocr_run_id TEXT, + status TEXT NOT NULL CHECK(status IN('indeterminate','presumed','confirmed')), + justification TEXT, + assessed_at TEXT NOT NULL CHECK(length(assessed_at)=20), + previous_assessment_id TEXT, + origin TEXT NOT NULL CHECK(origin='human'), + CHECK(status='indeterminate' OR length(trim(justification))>0), + FOREIGN KEY(evidence_id) REFERENCES preuves(id) ON DELETE RESTRICT, + FOREIGN KEY(ocr_run_id) REFERENCES identity_ocr_runs(id) ON DELETE RESTRICT, + FOREIGN KEY(previous_assessment_id) + REFERENCES document_identity_misuse_assessments(id) ON DELETE RESTRICT); +CREATE INDEX idx_identity_misuse_evidence_history + ON document_identity_misuse_assessments(evidence_id,assessed_at,id); +CREATE UNIQUE INDEX idx_identity_misuse_previous + ON document_identity_misuse_assessments(previous_assessment_id) + WHERE previous_assessment_id IS NOT NULL; +CREATE TRIGGER identity_misuse_consistency BEFORE INSERT + ON document_identity_misuse_assessments BEGIN + SELECT CASE WHEN NEW.ocr_run_id IS NOT NULL AND NOT EXISTS( + SELECT 1 FROM identity_ocr_runs r + WHERE r.id=NEW.ocr_run_id AND r.evidence_id=NEW.evidence_id) + THEN RAISE(ABORT,'OCR run does not belong to evidence') END; + SELECT CASE WHEN NEW.previous_assessment_id IS NOT NULL AND NOT EXISTS( + SELECT 1 FROM document_identity_misuse_assessments p + WHERE p.id=NEW.previous_assessment_id AND p.evidence_id=NEW.evidence_id) + THEN RAISE(ABORT,'previous assessment does not belong to evidence') END; + SELECT CASE WHEN EXISTS(SELECT 1 FROM document_identity_misuse_assessments p + WHERE p.evidence_id=NEW.evidence_id) AND NEW.previous_assessment_id IS NULL + THEN RAISE(ABORT,'previous assessment is required') END; + SELECT CASE WHEN NEW.previous_assessment_id IS NOT NULL AND EXISTS( + SELECT 1 FROM document_identity_misuse_assessments n + WHERE n.previous_assessment_id=NEW.previous_assessment_id) + THEN RAISE(ABORT,'previous assessment is not current') END; +END; +CREATE TRIGGER identity_misuse_append_only_update BEFORE UPDATE + ON document_identity_misuse_assessments BEGIN + SELECT RAISE(ABORT,'identity misuse history is append-only'); END; +CREATE TRIGGER identity_misuse_append_only_delete BEFORE DELETE + ON document_identity_misuse_assessments BEGIN + SELECT RAISE(ABORT,'identity misuse history is append-only'); END; diff --git a/diff_stat.txt b/diff_stat.txt deleted file mode 100644 index 97a5e6a..0000000 --- a/diff_stat.txt +++ /dev/null @@ -1,20 +0,0 @@ - Makefile | 2 + - include/core/person_creation_task.h | 3 +- - include/dao/identity_traceability_dao.h | 2 + - include/views/create_person_dialog.h | 2 + - include/views/main_window.h | 7 ++++ - include/views/person_vocabulary_adapter.h | 3 ++ - include/widgets/entity_details_panel.h | 2 + - include/widgets/workspace.h | 4 +- - src/core/application.c | 20 ++++++++-- - src/core/person_creation_task.c | 45 +++++++++++++++++++--- - src/dao/identity_traceability_dao.c | 26 +++++++++++++ - src/views/create_person_dialog.c | 63 +++++++++++++++++++++++++++---- - src/views/evidence_metadata_dialog.c | 52 +++++++++++++++++++++++++ - src/views/main_window.c | 8 +++- - src/views/person_vocabulary_adapter.c | 39 +++++++++++++++++++ - src/widgets/entity_details_panel.c | 56 +++++++++++++++++++++++++++ - src/widgets/workspace.c | 7 ++++ - tests/test_create_person_dialog_gtk.c | 3 ++ - tests/test_create_person_dialog_ocr_gtk.c | 2 + - 19 files changed, 327 insertions(+), 19 deletions(-) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index b661447..378e20d 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -30,7 +30,7 @@ pause, retour au début, détachement et libération via des actions injectées. > **Version :** 3.2 > **Dernière mise à jour :** 2026-07-30 -> **Schéma SQLite courant :** V18 +> **Schéma SQLite courant :** V20 ## Personnes contextuelles — SQLite V14 @@ -134,6 +134,21 @@ service relit le champ V18 avant COMMIT et conserve la valeur existante par défaut en cas de conflit. La fiche personne relit ces champs par `PersonDetailsProvider` et le DAO de projection. +`CreatePersonDialog` sépare sept pages : personne, rôles, preuves, révision +OCR, projection OCR, relations factuelles et confirmation. La page de +projection contient uniquement `PersonOcrProjectionEditor`; la page suivante +contient uniquement `PersonFactualRelationEditor`. Le résumé distingue la +correction OCR persistée, les projections choisies et les relations préparées. + +La V20 ajoute `document_identity_misuse_assessments`, domaine distinct de +l’authenticité documentaire. Ses états `indeterminate`, `presumed` et +`confirmed` sont exclusivement humains, chaînés et append-only. La fiche de +preuve affiche séparément leur origine et leur justification. + +La collecte des justifications des rôles sensibles et l’alimentation plus +complète de `person_identification_assessments` restent des renforcements de +cohérence, non des exigences contractuelles explicites du ticket #109. + --- ## 1. Objectif @@ -430,7 +445,7 @@ validation du chemin ↓ création de l'arborescence ↓ -initialisation transactionnelle de SQLite V18 +initialisation transactionnelle de SQLite V20 ↓ création de l'identité de l'enquête ↓ @@ -879,7 +894,8 @@ Validation : ```sh make clean make -j8 -make -j8 test +make check-source-size +DISPLAY="$DISPLAY" WAYLAND_DISPLAY="$WAYLAND_DISPLAY" make -j8 test git diff --check ``` diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 4d24b9d..b06bac9 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -25,6 +25,10 @@ La projection OCR se valide avec `test_person_ocr_projection`, `test_person_ocr_projection_editor_gtk`, ce dernier sous affichage réel et `G_DEBUG=fatal-criticals`. +L’assistant complet se valide également avec +`tests/test_create_person_dialog_gtk` : il vérifie l’ordre des sept pages, +leur séparation et les trois rubriques du résumé final. + La validation ciblée de l’aperçu partagé comprend `test_evidence_preview_widget_gtk`, `test_evidence_preview`, `test_evidence_video_preview_controller` et @@ -306,6 +310,9 @@ make -j8 \ tests/test_workspace_identity_ocr_gtk \ tests/test_evidence_preview_widget_gtk \ tests/test_dialog_geometry_gtk + tests/test_person_factual_relation_editor_gtk \ + tests/test_document_authenticity_editor_gtk \ + tests/test_person_ocr_projection_editor_gtk G_DEBUG=fatal-criticals timeout 30s ./tests/test_create_person_dialog_ocr_gtk G_DEBUG=fatal-criticals timeout 30s ./tests/test_evidence_identity_import_gtk @@ -313,6 +320,9 @@ G_DEBUG=fatal-criticals timeout 30s ./tests/test_evidence_metadata_dialog_gtk G_DEBUG=fatal-criticals timeout 30s ./tests/test_workspace_identity_ocr_gtk G_DEBUG=fatal-criticals timeout 30s ./tests/test_evidence_preview_widget_gtk G_DEBUG=fatal-criticals timeout 30s ./tests/test_dialog_geometry_gtk +G_DEBUG=fatal-criticals timeout 30s ./tests/test_person_factual_relation_editor_gtk +G_DEBUG=fatal-criticals timeout 30s ./tests/test_document_authenticity_editor_gtk +G_DEBUG=fatal-criticals timeout 30s ./tests/test_person_ocr_projection_editor_gtk ``` Ils doivent cliquer sur les contrôles de production, passer par `MainWindow` @@ -655,10 +665,19 @@ Les tests doivent utiliser : Avant chaque commit : ```sh +make clean +make -j8 +make check-source-size +DISPLAY="$DISPLAY" WAYLAND_DISPLAY="$WAYLAND_DISPLAY" make -j8 test git diff --check git status --short ``` +Les validations ASan/UBSan ciblées produisent leurs binaires dans `/tmp`. +Lorsque `ASAN_OPTIONS=detect_leaks=0` est nécessaire sous l’environnement GTK +tracé, cela valide AddressSanitizer et UndefinedBehaviorSanitizer, pas +LeakSanitizer. + Inspecter tout nouveau fichier inhabituel à la racine du dépôt. --- @@ -729,5 +748,10 @@ Utiliser exclusivement une enquête, une base SQLite et des documents 6. dialogues à 1200 × 800 et 760 × 560 : parent transitoire, formulaire défilable, répartition 2/3–1/3 et actions toujours visibles. -La validation manuelle réussie confirme ces parcours, mais ne clôt pas le -ticket #109 et ne couvre pas les limitations encore listées dans la roadmap. +Contrôler aussi les sept étapes de l’assistant, les trois rubriques du résumé, +la projection vide par défaut et l’absence de relation automatique. + +La validation manuelle doit également couvrir la section V20 distincte de +l’authenticité. La justification des rôles sensibles et l’historisation +complète de l’identification sont des renforcements de cohérence, pas des +critères d’acceptation explicites du ticket #109. diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 2abc69c..f1abdc9 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -15,9 +15,9 @@ formulaire défilable, un aperçu redimensionnable et des actions fixes. La validation manuelle de ces parcours est réussie. Elle ne vaut pas achèvement du ticket #109. -> **Dernière mise à jour :** 2026-07-30 +> **Dernière mise à jour :** 2026-08-02 > **État du projet :** développement actif -> **Schéma SQLite courant :** V18 +> **Schéma SQLite courant :** V20 > **Usage opérationnel :** non prêt pour la production --- @@ -131,7 +131,7 @@ La branche `main` contient notamment : - création, validation et ouverture d'enquêtes ; - session d'enquête remplaçable proprement ; -- infrastructure SQLite et migrations jusqu'à V18 ; +- infrastructure SQLite et migrations jusqu'à V20 ; - couche Database, DAO et services métier ; - import de preuves avec copie contrôlée et SHA-256 ; - vérification d'intégrité et reclassement des preuves ; @@ -185,13 +185,15 @@ PARTIEL — VALIDATION MANUELLE DES TRANCHES LIVRÉES RÉUSSIE Limitations restantes : -1. l’OCR groupé de plusieurs preuves dans une même opération n’est pas pris en - charge ; -2. les interfaces GTK complètes des états d’identification et des rôles V18 - restent à construire ; -3. la provenance uniforme de tous les autres dérivés reste partielle ; +1. la justification des rôles sensibles et l’alimentation complète de + `person_identification_assessments` restent des renforcements de cohérence, + sans être des exigences explicites du ticket #109 ; +2. l’OCR groupé n’est volontairement pas pris en charge : après un import + multiple, chaque preuve est analysée individuellement ; +3. la provenance uniforme des dérivés hors parcours OCR reste partielle. -Ces limites interdisent de présenter le ticket #109 comme terminé. +La V20 couvre distinctement les états contractuels d’usage d’identité présumé +et confirmé. Une validation manuelle finale reste requise avant clôture. ### Ticket #107 — Pivot e-mail forensique diff --git a/docs/database/DATABASE_ARCHITECTURE.md b/docs/database/DATABASE_ARCHITECTURE.md index c12b2fb..7dd17f7 100644 --- a/docs/database/DATABASE_ARCHITECTURE.md +++ b/docs/database/DATABASE_ARCHITECTURE.md @@ -26,9 +26,12 @@ dernière table conserve personne, cible, valeurs précédente et nouvelle, preuve, OcrRun, champ OCR, qualité, stratégie, date et origine humaine. Un trigger refuse une source devenue non projectable. -> **Statut :** architecture courante -> **Version du schéma :** V19 -> **Dernière mise à jour :** 2026-07-24 +> **Statut :** architecture courante +V20 ajoute l’historique humain append-only +`document_identity_misuse_assessments`, distinct de l’authenticité. + +> **Version du schéma :** V20 +> **Dernière mise à jour :** 2026-08-02 > **Source de vérité détaillée :** `SCHEMA_AUDIT_CURRENT.md` --- @@ -71,7 +74,7 @@ docs/database/SCHEMA_AUDIT_CURRENT.md L'état réel du schéma est déterminé par : 1. les constantes de version dans le code ; -2. `database/schema_v1.sql` à `database/schema_v10.sql` ; +2. `database/schema_v1.sql` à `database/schema_v19.sql` ; 3. `database/schema_current.sql` ; 4. les fonctions d'installation et de migration ; 5. `tests/test_database.c` et les tests DAO ; @@ -242,6 +245,7 @@ database/schema_v7.sql database/schema_v8.sql database/schema_v9.sql database/schema_v10.sql +database/schema_v11.sql à database/schema_v19.sql ``` `database/schema_current.sql` contient des extensions ou réparations @@ -264,7 +268,7 @@ PRAGMA foreign_keys = ON ↓ BEGIN ↓ -installation V1 à V10 +installation du socle historique puis du schéma courant V20 ↓ application du schéma courant idempotent ↓ @@ -278,7 +282,7 @@ Un échec provoque un rollback. Une base neuve doit aboutir directement à : ```text -schema_version = 10 +schema_version = 19 ``` --- diff --git a/docs/database/SCHEMA_AUDIT_CURRENT.md b/docs/database/SCHEMA_AUDIT_CURRENT.md index 56bac8d..0796eb3 100644 --- a/docs/database/SCHEMA_AUDIT_CURRENT.md +++ b/docs/database/SCHEMA_AUDIT_CURRENT.md @@ -1,1581 +1,130 @@ -# Audit du schéma SQLite courant — V10 - -> [!IMPORTANT] -> Ce document décrit le schéma réellement présent sur la branche `main` au -> commit `613d2096bc5eeb2c1c4f60ae701292e19a2abe66`. -> -> Il distingue l’implémentation SQLite V10 du chantier fonctionnel plus large -> du pivot e-mail suivi dans le ticket Forgejo #107, qui reste ouvert. - -> **Projet :** Labfy Investigation -> **Date de l’audit :** 2026-07-24 -> **Branche auditée :** `main` publique sur Forgejo -> **Commit audité :** `613d2096bc5eeb2c1c4f60ae701292e19a2abe66` -> **Version de schéma confirmée par le code :** **V10** -> **Statut du document :** audit courant vérifié de la V10 -> **Périmètre :** schémas SQL, mécanisme de migration, couche Database, tests, -> documentation et tickets Forgejo associés - ---- - -# 1. Résumé exécutif - -Le schéma SQLite courant de Labfy Investigation est la **V10**. - -Cette conclusion est confirmée conjointement par : - -- `DATABASE_SCHEMA_VERSION_CURRENT 10` ; -- `DATABASE_SCHEMA_VERSION_CURRENT_TEXT "10"` ; -- `database/schema_v10.sql` ; -- `schema_install_v10()` ; -- `database_migrate_v9_to_v10()` ; -- le cas `9` de `database_migrate_to_latest()` ; -- l’installation de V10 dans `database_initialize()` ; -- les assertions V10 de `tests/test_database.c`. - -La V10 ajoute principalement : - -- la table `bank_account_entities` ; -- la conservation structurée des données IBAN, BIC et RIB ; -- un statut de vérification contrôlé ; -- une provenance contrôlée ; -- des liens facultatifs vers une preuve et une extraction ; -- neuf nouveaux types système de relations utiles au pivot e-mail et bancaire. - -Le mécanisme général reste cohérent : - -- la version est stockée dans `metadata.schema_version` ; -- une base plus récente que l’application est refusée ; -- chaque migration possède une fonction dédiée ; -- chaque migration s’exécute dans une transaction ; -- la version n’est mise à jour qu’après l’application du SQL ; -- un échec provoque un rollback ; -- une base neuve reçoit V1 à V10 dans une transaction initiale ; -- `schema_current.sql` complète les structures manquantes de manière - idempotente à l’ouverture. - -Les points les plus solides sont : - -- activation explicite des clés étrangères ; -- requêtes préparées pour les valeurs variables ; -- chaîne de migration explicite jusqu’à V10 ; -- migration V9 conservatrice des types de relations ; -- création V10 testée sur une base neuve ; -- migration V1 vers V10 vérifiée ; -- tests dédiés au vocabulaire contrôlé, aux propositions bancaires et au - pipeline EML. - -Les principaux points à renforcer sont : - -- absence d’une fixture dédiée V9 → V10 avec données bancaires ; -- absence d’un test provoquant un rollback de la migration V10 ; -- absence d’un `PRAGMA foreign_key_check` générique avant chaque commit de - migration ; -- duplication de la version courante sous forme numérique et textuelle ; -- duplication partielle de la structure V10 entre `schema_v10.sql` et - `schema_current.sql` ; -- documentation générale encore fortement marquée par l’historique V1 ; -- ticket #107 encore ouvert : la présence du schéma V10 ne signifie pas que le - pivot e-mail complet est terminé. - - ---- - -# 2. Méthode et hiérarchie des sources - -L’audit applique l’ordre de confiance suivant : - -1. code présent dans la branche auditée ; -2. scripts de schéma et fonctions de migration ; -3. tests automatisés ; -4. commits associés ; -5. tickets Forgejo fermés ; -6. documentation d’architecture ; -7. anciens audits et feuilles de route. - -Cette hiérarchie est nécessaire parce qu’un document historique peut décrire -une intention ou une ancienne version sans représenter l’état courant. - -## 2.1 Sources principales examinées - -### Schémas - -- `database/schema_v1.sql` -- `database/schema_v2.sql` -- `database/schema_v3.sql` -- `database/schema_v4.sql` -- `database/schema_v5.sql` -- `database/schema_v6.sql` -- `database/schema_v7.sql` -- `database/schema_v8.sql` -- `database/schema_v9.sql` -- `database/schema_v10.sql` -- `database/schema_current.sql` - -### Infrastructure SQLite - -- `src/database/database.c` -- `src/database/schema.c` -- `src/database/statement.c` -- `src/database/transaction.c` -- `include/database/database.h` -- `include/database/schema.h` - -### Accès métier et modèles - -- contenu de `include/dao/` -- contenu de `src/dao/` -- contenu de `include/models/` -- modules liés aux preuves, entités, relations, provenance OSINT, extractions - et positions du graphe - -### Tests - -- `tests/test_database.c` -- `tests/test_statement.c` -- `tests/test_transaction.c` -- tests des DAO et services visibles dans `tests/` -- tests des types canoniques de relations - -### Documentation et tickets - -- `docs/database/DATABASE_ARCHITECTURE.md` -- `docs/database/SCHEMA_AUDIT_V1.md` -- ticket Forgejo `#106` : normalisation des types de relations -- commit `8bc3b43d63` : centralisation des types de relations - -## 2.2 Limites de l’audit - -Cet audit est une analyse statique du dépôt public. - -Il n’a pas exécuté : - -- `make -j8` ; -- la suite de tests ; -- une migration réelle sur une base synthétique ; -- `PRAGMA integrity_check` sur une base V10 produite localement ; -- `PRAGMA foreign_key_check` sur une base V10 produite localement ; -- une comparaison binaire entre une base migrée et une base fraîche. - -Les procédures de vérification reproductible sont proposées plus loin. - ---- - -# 3. Source de vérité de la version - -## 3.1 Constante courante - -La source de vérité publique se trouve dans : - -```text -src/database/database.c -``` - -avec les deux définitions : - -```c -#define DATABASE_SCHEMA_VERSION_CURRENT 10 -#define DATABASE_SCHEMA_VERSION_CURRENT_TEXT "10" -``` - -La première sert aux comparaisons numériques. - -La seconde est enregistrée dans : - -```text -metadata.schema_version -``` - -## 3.2 Lecture de la version - -La fonction : - -```c -database_read_schema_version() -``` - -effectue les contrôles suivants : - -- prépare une requête vers `metadata` ; -- lie la clé `schema_version` ; -- refuse l’absence de valeur ; -- refuse une chaîne vide ; -- convertit la valeur en entier ; -- refuse une valeur non numérique ; -- refuse une version inférieure à 1 ; -- refuse une valeur supérieure à `G_MAXINT` ; -- vérifie qu’aucune seconde ligne n’est retournée. - -## 3.3 Base plus récente que l’application - -La fonction : - -```c -database_migrate_to_latest() -``` - -refuse une base dont la version est supérieure à : - -```c -DATABASE_SCHEMA_VERSION_CURRENT -``` - -Cela évite qu’une ancienne version de Labfy Investigation ouvre et modifie une -base créée par une version plus récente. - -## 3.4 Version inconnue ou non migrable - -La boucle de migration utilise un `switch` sur la version actuelle. - -Une version ancienne qui ne possède aucun chemin connu produit une erreur -d’état au lieu d’essayer une transformation implicite. - -## 3.5 Mise à jour de la version - -La fonction : - -```c -database_update_schema_version() -``` - -utilise une requête préparée et met à jour la clé `schema_version`. - -Dans chaque migration examinée, cette mise à jour intervient après -l’installation de la nouvelle structure et avant le `COMMIT`. - -En cas d’erreur, la transaction est annulée et la version ne doit pas avancer. - ---- - -# 4. Installation d’une base neuve - -La fonction : - -```c -database_initialize() -``` - -crée une base neuve dans une transaction initiale unique. - -L’ordre V10 observé est le suivant : - -1. ouverture de SQLite ; -2. activation de `PRAGMA foreign_keys = ON` ; -3. début de transaction ; -4. installation de V1 ; -5. installation de V2 ; -6. installation de V3 ; -7. installation de V4 ; -8. installation de V5 ; -9. installation de V6 ; -10. installation de V7 ; -11. installation de V8 ; -12. installation de V9 ; -13. installation de V10 ; -14. application de `schema_current.sql` ; -15. insertion des métadonnées, dont `schema_version = 10` ; -16. insertion de l’enquête ; -17. commit. - -Un échec provoque un rollback de l’ensemble. - -## Observation - -Une base neuve rejoue toute l’histoire du schéma. - -Cette méthode garantit qu’une base neuve et une base migrée traversent les mêmes -étapes. Elle impose cependant que chaque ancien script reste compatible avec le -moteur SQLite utilisé aujourd’hui. - -À moyen terme, un schéma de référence consolidé pourrait être envisagé pour les -bases neuves, tout en conservant les migrations historiques pour les anciennes -bases. Un tel changement exigerait une comparaison automatique entre le schéma -consolidé et le schéma obtenu par V1 → V10. - ---- - -# 5. Chaîne publique des migrations - -| Passage | Script | Fonction C | Objet principal | -|---|---|---|---| -| création | `schema_v1.sql` | `schema_install_v1()` | socle métier complet | -| V1 → V2 | `schema_v2.sql` | `schema_install_v2()` | renforcement des preuves | -| V2 → V3 | `schema_v3.sql` | `schema_install_v3()` | provenance OSINT | -| V3 → V4 | `schema_v4.sql` | `schema_install_v4()` | comptes sociaux | -| V4 → V5 | `schema_v5.sql` | `schema_install_v5()` | rôles des personnes | -| V5 → V6 | `schema_v6.sql` | `schema_install_v6()` | identité usurpée | -| V6 → V7 | `schema_v7.sql` | `schema_install_v7()` | extractions | -| V7 → V8 | `schema_v8.sql` | `schema_install_v8()` | viewport du graphe | -| V8 → V9 | `schema_v9.sql` + migration C | `schema_install_v9()` | types canoniques de relations | -| V9 → V10 | `schema_v10.sql` | `schema_install_v10()` | pivot e-mail et entités bancaires | -| complément | `schema_current.sql` | `schema_ensure_current()` | extensions idempotentes V10 | - -## 5.1 V1 — Socle métier - -La V1 crée notamment : - -### Métadonnées - -- `metadata` -- `investigation` - -### Classification et référentiels - -- `categories` -- `tags` -- `types_preuve` -- `types_entite` -- `types_source` -- `types_outil` - -### Collecte - -- `sources` -- `preuves` -- `recherches` - -### Connaissance - -- `entites` -- `relations` - -### Raisonnement et traçabilité - -- `hypotheses` -- `chronologie` -- `journal` - -### Tables de liaison - -- `tag_preuves` -- `tag_recherches` -- `tag_entites` -- `tag_relations` -- `tag_hypotheses` -- `tag_chronologie` -- `recherche_preuves` -- `recherche_entites` -- `preuve_entites` -- `relation_preuves` -- `recherche_relations` -- `recherche_chronologie` -- `preuve_chronologie` -- `entite_chronologie` -- `relation_chronologie` -- `hypothese_preuves` -- `hypothese_entites` -- `hypothese_relations` -- `recherche_hypotheses` - -La V1 constitue un schéma étendu, et non un simple prototype à deux tables. - -## 5.2 V2 — Renforcement des preuves - -La V2 ajoute à `preuves` : - -- `original_name` ; -- `collected_at` ; -- `source` ; -- `integrity_status`. - -Elle effectue également : - -- un backfill de `original_name` depuis `name` ; -- la création de `idx_preuves_imported_at` ; -- un trigger de validation à l’insertion ; -- un trigger de validation à la mise à jour. - -Les triggers renforcent notamment : - -- le nom original obligatoire ; -- la taille non négative ; -- le SHA-256 en minuscules sur 64 caractères ; -- la plage valide de `integrity_status`. - -## 5.3 V3 — Provenance OSINT structurée - -La V3 ajoute : - -- `osint_executions` -- `osint_execution_entities` -- `osint_execution_relations` - -La table principale conserve notamment : - -- l’outil ; -- sa version ; -- l’action ; -- la sélection d’origine ; -- la cible ; -- les arguments ; -- les dates de début et de fin ; -- le code de sortie ; -- l’état final ; -- les sorties standard et erreur brutes ; -- le SHA-256 de la sortie. - -Les tables de liaison indiquent si les entités ou relations ont été créées ou -réutilisées. - -## 5.4 V4 — Comptes sociaux - -La V4 : - -- ajoute les types TikTok, X, Telegram et compte social générique ; -- crée `comptes_sociaux`. - -Cette table est une extension spécialisée d’une entité et conserve : - -- la plateforme ; -- l’URL du profil ; -- le pseudonyme ; -- un identifiant de plateforme facultatif ; -- la première observation ; -- l’état du compte ; -- des notes. - -## 5.5 V5 — Rôles des personnes - -La V5 crée `person_roles`. - -Le rôle est limité à une liste contrôlée comprenant notamment : - -- non catégorisé ; -- escroc présumé ; -- victime ; -- témoin ; -- suspect ; -- personne liée. - -## 5.6 V6 — Identité usurpée - -La V6 reconstruit `person_roles` afin d’ajouter : - -```text -impersonated_identity -``` - -Elle : - -1. renomme la table V5 ; -2. crée la nouvelle table ; -3. recopie les données ; -4. supprime l’ancienne table ; -5. recrée l’index. - -Cette migration est sensible aux clés étrangères et doit rester couverte par un -test de migration réel. - -## 5.7 V7 — Extractions - -La V7 crée `extractions`. - -Elle conserve : - -- l’identifiant de l’extraction ; -- une preuve associée facultative ; -- le type de source ; -- l’identifiant de la source ; -- l’outil ; -- la date de création. - -La provenance est partiellement polymorphe via : - -```text -source_kind -source_id -``` - -SQLite ne peut pas imposer directement une clé étrangère vers plusieurs tables -possibles. La cohérence de `source_id` dépend donc aussi de la couche métier. - -## 5.8 V8 — État du viewport - -La V8 crée `graph_viewport`. - -La table ne peut contenir qu’une ligne : - -```text -id = 1 -``` - -Elle persiste : - -- le zoom ; -- le décalage horizontal ; -- le décalage vertical ; -- la date de mise à jour. - -Il s’agit d’un état de présentation, pas d’une donnée métier. - -## 5.9 V9 — Types canoniques de relations - -La V9 crée `relation_types` avec : - -- un identifiant entier ; -- un code métier stable facultatif ; -- un libellé canonique ; -- une clé normalisée unique ; -- une description facultative ; -- un indicateur système. - -Elle insère dix types système initiaux, dont : - -- `resolves_to` -- `aliases_to` -- `uses_name_server` -- `links_to` -- `sends` -- `uses` -- `controls` -- `owns` -- `knows` -- `redirects_to` - -La migration ne se limite pas au fichier SQL. - -La fonction : - -```c -schema_v9_migrate_relation_types() -``` - -réalise également les opérations suivantes : - -1. ajoute `relations.relation_type_id` si nécessaire ; -2. parcourt les anciennes relations dans un ordre déterministe ; -3. normalise l’ancien texte `type_relation` ; -4. recherche un type existant par code ou clé normalisée ; -5. crée un type personnalisé lorsqu’aucun type ne correspond ; -6. rattache chaque relation au type canonique ; -7. crée l’index d’unicité canonique ; -8. crée l’index sur `relation_type_id` ; -9. crée des triggers interdisant un type canonique nul ; -10. exécute `PRAGMA foreign_key_check`. - -La colonne historique : - -```text -relations.type_relation -``` - -reste présente pour compatibilité. - -Depuis V9, l’identité logique du type doit être : - -```text -relations.relation_type_id -``` - -et non le texte historique. - - - -## 5.10 V10 — Pivot e-mail et entités bancaires - -La V10 crée : - -```text -bank_account_entities -``` - -Cette table conserve : - -- un UUID métier ; -- l’IBAN normalisé ; -- le BIC facultatif ; -- le nom du titulaire ; -- le nom et l’adresse de la banque ; -- le code pays ; -- le code banque ; -- le code guichet ; -- le numéro de compte ; -- la clé RIB ; -- le statut de vérification ; -- le type de provenance ; -- une preuve source facultative ; -- une extraction source facultative ; -- les dates de création et de mise à jour. - -Les statuts autorisés sont : - -```text -proposed -confirmed -rejected -conflicted -invalid -``` - -Les provenances autorisées sont : - -```text -observed -ocr -header -metadata -derived -manual -``` - -Les références vers `preuves` et `extractions` utilisent `ON DELETE SET NULL`. -La disparition d’un objet source ne supprime donc pas automatiquement la donnée -bancaire structurée. - -La V10 ajoute également les index : - -```text -idx_bank_account_entities_iban -idx_bank_account_entities_evidence -``` - -Elle insère neuf types système de relations : - -```text -sent_from -sent_to -reply_to -has_attachment -relayed_by -uses_domain -held_at -named_as_holder_of -supports -``` - -Ces codes techniques sont destinés à rester stables, tandis que les libellés -français peuvent évoluer indépendamment. - -### Migration V9 vers V10 - -La fonction : - -```c -database_migrate_v9_to_v10() -``` - -exécute atomiquement : - -1. `database_transaction_begin()` ; -2. `schema_install_v10()` ; -3. `database_update_schema_version(database, "10")` ; -4. `database_transaction_commit()`. - -En cas d’échec, elle appelle `database_transaction_rollback()`. - -### Portée fonctionnelle - -La présence du schéma V10 ne signifie pas que l’ensemble du ticket #107 est -terminé. - -Le ticket reste ouvert et couvre un périmètre beaucoup plus large : - -- analyse complète des en-têtes EML ; -- extraction MIME sécurisée ; -- OCR ; -- métadonnées ExifTool ; -- interface de révision ; -- intégration transactionnelle des propositions confirmées ; -- création ou réutilisation d’entités et de relations ; -- conservation complète de la provenance. - -La V10 constitue donc un socle persistant du pivot e-mail, pas la preuve de -l’achèvement de tout le flux fonctionnel. ---- - -# 6. Rôle de `schema_current.sql` - -`schema_current.sql` est présenté comme un ensemble d’extensions idempotentes -du schéma courant V10. - -Il crée si nécessaire : - -- `graph_node_positions` -- `extractions` -- `graph_layout_positions` -- `graph_viewport` -- `relation_types` -- `bank_account_entities` - -Il réinsère également, avec `INSERT OR IGNORE`, les types système de relations -ajoutés par V10. - -Il ajoute également deux triggers nettoyant les positions de graphe orphelines -après suppression d’une entité ou d’une relation. - -## 6.1 Migration des positions - -Le script copie les anciennes positions : - -```sql -INSERT OR IGNORE INTO graph_layout_positions (...) -SELECT ... FROM graph_node_positions; -``` - -puis exécute : - -```sql -DELETE FROM graph_node_positions; -``` - -L’objectif est de migrer l’ancien état limité aux entités vers une disposition -générique acceptant aussi les relations. - -## 6.2 Point d’attention - -Le script est exécuté après les migrations lors de l’ouverture. - -Il contient donc à la fois : - -- des créations idempotentes ; -- une migration de données de présentation ; -- une suppression des anciennes lignes. - -Le comportement peut être légitime, mais il doit être explicitement couvert par -des tests vérifiant plusieurs exécutions successives afin de garantir : - -- l’absence de perte de positions courantes ; -- l’absence de réimport d’anciennes coordonnées ; -- la stabilité après plusieurs ouvertures ; -- le nettoyage correct des positions orphelines. - ---- - -# 7. Inventaire statique du schéma V10 - -L’application statique des scripts versionnés et de `schema_current.sql` produit **46 tables distinctes**. - -Ce nombre est dérivé des scripts, et doit être confirmé sur une base générée par -une requête sur `sqlite_master`. - -## 7.1 Métadonnées - -| Table | Rôle | -|---|---| -| `metadata` | version et informations techniques | -| `investigation` | enquête unique contenue dans la base | - -## 7.2 Référentiels et classification - -| Table | Rôle | -|---|---| -| `categories` | classement principal | -| `tags` | annotations multiples | -| `types_preuve` | types de preuves | -| `types_entite` | types d’entités | -| `types_source` | types de sources | -| `types_outil` | types d’outils | -| `relation_types` | types canoniques de relations | - -## 7.3 Données métier principales - -| Table | Rôle | -|---|---| -| `sources` | origine d’une information | -| `preuves` | métadonnées des fichiers collectés | -| `recherches` | actions d’investigation | -| `entites` | objets identifiés | -| `relations` | liens orientés entre entités | -| `hypotheses` | raisonnements provisoires | -| `chronologie` | événements de l’enquête | -| `journal` | journal technique | - -## 7.4 Extensions spécialisées - -| Table | Version | Rôle | -|---|---:|---| -| `osint_executions` | V3 | provenance des traitements OSINT | -| `osint_execution_entities` | V3 | entités créées ou réutilisées | -| `osint_execution_relations` | V3 | relations créées ou réutilisées | -| `comptes_sociaux` | V4 | données propres aux comptes sociaux | -| `person_roles` | V5/V6 | rôle d’enquête d’une personne | -| `extractions` | V7 | provenance d’une extraction | -| `graph_viewport` | V8 | zoom et position du canevas | -| `graph_node_positions` | courant | ancien stockage des positions d’entités | -| `graph_layout_positions` | courant | positions génériques entités/relations | -| `bank_account_entities` | V10 | données bancaires structurées et vérifiables | - -## 7.5 Tables de liaison V1 - -| Domaine | Tables | -|---|---| -| tags | `tag_preuves`, `tag_recherches`, `tag_entites`, `tag_relations`, `tag_hypotheses`, `tag_chronologie` | -| recherches | `recherche_preuves`, `recherche_entites`, `recherche_relations`, `recherche_chronologie`, `recherche_hypotheses` | -| preuves | `preuve_entites`, `preuve_chronologie`, `relation_preuves` | -| chronologie | `entite_chronologie`, `relation_chronologie` | -| hypothèses | `hypothese_preuves`, `hypothese_entites`, `hypothese_relations` | - ---- - -# 8. Intégrité et sécurité des données - -## 8.1 Clés étrangères - -`database_open()` exécute : - -```sql -PRAGMA foreign_keys = ON; -``` - -Les stratégies observées sont : - -- `CASCADE` pour les objets strictement dépendants ; -- `RESTRICT` lorsque la suppression risquerait de casser l’historique ; -- `SET NULL` pour les références facultatives. - -## 8.2 Requêtes préparées - -Les valeurs variables de la couche Database et des DAO utilisent les fonctions -de préparation et de liaison. - -Les requêtes statiques sans donnée utilisateur peuvent être exécutées avec -`sqlite3_exec()`. - -La règle à conserver est : - -> aucune valeur externe ou utilisateur ne doit être concaténée dans une chaîne -> SQL. - -Le caractère append-only du journal ne constitue jamais une exception à cette -règle. - -## 8.3 Transactions - -Les migrations V1 à V10 sont pilotées par des fonctions dédiées. - -Chaque passage de version : - -- démarre une transaction ; -- applique le changement ; -- met à jour la version ; -- commit en cas de succès ; -- rollback en cas d’échec. - -La création d’une base neuve est également atomique. - -## 8.4 Preuves - -Le schéma et la couche métier conservent notamment : - -- UUID ; -- chemin relatif ; -- nom interne ; -- nom original ; -- taille ; -- SHA-256 ; -- type MIME ; -- date du fichier ; -- date de collecte ; -- date d’import ; -- statut d’intégrité ; -- statut logique. - -Le fichier original reste dans l’arborescence de l’enquête. SQLite conserve ses -métadonnées et ses relations. - -## 8.5 Suppression logique - -Plusieurs objets V1 utilisent des statuts tels que : - -```text -active -archived -deleted -``` - -Certaines suppressions physiques existent néanmoins dans les DAO, notamment -pour les relations. - -Le document d’architecture doit donc éviter d’affirmer que toute suppression -est systématiquement logique. La stratégie réelle doit être documentée table -par table. - -## 8.6 Journal et chronologie - -Le schéma distingue : - -- `chronologie` : événements significatifs de l’enquête ; -- `journal` : trace technique des actions de l’application. - -Le journal est conçu comme append-only dans la documentation, mais cet audit -n’a pas identifié de trigger SQLite interdisant une mise à jour ou une -suppression. Cette propriété repose donc actuellement au moins en partie sur la -couche applicative. - ---- - -# 9. Correspondance SQL, modèles, DAO et tests - -| Domaine | Table principale | Modèle ou structure | DAO/service observé | Tests observés | -|---|---|---|---|---| -| enquête | `investigation` | `InvestigationRecord` | `InvestigationDao` | `test_investigation_record`, `test_investigation_dao` | -| preuves | `preuves` | `EvidenceRecord` | `EvidenceDao`, `EvidenceTypeDao` | nombreux tests preuve/import/intégrité | -| preuve-entité | `preuve_entites` | — | `EvidenceEntityDao` | `test_evidence_entity_dao` | -| entités | `entites` | `EntityRecord` | `EntityDao`, `EntityTypeDao` | tests modèle et DAO | -| relations | `relations` | `RelationRecord` | `RelationDao`, `RelationService` | tests DAO, modèle et service | -| types de relation | `relation_types` | `RelationType` | `RelationTypeDao`, `RelationTypeService` | normalisation et service | -| relation-preuve | `relation_preuves` | — | `RelationEvidenceDao` | `test_relation_evidence_dao` | -| provenance OSINT | `osint_executions` | `OsintExecutionRecord` | `OsintExecutionDao` | DAO et intégrité | -| extractions | `extractions` | contexte métier | `ExtractionDao`, service de dépôt | `test_extraction_drop_service` | -| positions graphe | tables de graphe | `GraphNodePosition`, layout | `GraphNodePositionDao` | `test_graph_node_position_dao` | -| comptes sociaux | `comptes_sociaux` | plateforme sociale | service compte social | `test_social_account_service` | -| personnes | `person_roles` | extension d’entité | service personne | `test_person_entity_service` | -| comptes bancaires | `bank_account_entities` | `BankProposal` | pipeline EML et analyse bancaire ; DAO dédié non identifié dans les fichiers inspectés | `test_bank_proposal`, `test_eml_pipeline_task`, présence de table dans `test_database` | - -## Observation - -La V1 contient davantage de domaines que les DAO publics actuellement exposés. - -Aucun DAO dédié n’a été observé dans `include/dao/` pour plusieurs tables, dont : - -- `sources` ; -- `recherches` ; -- `hypotheses` ; -- `chronologie` ; -- `journal` ; -- `categories` ; -- `tags`. - -Cela ne signifie pas que ces tables sont inutilisables. Cela indique seulement -qu’aucune interface DAO publique dédiée n’est visible dans le dossier audité au -commit indiqué. - ---- - -# 10. Couverture de tests observée - -## 10.1 Points couverts par `test_database.c` - -Le fichier vérifie notamment : - -- l’initialisation d’une base valide ; -- le rollback d’une initialisation défaillante ; -- la présence de la version `10` dans une base neuve ; -- la présence de `bank_account_entities` ; -- la présence de plusieurs tables ajoutées après V1 ; -- la migration d’une base V1 vers la version courante V10 ; -- la conservation d’une preuve V1 ; -- le backfill V2 de `original_name` ; -- l’ajout des colonnes V2 ; -- l’ajout des triggers V2 ; -- le rollback complet d’une migration V2 provoquée en échec ; -- `PRAGMA integrity_check` après le rollback. - -## 10.2 Tests spécialisés observés - -Le dépôt possède également des tests pour : - -- les preuves et leur intégrité ; -- les entités ; -- les relations ; -- les types canoniques de relations ; -- les exécutions OSINT ; -- les extractions ; -- les positions du graphe ; -- les comptes sociaux ; -- les rôles des personnes ; -- le vocabulaire contrôlé ; -- l’analyse et la validation des propositions IBAN/BIC ; -- le pipeline EML asynchrone. - -## 10.3 Lacunes de couverture à traiter - -Le test principal de migration ne fournit pas une fixture indépendante pour -chaque version V2 à V10. - -Aucun test dédié nommé V9 → V10 n’a été identifié dans `test_database.c`. -La création d’une base neuve vérifie bien la présence de la table V10, et la -fixture V1 atteint bien la version 10, mais cela ne remplace pas une migration -V9 réaliste contenant des relations, extractions, preuves et données bancaires. - -Matrice recommandée : - -| Base d’entrée | Migration | Vérifications minimales | -|---|---|---| -| V1 | V1 → V10 | données, version, FK, intégrité | -| V2 | V2 → V10 | preuve V2 conservée | -| V3 | V3 → V10 | provenance OSINT conservée | -| V4 | V4 → V10 | comptes sociaux conservés | -| V5 | V5 → V10 | rôles conservés pendant reconstruction V6 | -| V6 | V6 → V10 | identité usurpée conservée | -| V7 | V7 → V10 | extractions conservées | -| V8 | V8 → V10 | viewport et positions conservés | -| V9 | V9 → V10 | relations canoniques conservées, table bancaire créée | -| V10 | réouverture | idempotence de `schema_current.sql` | - -Chaque fixture devrait vérifier au minimum : - -```sql -PRAGMA integrity_check; -PRAGMA foreign_key_check; -SELECT value FROM metadata WHERE key = 'schema_version'; -``` - ---- - -# 11. Constats d’audit - -## AUD-001 — Documentation courante restée en V1 - -**Sévérité : élevée** - -`docs/database/DATABASE_ARCHITECTURE.md` s’annonce encore comme : - -```text -Statut : Stable (V1) -Schéma : V1 -``` - -alors que le code utilise V10. - -### Risque - -- confusion des développeurs ; -- erreurs des agents locaux ; -- mauvaise interprétation de l’état du projet ; -- décisions basées sur des structures historiques ; -- oubli des tables V2 à V10. - -### Action recommandée - -Créer : - -```text -docs/database/SCHEMA_AUDIT_CURRENT.md -``` - -et déplacer les audits historiques vers : - -```text -docs/database/audits/SCHEMA_AUDIT_V1.md -docs/database/audits/SCHEMA_AUDIT_V9.md -``` - -Le document courant doit indiquer le commit exact qu’il audite. - ---- - -## AUD-002 — Couverture spécifique V9 → V10 insuffisante - -**Sévérité : élevée** - -La V10 est bien publiée et installée par le code. Les tests confirment : - -- une base neuve en version 10 ; -- la présence de `bank_account_entities` ; -- une migration V1 atteignant la version 10. - -Aucune fixture dédiée V9 → V10 n’a toutefois été identifiée. - -### Risque - -Une régression propre au passage V9 → V10 pourrait ne pas être détectée, -notamment sur : - -- les relations canoniques V9 ; -- les références vers `preuves` et `extractions` ; -- l’idempotence des nouveaux types de relations ; -- le rollback après création partielle de la table bancaire. - -### Action recommandée - -Ajouter un test synthétique V9 → V10 qui vérifie : - -1. la conservation des objets V9 ; -2. la création de `bank_account_entities` ; -3. l’unicité des types de relations insérés ; -4. `PRAGMA integrity_check` ; -5. `PRAGMA foreign_key_check` ; -6. le rollback complet d’une migration V10 volontairement mise en échec. - ---- - -## AUD-003 — Version courante dupliquée - -**Sévérité : moyenne** - -La version est définie deux fois : - -```c -DATABASE_SCHEMA_VERSION_CURRENT -DATABASE_SCHEMA_VERSION_CURRENT_TEXT -``` - -Les tests comparent aussi directement la chaîne `"10"`. - -### Risque - -Une future migration peut modifier une valeur et oublier l’autre. - -### Action recommandée - -Conserver une seule constante numérique et produire la représentation textuelle -au moment de l’écriture, ou centraliser les deux valeurs dans un fichier -d’interface unique couvert par une assertion de test. - ---- - -## AUD-004 — `schema_current.sql` mélange plusieurs responsabilités - -**Sévérité : moyenne** - -Le fichier sert à la fois à : - -- créer des structures manquantes ; -- maintenir la compatibilité ; -- migrer des positions ; -- supprimer les anciennes positions ; -- installer des triggers ; -- recréer la table bancaire V10 si elle manque ; -- réinsérer les types système du pivot e-mail. - -### Risque - -Une opération supposée idempotente peut finir par modifier des données à chaque -ouverture. - -### Action recommandée - -Documenter chaque opération et ajouter un test exécutant -`schema_ensure_current()` plusieurs fois sur la même fixture. - ---- - -## AUD-005 — Contrôle des clés étrangères non généralisé - -**Sévérité : moyenne** - -La migration V9 exécute explicitement : - -```sql -PRAGMA foreign_key_check; -``` - -Aucun contrôle générique similaire n’a été observé dans le pilote commun de -toutes les migrations. - -### Action recommandée - -Exécuter un contrôle générique avant le commit de chaque migration, ou fournir -une justification documentée lorsqu’une migration ne le nécessite pas. - ---- - -## AUD-006 — Couverture de migration incomplète par version - -**Sévérité : élevée** - -Le test principal couvre : - -- création d’une base neuve ; -- migration V1 vers la version courante ; -- rollback V2. - -Il ne constitue pas une matrice complète de fixtures V2 à V10. - -### Action recommandée - -Ajouter une fixture synthétique minimale pour chaque version publiée et migrer -chacune vers la version courante. - ---- - -## AUD-007 — Absence de sauvegarde préalable observée - -**Sévérité : élevée pour des enquêtes réelles** - -Aucun appel de sauvegarde SQLite ou copie préalable n’a été observé dans -`database_migrate_to_latest()`. - -La transaction protège la cohérence logique, mais elle ne remplace pas une -copie de sécurité face à : - -- panne disque ; -- interruption brutale ; -- défaut SQLite ou système de fichiers ; -- erreur de migration non anticipée ; -- corruption déjà présente. - -### Action recommandée - -Avant toute migration d’une base existante : - -1. fermer ou stabiliser les accès concurrents ; -2. créer une sauvegarde cohérente ; -3. vérifier sa création ; -4. lancer la migration ; -5. conserver la sauvegarde tant que la validation n’est pas terminée. - -Cette fonctionnalité doit être testée uniquement sur des bases synthétiques. - ---- - -## AUD-008 — Ancienne et nouvelle identité du type de relation coexistent - -**Sévérité : moyenne** - -V9 conserve : - -```text -relations.type_relation -``` - -et ajoute : - -```text -relations.relation_type_id -``` - -La documentation du commit précise que la première colonne ne doit plus servir -d’identité. - -### Risque - -Un module ancien peut encore filtrer ou détecter les doublons par texte. - -### Action recommandée - -Auditer tous les producteurs et lecteurs de relations, puis ajouter un test -interdisant toute régression vers `type_relation` comme clé métier. - -La colonne historique pourra être supprimée dans une future migration seulement -après validation de toutes les anciennes bases et de tous les adaptateurs. - ---- - -## AUD-009 — Append-only non imposé par SQLite - -**Sévérité : faible à moyenne** - -Le journal est documenté comme append-only, mais aucun trigger interdisant -`UPDATE` ou `DELETE` n’a été identifié dans les scripts examinés. - -### Action recommandée - -Choisir explicitement l’une des deux politiques : - -- garantie applicative documentée et testée ; -- garantie SQLite par triggers de refus. - -Le document courant doit dire laquelle est réellement retenue. - ---- - -## AUD-010 — Paramètres SQLite de durabilité non documentés dans le code audité - -**Sévérité : à évaluer** - -Aucun réglage explicite n’a été identifié dans `database.c` pour : - -- `journal_mode` ; -- `synchronous` ; -- `busy_timeout`. - -SQLite applique donc probablement ses valeurs par défaut, sauf réglage réalisé -ailleurs. - -### Action recommandée - -Documenter volontairement les paramètres retenus et leurs conséquences avant -un usage opérationnel. - -Ne pas activer WAL ou modifier `synchronous` sans étudier la portabilité d’une -enquête et les procédures de copie de ses fichiers annexes. - ---- - -# 12. État de validation de la V10 - -## 12.1 Implémentation confirmée - -Les éléments suivants sont présents au commit audité : - -- `database/schema_v10.sql` ; -- déclaration de `schema_install_v10()` ; -- implémentation de `schema_install_v10()` ; -- `database_migrate_v9_to_v10()` ; -- cas `9` dans `database_migrate_to_latest()` ; -- installation V10 dans `database_initialize()` ; -- version numérique `10` ; -- version textuelle `"10"` ; -- table `bank_account_entities` ; -- index IBAN et preuve ; -- nouveaux types système de relations ; -- mise à jour de `schema_current.sql` ; -- mise à jour de `test_database.c`. - -## 12.2 Validation partielle confirmée - -Les tests présents couvrent : - -- la création d’une base neuve V10 ; -- la présence de `bank_account_entities` ; -- l’arrivée d’une ancienne base V1 en version 10 ; -- le vocabulaire contrôlé ; -- l’analyse de propositions bancaires ; -- un scénario de pipeline EML. - -## 12.3 Validation encore recommandée - -Restent à ajouter ou confirmer : - -- fixture directe V9 → V10 ; -- rollback provoqué de la migration V10 ; -- conservation de données V9 riches ; -- vérification des deux clés étrangères de `bank_account_entities` ; -- contrôle de doublon des types système après ouvertures répétées ; -- `PRAGMA foreign_key_check` avant validation de V10 ; -- DAO ou service de persistance clairement identifié pour - `bank_account_entities` ; -- test de réouverture multiple de `schema_current.sql` ; -- validation complète des critères du ticket #107. - -## 12.4 Statut fonctionnel - -Le **schéma V10 est implémenté**. - -Le **pivot e-mail complet n’est pas déclaré terminé** : le ticket #107 est -toujours ouvert et couvre un périmètre supérieur à la seule migration SQLite. - ---- - -# 13. Procédure de vérification reproductible - -> Utiliser uniquement une copie synthétique. -> Ne jamais exécuter cette procédure sur une base d’enquête réelle sans -> sauvegarde et validation préalable. - -## 13.1 Compilation - -```bash +# Audit du schéma SQLite courant — V20 + +> **Projet :** Labfy Investigation +> **Date de l’audit :** 2026-08-02 +> **Branche auditée :** `main` +> **Version courante :** V20 +> **Statut :** audit courant ; les audits V9 et V10 restent archivés dans +> `docs/database/audits/`. + +## 1. Sources de vérité + +La version 20 est définie par `DATABASE_SCHEMA_VERSION_CURRENT` et +`DATABASE_SCHEMA_VERSION_CURRENT_TEXT` dans `src/database/database.c`. +L’état courant est décrit par `database/schema_current.sql`; la migration +V18→V19 est `database/schema_v19.sql` et V19→V20 est +`database/schema_v20.sql`. Les fonctions publiques correspondantes +sont déclarées dans `include/database/schema.h` et implémentées dans +`src/database/schema.c`. + +Une base neuve reçoit directement le schéma courant V20 dans la transaction +d’initialisation. Une base historique est migrée séquentiellement ; le passage +chaque migration installe son script et ne met à jour `metadata.schema_version` +qu’avant le `COMMIT`. Un échec entraîne un `ROLLBACK`. + +## 2. Extensions d’identité V18 conservées + +V18 ajoute les vocabulaires `identification_status_vocabulary` et +`person_role_vocabulary`, les évaluations humaines +`document_authenticity_assessments`, les relations factuelles +`person_evidence_factual_relations` et l’historique +`person_identification_assessments`. + +`identity_field_observations` conserve séparément : + +- `raw_value`, immuable après extraction ; +- `normalized_value` ; +- `corrected_value` ; +- `confirmed_value` ; +- `confirmation_state` ; +- `value_quality`. + +Les triggers V18 refusent une confirmation sans action humaine ainsi que les +valeurs rejetées, en conflit, incertaines ou invalides. Les clés et triggers +vérifient aussi la cohérence preuve/OcrRun des évaluations d’authenticité et +des relations factuelles. + +## 3. Extension V19 + +### `person_profile_fields` + +Cette table porte les champs structurés d’une personne : + +- `declared_name` ; +- `surname` ; +- `given_names` ; +- `birth_date` ; +- `birth_place` ; +- `nationality` ; +- `sex_as_printed` ; +- `address_as_printed`. + +La clé primaire `(person_id, field_code)`, la liste fermée des codes, la valeur +non vide et la clé étrangère vers `entites` bornent les écritures. + +### `person_ocr_field_projections` + +Chaque ligne append-only conserve : personne, champ cible, valeur précédente, +nouvelle valeur, preuve, OcrRun, champ OCR, code OCR, qualité, statut de +révision, stratégie, date UTC et origine humaine. Les stratégies persistées +sont `fill_empty` et `replace_existing`; les qualités sont `complete` ou +`partial`; les statuts sont `accepted` ou `modified`. + +L’index `idx_person_projection_person` sert la consultation chronologique. +Le trigger `projection_v19_consistency` relit le champ OCR lors de l’insertion +et refuse toute source devenue non projectable ou incohérente. + +## 4. Atomicité et provenance + +La V20 ajoute `document_identity_misuse_assessments`. Les trois états fermés +sont `indeterminate`, `presumed` et `confirmed`. Une justification est imposée +pour les deux derniers. Les triggers vérifient l’OcrRun, la chaîne précédente, +interdisent les branches ainsi que tout `UPDATE` ou `DELETE`. L’origine +persistée est exclusivement `human`. + +`PersonCreationCoordinator` englobe dans une transaction la personne, ses +rôles, les preuves et rattachements, les observations OCR, les relations +factuelles explicites, les champs structurés et leur provenance V19. Les +copies définitives de preuves disposent d’une compensation si SQLite échoue. +Le service de projection peut rejoindre une transaction existante ou en ouvrir +une lorsqu’il est appelé seul. + +Les historiques d’authenticité, de relations factuelles et de projections ne +sont ni remplacés ni supprimés silencieusement. Les originaux et les lignes +OCR sources restent inchangés par une projection. + +## 5. Création, migration et réouverture vérifiées + +Les tests couvrent : + +- création directe d’une base V20 et présence des extensions V19/V20 ; +- migration historique jusqu’à V20 ; +- migration V17→V18 avec conservation OCR ; +- migration V18→V19 ; +- migration V19→V20 et rollback en cas d’échec ; +- réouverture SQLite ; +- cohérence preuve/OcrRun/champ ; +- source devenue obsolète ; +- échec au milieu d’une série ; +- rollback sans profil ni provenance partiels. + +Commandes de validation : + +```sh make clean make -j8 -make -j8 test +make check-source-size +DISPLAY="$DISPLAY" WAYLAND_DISPLAY="$WAYLAND_DISPLAY" make -j8 test git diff --check ``` -En cas d’échec provoqué par la parallélisation, revenir temporairement à : +Les fixtures sont exclusivement synthétiques (`SPECIMEN`) et les bases sont +créées dans des répertoires temporaires. -```bash -make -make test -``` +## 6. Limites fonctionnelles hors schéma -## 13.2 Création d’une base synthétique - -Créer une enquête de test par l’API normale de l’application ou par un test -dédié, puis vérifier : - -```bash -sqlite3 /chemin/test/Enquete.sqlite \ - "SELECT value FROM metadata WHERE key = 'schema_version';" - -sqlite3 /chemin/test/Enquete.sqlite \ - "PRAGMA integrity_check;" - -sqlite3 /chemin/test/Enquete.sqlite \ - "PRAGMA foreign_key_check;" -``` - -Résultats attendus : - -```text - -ok - -``` - -## 13.3 Inventaire du schéma - -```bash -sqlite3 /chemin/test/Enquete.sqlite \ - "SELECT type, name - FROM sqlite_master - WHERE name NOT LIKE 'sqlite_%' - ORDER BY type, name;" -``` - -## 13.4 Vérification de l’idempotence - -1. ouvrir la base ; -2. fermer la base ; -3. enregistrer un dump du schéma et les données de présentation ; -4. rouvrir la base plusieurs fois ; -5. comparer les résultats ; -6. vérifier les positions du graphe ; -7. vérifier les types de relations ; -8. exécuter à nouveau les deux PRAGMA d’intégrité. - -## 13.5 Vérification de migration - -Pour chaque fixture V1 à V10 : - -1. calculer une copie de travail ; -2. relever la version initiale ; -3. compter les objets métier ; -4. migrer ; -5. vérifier la version finale ; -6. comparer les UUID ; -7. comparer les empreintes des preuves ; -8. comparer les liaisons ; -9. vérifier les contraintes ; -10. vérifier le rollback sur une copie volontairement invalide. - ---- - -# 14. Règle de maintenance proposée - -Toute nouvelle version du schéma doit livrer ensemble : - -```text -migration SQL -+ fonction d’installation -+ fonction de migration -+ incrément de version -+ tests de base neuve -+ tests d’ancienne base -+ test de rollback -+ contrôle d’intégrité -+ mise à jour de SCHEMA_AUDIT_CURRENT.md -+ ticket Forgejo -+ commit vérifiable -``` - -Un ticket de migration ne doit pas être fermé tant que cette chaîne n’est pas -complète. - -## Statuts documentaires obligatoires - -Chaque table, colonne ou fonctionnalité mentionnée dans la documentation devrait -porter l’un des statuts suivants : - -```text -IMPLÉMENTÉ -PARTIELLEMENT IMPLÉMENTÉ -DOCUMENTÉ MAIS NON IMPLÉMENTÉ -HISTORIQUE -ABANDONNÉ -``` - -Cette règle évitera de confondre : - -- une architecture cible ; -- un ancien audit ; -- une fonctionnalité réellement présente ; -- un ticket encore ouvert ; -- une migration locale non publiée. - ---- - -# 15. Conclusion - -Le schéma V10 repose sur une base technique sérieuse : - -- modèle relationnel riche ; -- versionnement explicite ; -- migrations transactionnelles ; -- provenance OSINT ; -- contraintes sur les preuves ; -- normalisation des types de relations ; -- séparation des données métier et de l’état du graphe ; -- table bancaire structurée avec provenance et statut contrôlés ; -- tests dédiés aux nouveaux composants EML et bancaires. - -La V10 est bien publiée et détectable par le code comme version courante. - -Le principal risque documentaire reste l’écart entre : - -- l’ancienne documentation V1 ; -- le schéma courant V10 ; -- les fonctionnalités partielles déjà présentes ; -- le ticket #107, qui décrit encore un flux complet en cours de réalisation. - -`SCHEMA_AUDIT_V1.md` et `SCHEMA_AUDIT_V9.md` doivent rester des documents -historiques. Le présent fichier doit devenir la référence courante jusqu’à la -prochaine migration. - -Les prochaines actions recommandées sont : - -1. ajouter une fixture V9 → V10 ; -2. tester le rollback V10 ; -3. exécuter `integrity_check` et `foreign_key_check` sur une base synthétique ; -4. clarifier le service responsable de la persistance bancaire ; -5. maintenir ce document à chaque nouvelle version ; -6. ne fermer le ticket #107 qu’après validation de son flux fonctionnel complet. - ---- - -# 16. Références du dépôt - -## Commit audité - -```text -613d2096bc5eeb2c1c4f60ae701292e19a2abe66 -feat(relations): centraliser les types et améliorer les aperçus -Date : 2026-07-24 12:53:31 +02:00 -``` - -Le commit contient notamment : - -```text -28 fichiers modifiés -1826 ajouts -18 suppressions -``` - -## Tickets Forgejo liés - -```text -#107 — Ajouter un pivot e-mail forensique avec OCR, métadonnées et - normalisation des données -État : ouvert -``` - -```text -#106 — Normaliser et centraliser les types de relations -État : fermé -``` - -## Documents historiques - -```text -docs/database/DATABASE_ARCHITECTURE.md -docs/database/SCHEMA_AUDIT_V1.md -docs/database/audits/SCHEMA_AUDIT_V9.md -``` - -## Fichiers constituant la source de vérité technique V10 - -```text -database/schema_v1.sql -database/schema_v2.sql -database/schema_v3.sql -database/schema_v4.sql -database/schema_v5.sql -database/schema_v6.sql -database/schema_v7.sql -database/schema_v8.sql -database/schema_v9.sql -database/schema_v10.sql -database/schema_current.sql - -src/database/database.c -src/database/schema.c -src/database/statement.c -src/database/transaction.c - -include/database/database.h -include/database/schema.h - -include/core/bank_proposal.h -include/core/controlled_vocab.h -include/core/eml_mime_extractor.h -include/core/eml_pipeline_task.h - -tests/test_database.c -tests/test_statement.c -tests/test_transaction.c -tests/test_bank_proposal.c -tests/test_controlled_vocab.c -tests/test_eml_pipeline_task.c -``` +La collecte des justifications des rôles sensibles et l’usage plus complet de +`person_identification_assessments` restent des renforcements architecturaux, +pas des exigences contractuelles explicites du ticket #109. Les états d’usage +d’identité présumé et confirmé sont désormais modélisés séparément en V20 et +ne sont pas confondus avec l’authenticité documentaire. diff --git a/include/core/document_identity_misuse_service.h b/include/core/document_identity_misuse_service.h new file mode 100644 index 0000000..1bf6867 --- /dev/null +++ b/include/core/document_identity_misuse_service.h @@ -0,0 +1,16 @@ +#ifndef LABFY_DOCUMENT_IDENTITY_MISUSE_SERVICE_H +#define LABFY_DOCUMENT_IDENTITY_MISUSE_SERVICE_H +#include "database/database.h" +#include "models/identity_traceability.h" +#include +G_BEGIN_DECLS +typedef struct {const char*code,*label,*description;gboolean requires_justification;} DocumentIdentityMisuseStatus; +const DocumentIdentityMisuseStatus *document_identity_misuse_service_statuses(gsize *count); +const DocumentIdentityMisuseStatus *document_identity_misuse_service_status(const char *code); +GPtrArray *document_identity_misuse_service_list_runs(Database *database,const char *evidence_identifier,GError **error); +GPtrArray *document_identity_misuse_service_history(Database *database,const char *evidence_identifier,GError **error); +gboolean document_identity_misuse_service_add(Database *database,const char *evidence_identifier, + const char *status,const char *justification,const char *ocr_run_identifier, + DocumentIdentityMisuseAssessment **created,GError **error); +G_END_DECLS +#endif diff --git a/include/dao/identity_traceability_dao.h b/include/dao/identity_traceability_dao.h index 12b1a66..283c653 100644 --- a/include/dao/identity_traceability_dao.h +++ b/include/dao/identity_traceability_dao.h @@ -16,6 +16,13 @@ GPtrArray *identity_traceability_dao_list_authenticity( IdentityTraceabilityDao *dao,const char *evidence_identifier,GError **error); DocumentAuthenticityAssessment *identity_traceability_dao_current_authenticity( IdentityTraceabilityDao *dao,const char *evidence_identifier,GError **error); +gboolean identity_traceability_dao_insert_identity_misuse( + IdentityTraceabilityDao *dao,const DocumentIdentityMisuseAssessment *assessment, + GError **error); +GPtrArray *identity_traceability_dao_list_identity_misuse( + IdentityTraceabilityDao *dao,const char *evidence_identifier,GError **error); +DocumentIdentityMisuseAssessment *identity_traceability_dao_current_identity_misuse( + IdentityTraceabilityDao *dao,const char *evidence_identifier,GError **error); gboolean identity_traceability_dao_insert_factual_relation( IdentityTraceabilityDao *dao,const PersonEvidenceFactualRelation *relation, GError **error); diff --git a/include/database/schema.h b/include/database/schema.h index ace0a5f..10b49ce 100644 --- a/include/database/schema.h +++ b/include/database/schema.h @@ -100,6 +100,7 @@ bool schema_install_v16(Database *database); bool schema_install_v17(Database *database); bool schema_install_v18(Database *database); bool schema_install_v19(Database *database); +bool schema_install_v20(Database *database); /** * @brief Garantit la présence des extensions du schéma courant V2. diff --git a/include/models/identity_traceability.h b/include/models/identity_traceability.h index e2cac78..0a3c22b 100644 --- a/include/models/identity_traceability.h +++ b/include/models/identity_traceability.h @@ -9,6 +9,12 @@ typedef struct { char *previous_identifier, *technical_note, *origin; } DocumentAuthenticityAssessment; +typedef struct { + char *identifier, *evidence_identifier, *ocr_run_identifier; + char *status, *justification, *assessed_at; + char *previous_identifier, *origin; +} DocumentIdentityMisuseAssessment; + typedef struct { char *identifier, *person_identifier, *evidence_identifier; char *ocr_run_identifier, *relation_type, *factual_note; @@ -25,6 +31,7 @@ typedef struct { typedef PersonRoleVocabularyEntry IdentificationStatusVocabularyEntry; gboolean identity_traceability_authenticity_status_valid(const char *status); +gboolean identity_traceability_identity_misuse_status_valid(const char *status); gboolean identity_traceability_relation_type_valid(const char *type); gboolean identity_traceability_identification_status_valid(const char *status); gboolean identity_traceability_value_quality_valid(const char *quality); @@ -51,6 +58,24 @@ const char *document_authenticity_assessment_get_previous_identifier(const Docum const char *document_authenticity_assessment_get_technical_note(const DocumentAuthenticityAssessment *assessment); const char *document_authenticity_assessment_get_origin(const DocumentAuthenticityAssessment *assessment); +DocumentIdentityMisuseAssessment *document_identity_misuse_assessment_new( + const char *identifier, const char *evidence_identifier, + const char *ocr_run_identifier, const char *status, + const char *justification, const char *assessed_at, + const char *previous_identifier); +DocumentIdentityMisuseAssessment *document_identity_misuse_assessment_copy( + const DocumentIdentityMisuseAssessment *assessment); +void document_identity_misuse_assessment_free( + DocumentIdentityMisuseAssessment *assessment); +const char *document_identity_misuse_assessment_get_identifier(const DocumentIdentityMisuseAssessment *assessment); +const char *document_identity_misuse_assessment_get_evidence_identifier(const DocumentIdentityMisuseAssessment *assessment); +const char *document_identity_misuse_assessment_get_ocr_run_identifier(const DocumentIdentityMisuseAssessment *assessment); +const char *document_identity_misuse_assessment_get_status(const DocumentIdentityMisuseAssessment *assessment); +const char *document_identity_misuse_assessment_get_justification(const DocumentIdentityMisuseAssessment *assessment); +const char *document_identity_misuse_assessment_get_assessed_at(const DocumentIdentityMisuseAssessment *assessment); +const char *document_identity_misuse_assessment_get_previous_identifier(const DocumentIdentityMisuseAssessment *assessment); +const char *document_identity_misuse_assessment_get_origin(const DocumentIdentityMisuseAssessment *assessment); + PersonEvidenceFactualRelation *person_evidence_factual_relation_new( const char *identifier, const char *person_identifier, const char *evidence_identifier, const char *ocr_run_identifier, diff --git a/include/views/document_identity_misuse_editor.h b/include/views/document_identity_misuse_editor.h new file mode 100644 index 0000000..0a2d25d --- /dev/null +++ b/include/views/document_identity_misuse_editor.h @@ -0,0 +1,12 @@ +#ifndef LABFY_DOCUMENT_IDENTITY_MISUSE_EDITOR_H +#define LABFY_DOCUMENT_IDENTITY_MISUSE_EDITOR_H +#include "database/database.h" +#include +G_BEGIN_DECLS +typedef struct DocumentIdentityMisuseEditor DocumentIdentityMisuseEditor; +DocumentIdentityMisuseEditor *document_identity_misuse_editor_new(Database *database,const char *evidence_identifier); +GtkWidget *document_identity_misuse_editor_get_widget(DocumentIdentityMisuseEditor *editor); +gboolean document_identity_misuse_editor_refresh(DocumentIdentityMisuseEditor *editor,GError **error); +void document_identity_misuse_editor_free(DocumentIdentityMisuseEditor *editor); +G_END_DECLS +#endif diff --git a/name_status.txt b/name_status.txt deleted file mode 100644 index f92382d..0000000 --- a/name_status.txt +++ /dev/null @@ -1,19 +0,0 @@ -M Makefile -M include/core/person_creation_task.h -M include/dao/identity_traceability_dao.h -M include/views/create_person_dialog.h -M include/views/main_window.h -M include/views/person_vocabulary_adapter.h -M include/widgets/entity_details_panel.h -M include/widgets/workspace.h -M src/core/application.c -M src/core/person_creation_task.c -M src/dao/identity_traceability_dao.c -M src/views/create_person_dialog.c -M src/views/evidence_metadata_dialog.c -M src/views/main_window.c -M src/views/person_vocabulary_adapter.c -M src/widgets/entity_details_panel.c -M src/widgets/workspace.c -M tests/test_create_person_dialog_gtk.c -M tests/test_create_person_dialog_ocr_gtk.c diff --git a/src/core/document_identity_misuse_service.c b/src/core/document_identity_misuse_service.c new file mode 100644 index 0000000..df54a4c --- /dev/null +++ b/src/core/document_identity_misuse_service.c @@ -0,0 +1,40 @@ +#include "core/document_identity_misuse_service.h" +#include "dao/identity_ocr_dao.h" +#include "dao/identity_traceability_dao.h" +#include "database/transaction.h" +static GQuark domain(void){return g_quark_from_static_string("document-identity-misuse-service");} +static const DocumentIdentityMisuseStatus statuses[]={ + {"indeterminate","Hypothèse d’usurpation indéterminée","Aucune conclusion humaine sur un usage abusif d’identité.",FALSE}, + {"presumed","Document présumé utilisé dans une usurpation d’identité","Hypothèse humaine prudente nécessitant une justification.",TRUE}, + {"confirmed","Utilisation dans une usurpation d’identité confirmée","Conclusion humaine documentée, jamais déduite automatiquement.",TRUE}}; +const DocumentIdentityMisuseStatus *document_identity_misuse_service_statuses(gsize*c) +{if(c)*c=G_N_ELEMENTS(statuses);return statuses;} +const DocumentIdentityMisuseStatus *document_identity_misuse_service_status(const char*code) +{for(guint i=0;irequires_justification&&(!j||!*j))){ + g_free(j);g_set_error_literal(error,domain(),1,"Une justification est requise pour cette évaluation.");return FALSE;} + IdentityTraceabilityDao*dao=identity_traceability_dao_new(d);GError*local=NULL; + if(!database_transaction_begin(d)){identity_traceability_dao_free(dao);g_free(j);g_set_error_literal(error,domain(),2,"Impossible de démarrer la transaction d’usage d’identité.");return FALSE;} + DocumentIdentityMisuseAssessment*previous=identity_traceability_dao_current_identity_misuse(dao,evidence,&local); + if(local){database_transaction_rollback(d);g_propagate_error(error,local);identity_traceability_dao_free(dao);g_free(j);return FALSE;} + char*id=g_uuid_string_random();GDateTime*now=g_date_time_new_now_utc();char*at=g_date_time_format(now,"%Y-%m-%dT%H:%M:%SZ"); + DocumentIdentityMisuseAssessment*a=document_identity_misuse_assessment_new(id,evidence,run,status,j&&*j?j:NULL,at, + previous?document_identity_misuse_assessment_get_identifier(previous):NULL); + gboolean ok=a&&identity_traceability_dao_insert_identity_misuse(dao,a,&local)&&database_transaction_commit(d); + if(!ok&&database_transaction_is_active(d))database_transaction_rollback(d); + if(!ok&&!local)g_set_error_literal(&local,domain(),2,"Impossible d’enregistrer l’évaluation d’usage d’identité."); + if(ok&&created)*created=document_identity_misuse_assessment_copy(a);else if(local)g_propagate_error(error,local); + document_identity_misuse_assessment_free(a);document_identity_misuse_assessment_free(previous);identity_traceability_dao_free(dao); + g_date_time_unref(now);g_free(at);g_free(id);g_free(j);return ok; +} diff --git a/src/dao/identity_traceability_dao.c b/src/dao/identity_traceability_dao.c index b6d5125..d099fd4 100644 --- a/src/dao/identity_traceability_dao.c +++ b/src/dao/identity_traceability_dao.c @@ -100,6 +100,83 @@ DocumentAuthenticityAssessment *identity_traceability_dao_current_authenticity( document_authenticity_assessment_copy(g_ptr_array_index(a,0)):NULL; g_ptr_array_unref(a);return r; } +gboolean identity_traceability_dao_insert_identity_misuse( + IdentityTraceabilityDao*d,const DocumentIdentityMisuseAssessment*a,GError**e) +{ + DocumentIdentityMisuseAssessment*valid=a?document_identity_misuse_assessment_copy(a):NULL; + if(!d||!valid){fail(e,"Évaluation d’usage d’identité invalide.");return FALSE;} + const char*run=document_identity_misuse_assessment_get_ocr_run_identifier(valid); + if(run){DatabaseStatement*check=database_statement_prepare(d->database, + "SELECT 1 FROM identity_ocr_runs WHERE id=? AND evidence_id=?;"); + gboolean compatible=check&&bind(check,1,run)&&bind(check,2, + document_identity_misuse_assessment_get_evidence_identifier(valid))&& + database_statement_step(check)==DATABASE_STATEMENT_STEP_ROW; + database_statement_finalize(check);if(!compatible){ + document_identity_misuse_assessment_free(valid); + fail(e,"L’exécution OCR ne correspond pas à la preuve évaluée.");return FALSE;}} + const char*previous=document_identity_misuse_assessment_get_previous_identifier(valid); + if(previous){DatabaseStatement*check=database_statement_prepare(d->database, + "SELECT 1 FROM document_identity_misuse_assessments WHERE id=? AND evidence_id=?;"); + gboolean compatible=check&&bind(check,1,previous)&&bind(check,2, + document_identity_misuse_assessment_get_evidence_identifier(valid))&& + database_statement_step(check)==DATABASE_STATEMENT_STEP_ROW; + database_statement_finalize(check);if(!compatible){ + document_identity_misuse_assessment_free(valid); + fail(e,"L’évaluation précédente ne correspond pas à la preuve.");return FALSE;}} + DatabaseStatement*s=database_statement_prepare(d->database, + "INSERT INTO document_identity_misuse_assessments VALUES(?,?,?,?,?,?,?,?);"); + gboolean ok=s&&bind(s,1,document_identity_misuse_assessment_get_identifier(valid))&& + bind(s,2,document_identity_misuse_assessment_get_evidence_identifier(valid))&& + bind(s,3,run)&&bind(s,4,document_identity_misuse_assessment_get_status(valid))&& + bind(s,5,document_identity_misuse_assessment_get_justification(valid))&& + bind(s,6,document_identity_misuse_assessment_get_assessed_at(valid))&& + bind(s,7,previous)&&bind(s,8,"human")&& + database_statement_step(s)==DATABASE_STATEMENT_STEP_DONE; + database_statement_finalize(s);document_identity_misuse_assessment_free(valid); + if(!ok)fail(e,"Impossible de conserver l’évaluation d’usage d’identité."); + return ok; +} +static DocumentIdentityMisuseAssessment *read_misuse(DatabaseStatement*s) +{ + char*v[8]={0};gboolean ok=TRUE;for(int i=0;i<8;i++) + ok=ok&&database_statement_column_text(s,i,&v[i]); + DocumentIdentityMisuseAssessment*a=ok?document_identity_misuse_assessment_new( + v[0],v[1],v[2],v[3],v[4],v[5],v[6]):NULL; + for(int i=0;i<8;i++)g_free(v[i]); + return a; +} +GPtrArray *identity_traceability_dao_list_identity_misuse( + IdentityTraceabilityDao*d,const char*evidence,GError**e) +{ + if(!d||!evidence){fail(e,"Lecture de l’usage d’identité invalide.");return NULL;} + DatabaseStatement*s=database_statement_prepare(d->database, + "SELECT id,evidence_id,ocr_run_id,status,justification,assessed_at," + "previous_assessment_id,origin FROM document_identity_misuse_assessments " + "WHERE evidence_id=? ORDER BY assessed_at,id;"); + GPtrArray*a=g_ptr_array_new_with_free_func((GDestroyNotify) + document_identity_misuse_assessment_free);if(!s||!bind(s,1,evidence))goto bad_misuse; + for(;;){DatabaseStatementStepResult step=database_statement_step(s); + if(step==DATABASE_STATEMENT_STEP_DONE)break; + if(step!=DATABASE_STATEMENT_STEP_ROW)goto bad_misuse; + DocumentIdentityMisuseAssessment*r=read_misuse(s);if(!r)goto bad_misuse;g_ptr_array_add(a,r);} + database_statement_finalize(s);return a; +bad_misuse:database_statement_finalize(s);g_ptr_array_unref(a); + fail(e,"Impossible de lire l’historique d’usage d’identité.");return NULL; +} +DocumentIdentityMisuseAssessment *identity_traceability_dao_current_identity_misuse( + IdentityTraceabilityDao*d,const char*evidence,GError**e) +{ + GPtrArray*a=identity_traceability_dao_list_identity_misuse(d,evidence,e);if(!a)return NULL; + DocumentIdentityMisuseAssessment*current=NULL; + for(guint i=0;ilen;i++){DocumentIdentityMisuseAssessment*c=g_ptr_array_index(a,i); + gboolean has_next=FALSE;for(guint j=0;jlen;j++){ + DocumentIdentityMisuseAssessment*n=g_ptr_array_index(a,j); + if(g_strcmp0(document_identity_misuse_assessment_get_previous_identifier(n), + document_identity_misuse_assessment_get_identifier(c))==0){has_next=TRUE;break;}} + if(!has_next)current=c;} + DocumentIdentityMisuseAssessment*r=document_identity_misuse_assessment_copy(current); + g_ptr_array_unref(a);return r; +} gboolean identity_traceability_dao_insert_factual_relation( IdentityTraceabilityDao*d,const PersonEvidenceFactualRelation*r,GError**e) { diff --git a/src/database/database.c b/src/database/database.c index 09f6e6d..96c052c 100644 --- a/src/database/database.c +++ b/src/database/database.c @@ -16,12 +16,12 @@ /** * @brief Version actuelle du schéma SQLite. */ -#define DATABASE_SCHEMA_VERSION_CURRENT 19 +#define DATABASE_SCHEMA_VERSION_CURRENT 20 /** * @brief Version actuelle sous forme textuelle pour metadata. */ -#define DATABASE_SCHEMA_VERSION_CURRENT_TEXT "19" +#define DATABASE_SCHEMA_VERSION_CURRENT_TEXT "20" /** * @brief Nom de l'application enregistré dans les métadonnées. @@ -947,6 +947,21 @@ rollback: return false; } +static bool database_migrate_v19_to_v20(Database *database) +{ + bool transaction_started = false; + if (database == NULL || !database_transaction_begin(database)) return false; + transaction_started = true; + if (!schema_install_v20(database) || + !database_update_schema_version(database, "20") || + !database_transaction_commit(database)) goto rollback; + return true; +rollback: + if (transaction_started && !database_transaction_rollback(database)) + g_warning("Impossible d’annuler la migration SQLite V19 vers V20."); + return false; +} + /** * @brief Garantit atomiquement la présence des extensions du schéma courant. */ @@ -1212,6 +1227,10 @@ bool database_migrate_to_latest( if (!database_migrate_v18_to_v19(database)) return false; schema_version = 19; break; + case 19: + if (!database_migrate_v19_to_v20(database)) return false; + schema_version = 20; + break; default: database_set_error( @@ -1354,8 +1373,7 @@ bool database_initialize( if (!schema_ensure_current( database - ) || - !schema_install_v19(database)) + )) { goto rollback; } diff --git a/src/database/schema.c b/src/database/schema.c index d0e0d5e..2541d41 100644 --- a/src/database/schema.c +++ b/src/database/schema.c @@ -439,6 +439,12 @@ bool schema_install_v19(Database *database) "la migration SQLite V19"); } +bool schema_install_v20(Database *database) +{ + return schema_execute_file(database, "database/schema_v20.sql", + "la migration SQLite V20"); +} + bool schema_ensure_current( Database *database ) diff --git a/src/models/identity_traceability.c b/src/models/identity_traceability.c index 66ffc5a..0888766 100644 --- a/src/models/identity_traceability.c +++ b/src/models/identity_traceability.c @@ -24,6 +24,11 @@ gboolean identity_traceability_authenticity_status_valid(const char *value) "suspicious","presumed_forged","confirmed_forged"}; return in_values(value,values,G_N_ELEMENTS(values)); } +gboolean identity_traceability_identity_misuse_status_valid(const char *value) +{ + static const char *const values[]={"indeterminate","presumed","confirmed"}; + return in_values(value,values,G_N_ELEMENTS(values)); +} gboolean identity_traceability_relation_type_valid(const char *value) { static const char *const values[]={"identity_observed_in", @@ -90,6 +95,42 @@ AUTH_GETTER(previous_identifier,previous_identifier) AUTH_GETTER(technical_note,technical_note) AUTH_GETTER(origin,origin) +DocumentIdentityMisuseAssessment *document_identity_misuse_assessment_new( + const char *id,const char *evidence,const char *run,const char *status, + const char *justification,const char *at,const char *previous) +{ + if(!g_uuid_string_is_valid(id)||!g_uuid_string_is_valid(evidence)|| + !uuid_or_null(run)||!uuid_or_null(previous)|| + !identity_traceability_identity_misuse_status_valid(status)|| + !timestamp(at)||!optional_text(justification)|| + (g_strcmp0(status,"indeterminate")!=0&&!text(justification)))return NULL; + DocumentIdentityMisuseAssessment*a=g_new0(DocumentIdentityMisuseAssessment,1); + a->identifier=g_strdup(id);a->evidence_identifier=g_strdup(evidence); + a->ocr_run_identifier=g_strdup(run);a->status=g_strdup(status); + a->justification=g_strdup(justification);a->assessed_at=g_strdup(at); + a->previous_identifier=g_strdup(previous);a->origin=g_strdup("human");return a; +} +DocumentIdentityMisuseAssessment *document_identity_misuse_assessment_copy( + const DocumentIdentityMisuseAssessment*a) +{return a?document_identity_misuse_assessment_new(a->identifier, + a->evidence_identifier,a->ocr_run_identifier,a->status,a->justification, + a->assessed_at,a->previous_identifier):NULL;} +void document_identity_misuse_assessment_free(DocumentIdentityMisuseAssessment*a) +{if(!a)return;g_free(a->identifier);g_free(a->evidence_identifier); + g_free(a->ocr_run_identifier);g_free(a->status);g_free(a->justification); + g_free(a->assessed_at);g_free(a->previous_identifier);g_free(a->origin);g_free(a);} +#define MISUSE_GETTER(name,field) \ +const char *document_identity_misuse_assessment_get_##name( \ + const DocumentIdentityMisuseAssessment*a){return a?a->field:NULL;} +MISUSE_GETTER(identifier,identifier) +MISUSE_GETTER(evidence_identifier,evidence_identifier) +MISUSE_GETTER(ocr_run_identifier,ocr_run_identifier) +MISUSE_GETTER(status,status) +MISUSE_GETTER(justification,justification) +MISUSE_GETTER(assessed_at,assessed_at) +MISUSE_GETTER(previous_identifier,previous_identifier) +MISUSE_GETTER(origin,origin) + PersonEvidenceFactualRelation *person_evidence_factual_relation_new( const char *id,const char *person,const char *evidence,const char *run, const char *type,const char *note,const char *at,gboolean active) diff --git a/src/views/document_identity_misuse_editor.c b/src/views/document_identity_misuse_editor.c new file mode 100644 index 0000000..9e6461b --- /dev/null +++ b/src/views/document_identity_misuse_editor.c @@ -0,0 +1,49 @@ +#include "views/document_identity_misuse_editor.h" +#include "core/document_identity_misuse_service.h" +#include "dao/identity_ocr_dao.h" +struct DocumentIdentityMisuseEditor{Database*database;char*evidence;GtkWidget*root; + GtkDropDown*status;GtkStringList*status_labels;GtkDropDown*run;GtkStringList*run_labels; + GPtrArray*run_ids;GtkTextBuffer*justification;GtkLabel*error;GtkBox*history;}; +static void clear_box(GtkBox*b){GtkWidget*c=gtk_widget_get_first_child(GTK_WIDGET(b));while(c){GtkWidget*n=gtk_widget_get_next_sibling(c);gtk_box_remove(b,c);c=n;}} +static char *buffer_text(GtkTextBuffer*b){GtkTextIter s,e;gtk_text_buffer_get_bounds(b,&s,&e);char*t=gtk_text_buffer_get_text(b,&s,&e,FALSE);g_strstrip(t);return t;} +gboolean document_identity_misuse_editor_refresh(DocumentIdentityMisuseEditor*x,GError**error) +{ + if(!x)return FALSE; + clear_box(x->history);GPtrArray*h=document_identity_misuse_service_history(x->database,x->evidence,error);if(!h)return FALSE; + if(h->len==0)gtk_box_append(x->history,gtk_label_new("Aucune évaluation humaine enregistrée.")); + for(gint i=(gint)h->len-1;i>=0;i--){DocumentIdentityMisuseAssessment*a=g_ptr_array_index(h,(guint)i); + const DocumentIdentityMisuseStatus*s=document_identity_misuse_service_status(document_identity_misuse_assessment_get_status(a)); + const char*j=document_identity_misuse_assessment_get_justification(a),*r=document_identity_misuse_assessment_get_ocr_run_identifier(a); + char*t=g_strdup_printf("%s — %s\nOrigine : humaine%s%s\nJustification : %s", + s?s->label:"Statut inconnu",document_identity_misuse_assessment_get_assessed_at(a), + r?" — OcrRun : ":"",r?r:"",j&&*j?j:"Aucune (statut indéterminé)"); + GtkWidget*l=gtk_label_new(t);gtk_label_set_xalign(GTK_LABEL(l),0);gtk_label_set_wrap(GTK_LABEL(l),TRUE);gtk_box_append(x->history,l);g_free(t);} + g_ptr_array_unref(h);return TRUE; +} +static void save(GtkButton*b,gpointer data) +{ + (void)b;DocumentIdentityMisuseEditor*x=data;guint si=gtk_drop_down_get_selected(x->status),ri=gtk_drop_down_get_selected(x->run);gsize count=0; + const DocumentIdentityMisuseStatus*s=document_identity_misuse_service_statuses(&count);if(si>=count)return; + char*j=buffer_text(x->justification);const char*run=ri>0&&ri-1run_ids->len?g_ptr_array_index(x->run_ids,ri-1):NULL;GError*error=NULL; + if(!document_identity_misuse_service_add(x->database,x->evidence,s[si].code,j,run,NULL,&error)){ + gtk_label_set_text(x->error,error?error->message:"Erreur d’enregistrement.");gtk_widget_set_visible(GTK_WIDGET(x->error),TRUE);g_clear_error(&error); + }else{gtk_widget_set_visible(GTK_WIDGET(x->error),FALSE);gtk_text_buffer_set_text(x->justification,"",-1);document_identity_misuse_editor_refresh(x,NULL);}g_free(j); +} +DocumentIdentityMisuseEditor *document_identity_misuse_editor_new(Database*d,const char*evidence) +{ + if(!d||!g_uuid_string_is_valid(evidence))return NULL; + DocumentIdentityMisuseEditor*x=g_new0(DocumentIdentityMisuseEditor,1);x->database=d;x->evidence=g_strdup(evidence); + x->root=gtk_box_new(GTK_ORIENTATION_VERTICAL,6);gtk_widget_set_name(x->root,"document-identity-misuse-editor"); + GtkWidget*title=gtk_label_new("Usage de l’identité du document — évaluation humaine distincte");gtk_label_set_xalign(GTK_LABEL(title),0);gtk_box_append(GTK_BOX(x->root),title); + x->status_labels=gtk_string_list_new(NULL);gsize count=0;const DocumentIdentityMisuseStatus*s=document_identity_misuse_service_statuses(&count);for(guint i=0;istatus_labels,s[i].label); + x->status=GTK_DROP_DOWN(gtk_drop_down_new(G_LIST_MODEL(g_object_ref(x->status_labels)),NULL));gtk_widget_set_name(GTK_WIDGET(x->status),"identity-misuse-status");gtk_box_append(GTK_BOX(x->root),GTK_WIDGET(x->status)); + x->run_labels=gtk_string_list_new(NULL);gtk_string_list_append(x->run_labels,"Aucune exécution OCR");x->run_ids=g_ptr_array_new_with_free_func(g_free);GPtrArray*runs=document_identity_misuse_service_list_runs(d,evidence,NULL); + if(runs){for(guint i=0;ilen;i++){IdentityOcrRunRecord*r=g_ptr_array_index(runs,i);char*l=g_strdup_printf("%s — %s",r->executed_at,r->engine);gtk_string_list_append(x->run_labels,l);g_ptr_array_add(x->run_ids,g_strdup(r->id));g_free(l);}g_ptr_array_unref(runs);} + x->run=GTK_DROP_DOWN(gtk_drop_down_new(G_LIST_MODEL(g_object_ref(x->run_labels)),NULL));gtk_widget_set_name(GTK_WIDGET(x->run),"identity-misuse-ocr-run");gtk_box_append(GTK_BOX(x->root),GTK_WIDGET(x->run)); + gtk_box_append(GTK_BOX(x->root),gtk_label_new("Justification factuelle"));GtkWidget*jv=gtk_text_view_new();x->justification=gtk_text_view_get_buffer(GTK_TEXT_VIEW(jv));gtk_widget_set_name(jv,"identity-misuse-justification");gtk_widget_set_size_request(jv,-1,60);gtk_box_append(GTK_BOX(x->root),jv); + x->error=GTK_LABEL(gtk_label_new(""));gtk_widget_set_name(GTK_WIDGET(x->error),"identity-misuse-error");gtk_widget_add_css_class(GTK_WIDGET(x->error),"error");gtk_widget_set_visible(GTK_WIDGET(x->error),FALSE);gtk_box_append(GTK_BOX(x->root),GTK_WIDGET(x->error)); + GtkWidget*button=gtk_button_new_with_label("Enregistrer l’évaluation humaine");gtk_widget_set_name(button,"identity-misuse-save");g_signal_connect(button,"clicked",G_CALLBACK(save),x);gtk_box_append(GTK_BOX(x->root),button); + x->history=GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL,4));gtk_widget_set_name(GTK_WIDGET(x->history),"identity-misuse-history");gtk_box_append(GTK_BOX(x->root),GTK_WIDGET(x->history));document_identity_misuse_editor_refresh(x,NULL);return x; +} +GtkWidget *document_identity_misuse_editor_get_widget(DocumentIdentityMisuseEditor*x){return x?x->root:NULL;} +void document_identity_misuse_editor_free(DocumentIdentityMisuseEditor*x){if(!x)return;g_clear_object(&x->status_labels);g_clear_object(&x->run_labels);g_ptr_array_unref(x->run_ids);g_free(x->evidence);g_free(x);} diff --git a/src/views/evidence_metadata_dialog.c b/src/views/evidence_metadata_dialog.c index 12e3064..fe7a039 100644 --- a/src/views/evidence_metadata_dialog.c +++ b/src/views/evidence_metadata_dialog.c @@ -13,6 +13,7 @@ #include "views/evidence_identity_ocr_dialog.h" #include "views/person_vocabulary_adapter.h" #include "views/document_authenticity_editor.h" +#include "views/document_identity_misuse_editor.h" #include "widgets/ocr_provenance_overlay.h" struct EvidenceMetadataDialogResult @@ -50,6 +51,7 @@ typedef struct gpointer user_data; gboolean completed; DocumentAuthenticityEditor *authenticity_editor; + DocumentIdentityMisuseEditor *identity_misuse_editor; } EvidenceMetadataDialogContext; static void evidence_metadata_dialog_complete( @@ -92,6 +94,7 @@ static void evidence_metadata_dialog_context_free(gpointer data) g_clear_pointer(&context->ocr_run_identifiers, g_ptr_array_unref); g_clear_object(&context->ocr_run_labels); document_authenticity_editor_free(context->authenticity_editor); + document_identity_misuse_editor_free(context->identity_misuse_editor); g_free(context->investigation_root); g_free(context->evidence_identifier); g_free(context); @@ -554,6 +557,16 @@ gboolean evidence_metadata_dialog_present_with_ocr( document_authenticity_editor_get_widget(context->authenticity_editor)); gtk_box_append(GTK_BOX(main_box), auth_scroll); } + context->identity_misuse_editor = document_identity_misuse_editor_new( + database, evidence_record_get_identifier(record)); + if (context->identity_misuse_editor != NULL) { + GtkWidget *misuse_scroll = gtk_scrolled_window_new(); + gtk_widget_set_size_request(misuse_scroll, -1, 300); + gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(misuse_scroll), + document_identity_misuse_editor_get_widget( + context->identity_misuse_editor)); + gtk_box_append(GTK_BOX(main_box), misuse_scroll); + } IdentityTraceabilityDao *trace_dao = identity_traceability_dao_new(database); if (trace_dao != NULL) { GPtrArray *relations = identity_traceability_dao_list_factual_relations_by_evidence( diff --git a/tests/test_database.c b/tests/test_database.c index 695d619..e36f09a 100644 --- a/tests/test_database.c +++ b/tests/test_database.c @@ -525,7 +525,7 @@ static void test_database_initialize_valid_database(void) "FROM investigation;" ); - assert(strcmp(schema_version, "19") == 0); + assert(strcmp(schema_version, "20") == 0); test_database_assert_table_exists(database, "person_role_assignments"); test_database_assert_table_exists(database, "bank_account_entities"); test_database_assert_table_exists(database, "relation_types"); @@ -543,6 +543,15 @@ static void test_database_initialize_valid_database(void) "person_role_vocabulary"); test_database_assert_table_exists(database, "identification_status_vocabulary"); + test_database_assert_table_exists(database, "person_profile_fields"); + test_database_assert_table_exists(database, + "document_identity_misuse_assessments"); + test_database_assert_table_exists(database, + "person_ocr_field_projections"); + test_database_assert_column_exists(database, + "person_ocr_field_projections", "previous_value"); + test_database_assert_column_exists(database, + "person_ocr_field_projections", "origin"); assert(strcmp(application_name, "Labfy Investigation") == 0); assert(created_at[0] != '\0'); @@ -1001,7 +1010,7 @@ static void test_database_migrate_v1_to_v2(void) assert( strcmp( schema_version, - "19" + "20" ) == 0 ); @@ -1431,6 +1440,7 @@ static void test_database_migrate_v12_to_v13_preserves_legacy_link(void) "'20000000-0000-4000-8000-000000000014','alleged_scammer'," "'2026-07-28T08:00:00Z');" "DROP TABLE person_ocr_field_projections;" + "DROP TABLE document_identity_misuse_assessments;" "DROP TABLE person_profile_fields;" "DROP TABLE person_identification_assessments;" "DROP TABLE person_evidence_factual_relations;" @@ -1466,7 +1476,7 @@ static void test_database_migrate_v12_to_v13_preserves_legacy_link(void) legacy_sources = test_database_read_single_text(sqlite_database, "SELECT COUNT(*) FROM preuve_entite_sources " "WHERE source_kind='legacy_manual';"); - assert(strcmp(version, "19") == 0); + assert(strcmp(version, "20") == 0); assert(strcmp(legacy_sources, "1") == 0); char *legacy_role = test_database_read_single_text(sqlite_database, "SELECT role_code || ':' || provenance_kind " diff --git a/tests/test_document_identity_misuse_editor_gtk.c b/tests/test_document_identity_misuse_editor_gtk.c new file mode 100644 index 0000000..c69620b --- /dev/null +++ b/tests/test_document_identity_misuse_editor_gtk.c @@ -0,0 +1,24 @@ +#include "views/document_identity_misuse_editor.h" +#include "core/document_identity_misuse_service.h" +#include "database/database.h" +#include +#include +#define EVIDENCE "11000000-0000-4000-8000-000000000020" +#define RUN "31000000-0000-4000-8000-000000000020" +#define AT "2026-08-02T10:00:00Z" +static GtkWidget *named(GtkWidget*w,const char*n){if(g_strcmp0(gtk_widget_get_name(w),n)==0)return w;for(GtkWidget*c=gtk_widget_get_first_child(w);c;c=gtk_widget_get_next_sibling(c)){GtkWidget*r=named(c,n);if(r)return r;}return NULL;} +static void test_editor(void) +{ + GError*error=NULL;char*dir=g_dir_make_tmp("labfy-misuse-gtk-XXXXXX",&error);g_assert_no_error(error);char*path=g_build_filename(dir,"Enquete.sqlite",NULL);g_assert_true(database_initialize(path,"SPECIMEN",dir)); + sqlite3*s=NULL;g_assert_cmpint(sqlite3_open(path,&s),==,SQLITE_OK);char*message=NULL; + const char*sql="INSERT INTO preuves(id,name,relative_path,type_id,size_bytes,sha256,imported_at,updated_at,status,locked,original_name) VALUES('" EVIDENCE "','specimen.png','specimen.png',2,8,'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa','" AT "','" AT "','active',0,'specimen.png');INSERT INTO identity_ocr_runs(id,evidence_id,expected_sha256,page_number,document_type,document_side,engine,requested_languages,available_languages,parameters,preprocessing_profile,executed_at,status,text_relative_path,text_sha256,tsv_relative_path,tsv_sha256) VALUES('" RUN "','" EVIDENCE "','aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',1,'identity_card','front','tesseract','fra','fra','SPECIMEN','none','" AT "','success','raw.txt','bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb','raw.tsv','cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc');"; + g_assert_cmpint(sqlite3_exec(s,sql,NULL,NULL,&message),==,SQLITE_OK);sqlite3_free(message);sqlite3_close(s); + Database*d=database_open(path);g_assert_nonnull(d);DocumentIdentityMisuseEditor*x=document_identity_misuse_editor_new(d,EVIDENCE);g_assert_nonnull(x);GtkWidget*root=document_identity_misuse_editor_get_widget(x);g_object_ref_sink(root); + GtkDropDown*status=GTK_DROP_DOWN(named(root,"identity-misuse-status"));GtkDropDown*run=GTK_DROP_DOWN(named(root,"identity-misuse-ocr-run"));GtkLabel*error_label=GTK_LABEL(named(root,"identity-misuse-error"));GtkWidget*save=named(root,"identity-misuse-save"); + g_assert_cmpuint(g_list_model_get_n_items(gtk_drop_down_get_model(status)),==,3);g_assert_cmpuint(g_list_model_get_n_items(gtk_drop_down_get_model(run)),==,2); + gtk_drop_down_set_selected(status,1);g_signal_emit_by_name(save,"clicked");g_assert_true(gtk_widget_get_visible(GTK_WIDGET(error_label))); + GtkTextBuffer*j=gtk_text_view_get_buffer(GTK_TEXT_VIEW(named(root,"identity-misuse-justification")));gtk_text_buffer_set_text(j,"Indices SPECIMEN",-1);gtk_drop_down_set_selected(run,1);g_signal_emit_by_name(save,"clicked");g_assert_false(gtk_widget_get_visible(GTK_WIDGET(error_label))); + GPtrArray*h=document_identity_misuse_service_history(d,EVIDENCE,&error);g_assert_no_error(error);g_assert_cmpuint(h->len,==,1);DocumentIdentityMisuseAssessment*a=g_ptr_array_index(h,0);g_assert_cmpstr(document_identity_misuse_assessment_get_status(a),==,"presumed");g_assert_cmpstr(document_identity_misuse_assessment_get_origin(a),==,"human");g_ptr_array_unref(h); + g_object_unref(root);document_identity_misuse_editor_free(x);database_close(d);g_remove(path);g_rmdir(dir);g_free(path);g_free(dir); +} +int main(int argc,char**argv){gtk_init();g_test_init(&argc,&argv,NULL);g_test_add_func("/identity-misuse/editor",test_editor);return g_test_run();} diff --git a/tests/test_evidence_metadata_dialog_gtk.c b/tests/test_evidence_metadata_dialog_gtk.c index 3cb18f5..1c3094c 100644 --- a/tests/test_evidence_metadata_dialog_gtk.c +++ b/tests/test_evidence_metadata_dialog_gtk.c @@ -329,6 +329,8 @@ static gboolean drive(gpointer data) g_assert_true(gtk_window_get_destroy_with_parent(dialog)); g_assert_nonnull(find_named(GTK_WIDGET(dialog), "document-authenticity-editor")); + g_assert_nonnull(find_named(GTK_WIDGET(dialog), + "document-identity-misuse-editor")); } if (dialog == NULL) { if (context->phase == 1) { diff --git a/tests/test_identity_traceability.c b/tests/test_identity_traceability.c index 8298498..ba78162 100644 --- a/tests/test_identity_traceability.c +++ b/tests/test_identity_traceability.c @@ -4,6 +4,7 @@ #include "database/database.h" #include "models/identity_traceability.h" #include "core/document_authenticity_service.h" +#include "core/document_identity_misuse_service.h" #include #include #include @@ -16,6 +17,8 @@ #define ASSESSMENT1 "60000000-0000-4000-8000-000000000018" #define ASSESSMENT2 "60000000-0000-4000-8000-000000000019" #define FACT "70000000-0000-4000-8000-000000000018" +#define MISUSE1 "90000000-0000-4000-8000-000000000018" +#define MISUSE2 "90000000-0000-4000-8000-000000000019" #define AT "2026-07-30T10:00:00Z" typedef struct{char*dir,*path;Database*database;}Fixture; @@ -114,6 +117,73 @@ static void test_authenticity_service(void) NULL,&error));g_assert_nonnull(error);g_clear_error(&error); h=document_authenticity_service_history(f.database,EVIDENCE,&error);g_assert_cmpuint(h->len,==,2);g_ptr_array_unref(h);fixture_free(&f); } +static void test_identity_misuse_service(void) +{ + gsize count=0;const DocumentIdentityMisuseStatus*statuses= + document_identity_misuse_service_statuses(&count); + static const char*codes[]={"indeterminate","presumed","confirmed"}; + static const char*labels[]={"Hypothèse d’usurpation indéterminée", + "Document présumé utilisé dans une usurpation d’identité", + "Utilisation dans une usurpation d’identité confirmée"}; + g_assert_cmpuint(count,==,3);for(guint i=0;ilen,==,2);g_ptr_array_unref(h); + database_close(f.database);f.database=database_open(f.path);g_assert_nonnull(f.database); + h=document_identity_misuse_service_history(f.database,EVIDENCE,&error); + g_assert_cmpuint(h->len,==,2);g_ptr_array_unref(h); + database_close(f.database);f.database=NULL;sqlite3*d=NULL; + g_assert_cmpint(sqlite3_open(f.path,&d),==,SQLITE_OK); + char*automatic=scalar(d,"SELECT (SELECT COUNT(*) FROM person_role_assignments)" + "||(SELECT COUNT(*) FROM person_evidence_factual_relations)" + "||(SELECT COUNT(*) FROM person_identification_assessments)" + "||(SELECT COUNT(*) FROM document_authenticity_assessments);"); + g_assert_cmpstr(automatic,==,"0000");g_free(automatic);char*message=NULL; + g_assert_cmpint(sqlite3_exec(d,"UPDATE document_identity_misuse_assessments " + "SET status='confirmed';",NULL,NULL,&message),!=,SQLITE_OK); + sqlite3_free(message);message=NULL;g_assert_cmpint(sqlite3_exec(d, + "DELETE FROM document_identity_misuse_assessments;",NULL,NULL,&message),!=,SQLITE_OK); + sqlite3_free(message);sqlite3_close(d);f.database=database_open(f.path); + g_assert_nonnull(f.database);fixture_free(&f); +} +static void test_migrate_v19_to_v20(void) +{ + Fixture f=fixture_new();database_close(f.database);f.database=NULL;sqlite3*d=NULL; + g_assert_cmpint(sqlite3_open(f.path,&d),==,SQLITE_OK); + exec_ok(d,"DROP TABLE document_identity_misuse_assessments;" + "UPDATE metadata SET value='19' WHERE key='schema_version';");sqlite3_close(d); + f.database=database_open(f.path);g_assert_nonnull(f.database); + g_assert_true(database_migrate_to_latest(f.database));database_close(f.database);f.database=NULL; + g_assert_cmpint(sqlite3_open(f.path,&d),==,SQLITE_OK);char*version=scalar(d, + "SELECT value FROM metadata WHERE key='schema_version';");char*table=scalar(d, + "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='document_identity_misuse_assessments';"); + g_assert_cmpstr(version,==,"20");g_assert_cmpstr(table,==,"1");g_free(version);g_free(table); + exec_ok(d,"DROP TABLE document_identity_misuse_assessments;" + "UPDATE metadata SET value='19' WHERE key='schema_version';" + "CREATE TABLE document_identity_misuse_assessments(dummy TEXT);");sqlite3_close(d); + f.database=database_open(f.path);g_test_expect_message(NULL,G_LOG_LEVEL_WARNING, + "*Impossible d’installer la migration SQLite V20*"); + g_assert_false(database_migrate_to_latest(f.database));g_test_assert_expected_messages(); + database_close(f.database);f.database=NULL;g_assert_cmpint(sqlite3_open(f.path,&d),==,SQLITE_OK); + version=scalar(d,"SELECT value FROM metadata WHERE key='schema_version';"); + g_assert_cmpstr(version,==,"19");g_free(version);exec_ok(d, + "DROP TABLE document_identity_misuse_assessments;");sqlite3_close(d); + f.database=database_open(f.path);g_assert_true(database_migrate_to_latest(f.database));fixture_free(&f); +} static void test_dao_history_and_relations(void) { Fixture f=fixture_new();GError*e=NULL; @@ -218,6 +288,7 @@ static void test_migrate_v17_preserves_ocr(void) Fixture f=fixture_new();database_close(f.database);f.database=NULL; sqlite3*d=NULL;g_assert_cmpint(sqlite3_open(f.path,&d),==,SQLITE_OK); exec_ok(d,"PRAGMA foreign_keys=OFF;" + "DROP TABLE document_identity_misuse_assessments;" "DROP TABLE person_ocr_field_projections;DROP TABLE person_profile_fields;" "DROP TABLE person_identification_assessments;" "DROP TABLE person_evidence_factual_relations;" @@ -258,7 +329,7 @@ static void test_migrate_v17_preserves_ocr(void) char*version=scalar(d,"SELECT value FROM metadata WHERE key='schema_version';"); char*values=scalar(d,"SELECT raw_value||':'||normalized_value||':'||corrected_value" "||':'||confirmation_state FROM identity_field_observations WHERE id='" FIELD "';"); - g_assert_cmpstr(version,==,"19"); + g_assert_cmpstr(version,==,"20"); g_assert_cmpstr(values,==,"BRUT SPECIMEN:BRUT SPECIMEN:CORRIGÉ SPECIMEN:unconfirmed"); sqlite3_close(d);g_free(version);g_free(values); f.database=database_open(f.path);g_assert_true(database_migrate_to_latest(f.database)); @@ -268,6 +339,8 @@ int main(int argc,char**argv) {g_test_init(&argc,&argv,NULL);g_test_add_func("/v18/models",test_models); g_test_add_func("/v18/dao-history-relations",test_dao_history_and_relations); g_test_add_func("/v18/authenticity-service",test_authenticity_service); + g_test_add_func("/v20/identity-misuse-service",test_identity_misuse_service); + g_test_add_func("/v20/migrate-v19",test_migrate_v19_to_v20); g_test_add_func("/v18/sqlite-negative-guards",test_sqlite_negative_guards); g_test_add_func("/v18/migrate-v17",test_migrate_v17_preserves_ocr); return g_test_run();} diff --git a/tests/test_person_details_traceability_gtk.c b/tests/test_person_details_traceability_gtk.c new file mode 100644 index 0000000..8b656dd --- /dev/null +++ b/tests/test_person_details_traceability_gtk.c @@ -0,0 +1,21 @@ +#include "widgets/entity_details_panel.h" +#include "models/evidence_record.h" +#include "models/identity_traceability.h" +#include +#define PERSON "22000000-0000-4000-8000-000000000020" +#define EVIDENCE "12000000-0000-4000-8000-000000000020" +#define RELATION "72000000-0000-4000-8000-000000000020" +#define AT "2026-08-02T10:00:00Z" +static void collect_text(GtkWidget*w,GString*out){if(GTK_IS_LABEL(w))g_string_append(out,gtk_label_get_text(GTK_LABEL(w)));for(GtkWidget*c=gtk_widget_get_first_child(w);c;c=gtk_widget_get_next_sibling(c)){g_string_append_c(out,'\n');collect_text(c,out);}} +static void test_person_details(void) +{ + GError*error=NULL;EntityDetailsPanel*panel=entity_details_panel_new();g_assert_nonnull(panel);GtkWidget*root=entity_details_panel_get_widget(panel);g_object_ref_sink(root); + EntityRecord*person=entity_record_new(PERSON,"person","Personne SPECIMEN","Personne SPECIMEN",NULL,0,AT,AT,ENTITY_STATUS_ACTIVE,&error);g_assert_no_error(error);g_assert_nonnull(person);entity_details_panel_set_entity(panel,person); + EvidenceRecord*evidence=evidence_record_new(EVIDENCE,"preuve-specimen.png","preuve-specimen.png","preuve-specimen.png","2",8,"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",AT,NULL,NULL,NULL,EVIDENCE_INTEGRITY_STATUS_UNKNOWN,&error);g_assert_no_error(error);g_assert_nonnull(evidence); + GPtrArray*evidence_records=g_ptr_array_new();g_ptr_array_add(evidence_records,evidence);PersonEvidenceFactualRelation*relation=person_evidence_factual_relation_new(RELATION,PERSON,EVIDENCE,NULL,"identity_observed_in","Note factuelle SPECIMEN",AT,TRUE);g_assert_nonnull(relation);GPtrArray*relations=g_ptr_array_new();g_ptr_array_add(relations,relation); + GHashTable*fields=g_hash_table_new(g_str_hash,g_str_equal);g_hash_table_insert(fields,"surname","NOM SPECIMEN");g_hash_table_insert(fields,"birth_date","2000-01-01"); + entity_details_panel_set_person_profile_fields(panel,fields);entity_details_panel_set_person_factual_relations(panel,relations,evidence_records); + GString*text=g_string_new(NULL);collect_text(root,text);g_assert_nonnull(strstr(text->str,"Valeurs confirmées du profil"));g_assert_nonnull(strstr(text->str,"NOM SPECIMEN"));g_assert_nonnull(strstr(text->str,"Relations factuelles"));g_assert_nonnull(strstr(text->str,"Identité observée dans la preuve"));g_assert_nonnull(strstr(text->str,"Note factuelle SPECIMEN")); + g_string_free(text,TRUE);g_hash_table_unref(fields);g_ptr_array_unref(relations);g_ptr_array_unref(evidence_records);person_evidence_factual_relation_free(relation);evidence_record_free(evidence);entity_record_free(person);entity_details_panel_free(panel);g_object_unref(root); +} +int main(int argc,char**argv){gtk_init();g_test_init(&argc,&argv,NULL);g_test_add_func("/person-details/traceability",test_person_details);return g_test_run();} diff --git a/ticket109.md b/ticket109.md deleted file mode 100644 index fd257e6..0000000 --- a/ticket109.md +++ /dev/null @@ -1,473 +0,0 @@ -## Contexte - -Lors de la création d’une personne dans une enquête, l’utilisateur doit -actuellement renseigner les données manuellement et ne peut pas choisir -clairement la nature de la personne ni associer facilement une preuve. - -Le parcours devient particulièrement long lorsque les noms de fichiers -ne permettent pas de savoir ce qu’ils contiennent. - -Le cas des documents d’identité nécessite également un traitement -forensique strict : - -- conservation de l’original ; -- analyse sur une copie de travail ; -- OCR contrôlé ; -- validation humaine ; -- distinction entre texte brut, valeur normalisée et correction manuelle ; -- aucune affirmation automatique concernant l’identité réelle ou - l’authenticité du document. - -## Objectif - -Créer un assistant fluide permettant, depuis la fenêtre de création d’une -personne : - -1. de sélectionner son rôle ou sa nature dans l’enquête ; -2. de choisir une preuve déjà importée ; -3. d’importer immédiatement une nouvelle preuve ; -4. de visualiser la preuve avant de la sélectionner ; -5. de qualifier le type de preuve ; -6. de lancer une analyse adaptée au type choisi ; -7. d’utiliser l’OCR pour préremplir les champs d’un document d’identité ; -8. de corriger manuellement les données proposées ; -9. de conserver la provenance complète de chaque valeur ; -10. de garantir l’intégrité du fichier original. - -## Terminologie - -Ne pas confondre : - -- le rôle de la personne dans l’enquête ; -- son niveau d’identification ; -- l’authenticité du document présenté ; -- la confiance accordée aux différentes observations. - -Une personne peut par exemple avoir : - -- rôle : Identité présentée ; -- identification : Non vérifiée ; -- document : Authenticité indéterminée ; -- hypothèse : Identité potentiellement usurpée. - -L’application ne doit jamais transformer automatiquement cette personne -en auteur identifié. - -## Fenêtre de création d’une personne - -Ajouter un champ contrôlé : - - Rôle dans l’enquête - -Valeurs initiales proposées : - -- Auteur présumé ; -- Identité présentée ; -- Identité potentiellement usurpée ; -- Victime ; -- Témoin ; -- Titulaire bancaire déclaré ; -- Intermédiaire ; -- Personne citée ; -- Autre. - -Ces valeurs doivent provenir du vocabulaire contrôlé du projet. - -Le champ existant « Identification » reste distinct et conserve des états -tels que : - -- Inconnu ; -- Non vérifié ; -- Partiellement identifié ; -- Identifié ; -- Contesté. - -## Association d’une preuve - -Depuis la fenêtre de création d’une personne, proposer deux actions : - -- Sélectionner une preuve existante ; -- Importer une nouvelle preuve. - -L’utilisateur ne doit pas être obligé de fermer la fenêtre, importer la -preuve ailleurs, puis recommencer la création de la personne. - -### Preuve existante - -La sélection doit afficher au minimum : - -- miniature ou aperçu ; -- nom du fichier ; -- type de preuve ; -- taille ; -- date d’import ; -- empreinte SHA-256 abrégée ; -- description éventuelle ; -- état d’intégrité. - -Prévoir une recherche et un filtrage par type. - -### Nouvelle preuve - -L’import déclenché depuis la fenêtre doit utiliser le mécanisme forensique -central du projet : - -- copie contrôlée dans l’enquête ; -- calcul du SHA-256 ; -- conservation du nom original ; -- enregistrement de la provenance ; -- aucune modification du fichier source ; -- détection du type MIME réel ; -- détection des collisions ; -- traitement asynchrone ; -- annulation sûre. - -À la fin de l’import, la nouvelle preuve doit être automatiquement -sélectionnée dans la fenêtre de création de la personne. - -## Aperçu des preuves - -La fenêtre de sélection et d’import doit fournir un aperçu suffisamment -grand pour identifier le contenu sans occuper toute la fenêtre. - -Prévoir une zone responsive avec : - -- conservation du ratio ; -- ajustement à la zone ; -- zoom avant et arrière ; -- retour à l’ajustement ; -- défilement lorsque l’image est agrandie ; -- message explicite lorsque l’aperçu est indisponible. - -Prise en charge minimale : - -- image : aperçu direct ; -- PDF : première page avec navigation entre les pages ; -- vidéo : miniature, durée et informations principales ; -- EML : résumé des en-têtes et liste des pièces jointes ; -- autre fichier : icône, type MIME, taille et métadonnées disponibles. - -L’aperçu est une représentation dérivée. - -Il ne doit jamais modifier le fichier original. - -Toute conversion, génération de miniature, rotation, amélioration, -redimensionnement ou rendu PDF doit être réalisée dans une zone de -travail temporaire contrôlée. - -## Qualification du type de preuve - -Permettre de sélectionner ou confirmer un type contrôlé, par exemple : - -- Document d’identité ; -- Carte nationale d’identité ; -- Passeport ; -- Permis de conduire ; -- Document bancaire ; -- Capture d’écran ; -- Conversation ; -- Courrier électronique ; -- PDF ; -- Photo ; -- Vidéo ; -- Billet ou justificatif de commande ; -- Autre. - -La détection automatique peut proposer un type, mais l’utilisateur doit -toujours pouvoir le corriger avant validation. - -Le type choisi détermine les analyses proposées. - -## Document d’identité - -Lorsqu’une preuve est qualifiée comme document d’identité, proposer : - - Analyser le document - -Le traitement doit être asynchrone et annulable. - -Il doit : - -1. vérifier l’intégrité de la preuve ; -2. créer une copie de travail ; -3. conserver l’original strictement intact ; -4. appliquer les conversions uniquement à la copie ; -5. exécuter l’OCR ; -6. conserver le texte OCR brut ; -7. proposer des champs structurés ; -8. attendre une validation humaine avant toute intégration. - -## Champs OCR proposés - -Selon les informations réellement visibles, proposer notamment : - -- nom ; -- prénoms ; -- sexe déclaré ; -- date de naissance ; -- lieu de naissance ; -- nationalité déclarée ; -- taille ; -- numéro du document ; -- date de délivrance ; -- date d’expiration ; -- autorité de délivrance ; -- zone lisible par machine ; -- autres valeurs observées. - -Ne jamais inventer une valeur absente ou illisible. - -Chaque proposition doit posséder un état : - -- détectée ; -- partielle ; -- incertaine ; -- invalide ; -- corrigée ; -- rejetée ; -- confirmée. - -## Validation et correction humaine - -Avant la création de la personne, afficher côte à côte autant que possible : - -- l’aperçu du document ; -- les valeurs extraites ; -- les champs modifiables ; -- le niveau de confiance OCR ; -- la provenance précise. - -Pour chaque champ, conserver séparément : - -- valeur OCR brute ; -- valeur normalisée ; -- valeur corrigée manuellement ; -- valeur finalement confirmée ; -- méthode d’extraction ; -- langue OCR ; -- version de l’outil ; -- date UTC ; -- preuve source ; -- page ou zone source lorsque disponible. - -Une correction manuelle ne doit jamais écraser le résultat OCR brut. - -L’utilisateur doit pouvoir : - -- modifier une proposition ; -- rejeter une proposition ; -- laisser un champ vide ; -- revenir à la valeur OCR ; -- confirmer uniquement certains champs. - -## Authenticité et identité usurpée - -Ajouter un statut contrôlé pour le document : - -- Authenticité indéterminée ; -- Présumé authentique ; -- Suspect ; -- Présumé falsifié ; -- Falsifié confirmé ; -- Document usurpé présumé ; -- Document usurpé confirmé. - -Les statuts affirmatifs doivent nécessiter une validation explicite et -une justification. - -L’OCR ne doit jamais conclure : - -- que le document est authentique ; -- que la personne figurant sur le document est l’auteur ; -- que l’identité est réellement usurpée ; -- que le titulaire a participé aux faits. - -Dans le cas courant, le document doit pouvoir être enregistré comme : - - Identité présentée — authenticité indéterminée - -avec une hypothèse séparée : - - Identité potentiellement usurpée - -## Création de la personne - -Après validation, créer la personne avec uniquement les champs confirmés. - -La personne doit être liée à la preuve par une relation factuelle, par -exemple : - -- Identité observée dans ; -- Document présenté au nom de ; -- Données extraites depuis ; -- Identité déclarée dans. - -Ne pas créer automatiquement une relation : - -- Est l’auteur ; -- Identité réelle de ; -- A usurpé l’identité de. - -Les résultats OCR rejetés ne doivent pas devenir des attributs de la -personne. - -## Intégrité et provenance - -Le fichier original doit rester immuable. - -Avant chaque analyse : - -- recalculer ou vérifier le SHA-256 ; -- bloquer l’analyse en cas de divergence ; -- ne jamais réécrire l’empreinte enregistrée pour masquer une différence. - -Tous les fichiers dérivés doivent être identifiables comme tels : - -- miniature ; -- rendu de page PDF ; -- image préparée pour OCR ; -- recadrage ; -- correction d’orientation ; -- texte OCR ; -- JSON de métadonnées. - -Chaque dérivé doit conserver : - -- preuve parente ; -- outil ; -- version ; -- arguments ; -- date UTC ; -- SHA-256 ; -- statut ; -- avertissements. - -## Interface - -Le parcours doit rester possible depuis une seule fenêtre ou un assistant -cohérent : - - Création de la personne - ↓ - choix du rôle - ↓ - sélection ou import de la preuve - ↓ - aperçu - ↓ - qualification du type - ↓ - analyse facultative - ↓ - révision des propositions - ↓ - création de la personne et des relations confirmées - -Utiliser des boutons compacts avec icônes et infobulles. - -Ne pas ajouter de gros boutons occupant inutilement l’interface. - -La fermeture ou l’annulation ne doit créer ni personne partielle, ni -attribut partiel, ni relation partielle. - -## Transactions - -La création finale doit être transactionnelle pour : - -- la personne ; -- ses attributs confirmés ; -- le rattachement à la preuve ; -- les observations OCR confirmées ; -- les relations ; -- les références aux fichiers dérivés conservés. - -En cas d’échec : - -- rollback complet ; -- original inchangé ; -- aucune personne incomplète ; -- aucune relation orpheline ; -- aucun fichier temporaire présenté comme preuve permanente. - -## Tests obligatoires - -Ajouter des fixtures exclusivement synthétiques. - -Couvrir au minimum : - -1. choix de chaque rôle contrôlé ; -2. sélection d’une preuve existante ; -3. import d’une nouvelle preuve depuis la fenêtre ; -4. sélection automatique après import ; -5. aperçu image ; -6. aperçu PDF ; -7. aperçu indisponible ; -8. navigation entre pages ; -9. qualification manuelle du type ; -10. document d’identité déclenchant l’OCR ; -11. autre type ne déclenchant pas automatiquement l’OCR ; -12. original inchangé ; -13. SHA-256 inchangé ; -14. travail sur copie ; -15. texte OCR brut conservé ; -16. valeur normalisée séparée ; -17. correction manuelle séparée ; -18. rejet d’un champ ; -19. champ illisible laissé vide ; -20. création avec seulement les valeurs confirmées ; -21. provenance par champ ; -22. annulation ; -23. rollback ; -24. fermeture de fenêtre pendant l’analyse ; -25. changement d’enquête pendant l’analyse ; -26. aucune écriture dans la mauvaise base ; -27. aucune donnée réelle dans les fixtures ; -28. aucune conclusion automatique sur l’authenticité ; -29. aucune fusion automatique avec l’auteur présumé ; -30. réouverture de l’enquête avec données persistantes. - -Séparer autant que possible : - -- services d’import ; -- génération d’aperçu ; -- OCR ; -- modèle de révision ; -- DAO ; -- tests GTK ciblés ; -- validation visuelle manuelle. - -## Critères d’acceptation - -Le ticket est terminé seulement si : - -- le rôle de la personne peut être sélectionné ; -- une preuve existante peut être choisie ; -- une nouvelle preuve peut être importée sans quitter le parcours ; -- un aperçu exploitable est disponible ; -- le type de preuve peut être confirmé ou corrigé ; -- un document d’identité peut être analysé par OCR ; -- les propositions OCR sont modifiables avant intégration ; -- le texte OCR brut n’est jamais écrasé ; -- l’original reste inchangé ; -- la provenance est persistée ; -- seules les valeurs confirmées alimentent la personne ; -- aucune identité n’est attribuée automatiquement à l’auteur présumé ; -- tous les tests passent. - -## Sécurité de développement - -Codex et tout agent de développement doivent travailler uniquement avec : - -- le dépôt source ; -- des fixtures synthétiques ; -- des bases SQLite temporaires synthétiques. - -Ils ne doivent jamais accéder à : - -- Enquete.sqlite réelle ; -- documents d’identité réels ; -- captures réelles ; -- e-mails réels ; -- données bancaires réelles ; -- vidéos réelles ; -- autres preuves de l’enquête. diff --git a/wc_out.txt b/wc_out.txt deleted file mode 100644 index 7ddf263..0000000 --- a/wc_out.txt +++ /dev/null @@ -1,10 +0,0 @@ - 9339 src/core/application.c - 142 src/core/person_creation_task.c - 2037 src/views/create_person_dialog.c - 660 src/views/evidence_metadata_dialog.c - 2245 src/views/main_window.c - 286 src/views/person_factual_relation_editor.c - 193 src/views/person_vocabulary_adapter.c - 1550 src/widgets/entity_details_panel.c - 4078 src/widgets/workspace.c - 20530 total