fonzarely/regine-photos-archiverpublic⑂ Fork 0
⑂ 6ad5e0a
Commits
⬇ Clone ▾
git clone https://git.rickub.com/fonzarely/regine-photos-archiver.git
git clone ssh://git@rickub.com/fonzarely/regine-photos-archiver.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

specs

Fabien Champigny committed 2026-09-19T14:07:35+02:00 Browse files
6ad5e0a parent: 3d729f0
modified specs/001-import-photos/contracts/cli-import.md +1 -1
@@ -23,7 +23,7 @@ regine import /Volumes/CARTE_SD [--titre TEXTE] [--destination nouveau|sous-doss
2323 - Échec de vérification d'un fichier (Edge Case lecture corrompue) : fichier signalé sur stderr, import interrompu pour ce fichier, carte non marquée sûre à effacer, code non-zéro.
2424 - Espace disque insuffisant (Edge Case) : message clair avant toute copie, code non-zéro.
2525 - Collision de nom de dossier (FR-012) : proposition de suffixe ou demande de confirmation sur stdout ; sans `--yes`, attend une réponse interactive.
26-- **`--destination fusion:ID` ciblant un dossier présent uniquement dans l'archive (pas en local)** : commande refusée avec un message explicite indiquant que ce cas dépend du mécanisme de checkout/réconciliation, **non implémenté dans cette version** (cf. `plan.md` § Complexity Tracking) — code non-zéro, aucune écriture.
26+- **`--destination fusion:ID` ciblant un dossier présent uniquement dans l'archive (pas en local)** : la commande effectue d'abord un checkout de ce dossier (`regine_core.archive.checkout`, `specs/005-checkout-reconciliation`) avant d'y intégrer les nouveaux fichiers, puis suit le cycle normal de réconciliation pour le réarchivage — transparent pour l'utilisateur, hormis un temps de transfert réseau supplémentaire signalé sur stdout. Si ce dossier est déjà verrouillé par un autre checkout en cours, la commande échoue explicitement (cf. `specs/005-checkout-reconciliation/contracts/cli-checkout-reconcile.md`).
2727
2828 ## Notes de scriptabilité
2929
@@ -23,7 +23,7 @@ regine import /Volumes/CARTE_SD [--titre TEXTE] [--destination nouveau|sous-doss
23 - Échec de vérification d'un fichier (Edge Case lecture corrompue) : fichier signalé sur stderr, import interrompu pour ce fichier, carte non marquée sûre à effacer, code non-zéro.23 - Échec de vérification d'un fichier (Edge Case lecture corrompue) : fichier signalé sur stderr, import interrompu pour ce fichier, carte non marquée sûre à effacer, code non-zéro.
24 - Espace disque insuffisant (Edge Case) : message clair avant toute copie, code non-zéro.24 - Espace disque insuffisant (Edge Case) : message clair avant toute copie, code non-zéro.
25 - Collision de nom de dossier (FR-012) : proposition de suffixe ou demande de confirmation sur stdout ; sans `--yes`, attend une réponse interactive.25 - Collision de nom de dossier (FR-012) : proposition de suffixe ou demande de confirmation sur stdout ; sans `--yes`, attend une réponse interactive.
26-- **`--destination fusion:ID` ciblant un dossier présent uniquement dans l'archive (pas en local)** : commande refusée avec un message explicite indiquant que ce cas dépend du mécanisme de checkout/réconciliation, **non implémenté dans cette version** (cf. `plan.md` § Complexity Tracking) — code non-zéro, aucune écriture.26+- **`--destination fusion:ID` ciblant un dossier présent uniquement dans l'archive (pas en local)** : la commande effectue d'abord un checkout de ce dossier (`regine_core.archive.checkout`, `specs/005-checkout-reconciliation`) avant d'y intégrer les nouveaux fichiers, puis suit le cycle normal de réconciliation pour le réarchivage — transparent pour l'utilisateur, hormis un temps de transfert réseau supplémentaire signalé sur stdout. Si ce dossier est déjà verrouillé par un autre checkout en cours, la commande échoue explicitement (cf. `specs/005-checkout-reconciliation/contracts/cli-checkout-reconcile.md`).
27 27
28 ## Notes de scriptabilité28 ## Notes de scriptabilité
29 29
modified specs/001-import-photos/contracts/regine-core-api.md +3 -3
@@ -12,7 +12,7 @@ Construit la répartition jour par jour, exclut les dates aberrantes (FR-002/003
1212
1313 ## `destination.resoudre_destination(groupe: GroupeImport, choix: ChoixUtilisateur) -> DestinationChoisie`
1414
15-Traduit le choix de l'utilisateur (FR-007) en `DestinationChoisie`. Pour `nouveau_dossier`/`nouveau_parent`, appelle `regine_core.dossier.root.determine_root` (`specs/004-categorisation-dossiers`) avec la catégorie éventuellement choisie. Pour `nouveau_sous_dossier`, réutilise le `RootLocation` du parent sans nouvel appel (FR-006 de specs/004). Si `necessite_checkout_archive` est vrai (fusion vers un dossier archivé, pas local), **lève `ChecoutNonDisponibleError`** plutôt que d'échouer silencieusement — cf. `plan.md` § Complexity Tracking.
15+Traduit le choix de l'utilisateur (FR-007) en `DestinationChoisie`. Pour `nouveau_dossier`/`nouveau_parent`, appelle `regine_core.dossier.root.determine_root` (`specs/004-categorisation-dossiers`) avec la catégorie éventuellement choisie. Pour `nouveau_sous_dossier`, réutilise le `RootLocation` du parent sans nouvel appel (FR-006 de specs/004). Si `necessite_checkout_archive` est vrai (fusion vers un dossier archivé, pas local), appelle `regine_core.archive.checkout.checkout(dossier_cible, dest_locale)` (`specs/005-checkout-reconciliation`) avant de poursuivre ; propage l'exception dédiée de `specs/005` si le dossier est déjà verrouillé, plutôt que d'échouer silencieusement.
1616
1717 ## `destination.lister_dossiers_candidats(titre_partiel: str, date_proche: date) -> list[Path]`
1818
@@ -38,6 +38,6 @@ Construit l'objet structuré (nombre de fichiers, taille totale, chemin de desti
3838
3939 Transfert final vérifié depuis la copie locale déjà renommée (FR-019). Lève une erreur si `resume_confirme` est faux — ne DOIT jamais être appelée sans confirmation explicite préalable côté appelant.
4040
41-## Dépendance bloquée
41+## Dépendance résolue
4242
43-`ChecoutNonDisponibleError` (levée par `resoudre_destination`) documente explicitement le sous-scénario FR-009 non implémenté : fusion vers un dossier présent uniquement dans l'archive. À lever tant qu'aucune spec/plan du mécanisme de checkout/réconciliation n'existe dans ce dépôt (cf. `plan.md` § Complexity Tracking, `research.md` § 6).
43+Le sous-scénario FR-009 (fusion vers un dossier présent uniquement dans l'archive) consomme désormais `regine_core.archive.checkout` (`specs/005-checkout-reconciliation`) — cf. `research.md` § 6, mis à jour le 2026-09-19.
@@ -12,7 +12,7 @@ Construit la répartition jour par jour, exclut les dates aberrantes (FR-002/003
12 12
13 ## `destination.resoudre_destination(groupe: GroupeImport, choix: ChoixUtilisateur) -> DestinationChoisie`13 ## `destination.resoudre_destination(groupe: GroupeImport, choix: ChoixUtilisateur) -> DestinationChoisie`
14 14
15-Traduit le choix de l'utilisateur (FR-007) en `DestinationChoisie`. Pour `nouveau_dossier`/`nouveau_parent`, appelle `regine_core.dossier.root.determine_root` (`specs/004-categorisation-dossiers`) avec la catégorie éventuellement choisie. Pour `nouveau_sous_dossier`, réutilise le `RootLocation` du parent sans nouvel appel (FR-006 de specs/004). Si `necessite_checkout_archive` est vrai (fusion vers un dossier archivé, pas local), **lève `ChecoutNonDisponibleError`** plutôt que d'échouer silencieusement — cf. `plan.md` § Complexity Tracking.15+Traduit le choix de l'utilisateur (FR-007) en `DestinationChoisie`. Pour `nouveau_dossier`/`nouveau_parent`, appelle `regine_core.dossier.root.determine_root` (`specs/004-categorisation-dossiers`) avec la catégorie éventuellement choisie. Pour `nouveau_sous_dossier`, réutilise le `RootLocation` du parent sans nouvel appel (FR-006 de specs/004). Si `necessite_checkout_archive` est vrai (fusion vers un dossier archivé, pas local), appelle `regine_core.archive.checkout.checkout(dossier_cible, dest_locale)` (`specs/005-checkout-reconciliation`) avant de poursuivre ; propage l'exception dédiée de `specs/005` si le dossier est déjà verrouillé, plutôt que d'échouer silencieusement.
16 16
17 ## `destination.lister_dossiers_candidats(titre_partiel: str, date_proche: date) -> list[Path]`17 ## `destination.lister_dossiers_candidats(titre_partiel: str, date_proche: date) -> list[Path]`
18 18
@@ -38,6 +38,6 @@ Construit l'objet structuré (nombre de fichiers, taille totale, chemin de desti
38 38
39 Transfert final vérifié depuis la copie locale déjà renommée (FR-019). Lève une erreur si `resume_confirme` est faux — ne DOIT jamais être appelée sans confirmation explicite préalable côté appelant.39 Transfert final vérifié depuis la copie locale déjà renommée (FR-019). Lève une erreur si `resume_confirme` est faux — ne DOIT jamais être appelée sans confirmation explicite préalable côté appelant.
40 40
41-## Dépendance bloquée41+## Dépendance résolue
42 42
43-`ChecoutNonDisponibleError` (levée par `resoudre_destination`) documente explicitement le sous-scénario FR-009 non implémenté : fusion vers un dossier présent uniquement dans l'archive. À lever tant qu'aucune spec/plan du mécanisme de checkout/réconciliation n'existe dans ce dépôt (cf. `plan.md` § Complexity Tracking, `research.md` § 6).43+Le sous-scénario FR-009 (fusion vers un dossier présent uniquement dans l'archive) consomme désormais `regine_core.archive.checkout` (`specs/005-checkout-reconciliation`) — cf. `research.md` § 6, mis à jour le 2026-09-19.
modified specs/001-import-photos/data-model.md +1 -1
@@ -29,7 +29,7 @@ Entités dérivées de `spec.md` § Key Entities et Functional Requirements. Ré
2929 | `type` | énumération : `nouveau_dossier` \| `nouveau_sous_dossier` \| `fusion` \| `nouveau_parent` | Choix explicite de l'utilisateur (FR-007) |
3030 | `dossier_cible` | `Path \| None` | Requis si `type in {nouveau_sous_dossier, fusion}` |
3131 | `root_location` | `RootLocation \| None` | Résolu via `regine_core.dossier.root.determine_root` (specs/004) uniquement si `type in {nouveau_dossier, nouveau_parent}` ; hérité du parent sinon (FR-006 de specs/004) |
32-| `necessite_checkout_archive` | bool | `True` si `type == fusion` et `dossier_cible` n'existe qu'archivé, pas en local (FR-009) — **déclenche le sous-scénario bloqué, cf. plan.md § Complexity Tracking** |
32+| `necessite_checkout_archive` | bool | `True` si `type == fusion` et `dossier_cible` n'existe qu'archivé, pas en local (FR-009) — déclenche un appel à `regine_core.archive.checkout` (`specs/005-checkout-reconciliation`) avant d'intégrer les nouveaux fichiers |
3333
3434 ## Renommage
3535
@@ -29,7 +29,7 @@ Entités dérivées de `spec.md` § Key Entities et Functional Requirements. Ré
29 | `type` | énumération : `nouveau_dossier` \| `nouveau_sous_dossier` \| `fusion` \| `nouveau_parent` | Choix explicite de l'utilisateur (FR-007) |29 | `type` | énumération : `nouveau_dossier` \| `nouveau_sous_dossier` \| `fusion` \| `nouveau_parent` | Choix explicite de l'utilisateur (FR-007) |
30 | `dossier_cible` | `Path \| None` | Requis si `type in {nouveau_sous_dossier, fusion}` |30 | `dossier_cible` | `Path \| None` | Requis si `type in {nouveau_sous_dossier, fusion}` |
31 | `root_location` | `RootLocation \| None` | Résolu via `regine_core.dossier.root.determine_root` (specs/004) uniquement si `type in {nouveau_dossier, nouveau_parent}` ; hérité du parent sinon (FR-006 de specs/004) |31 | `root_location` | `RootLocation \| None` | Résolu via `regine_core.dossier.root.determine_root` (specs/004) uniquement si `type in {nouveau_dossier, nouveau_parent}` ; hérité du parent sinon (FR-006 de specs/004) |
32-| `necessite_checkout_archive` | bool | `True` si `type == fusion` et `dossier_cible` n'existe qu'archivé, pas en local (FR-009) — **déclenche le sous-scénario bloqué, cf. plan.md § Complexity Tracking** |32+| `necessite_checkout_archive` | bool | `True` si `type == fusion` et `dossier_cible` n'existe qu'archivé, pas en local (FR-009) — déclenche un appel à `regine_core.archive.checkout` (`specs/005-checkout-reconciliation`) avant d'intégrer les nouveaux fichiers |
33 33
34 ## Renommage34 ## Renommage
35 35
modified specs/001-import-photos/plan.md +9 -13
@@ -14,7 +14,7 @@ Pipeline complet depuis une carte mémoire jusqu'au premier archivage : copie v
1414
1515 **Primary Dependencies**: `exiftool` (déjà une dépendance actée par `specs/002-profil-boitiers-optionnel` pour `regine_core.metadata.exif`, étendu ici pour la lecture de la date de prise de vue et l'écriture de l'identifiant pérenne) ; bibliothèque standard uniquement sinon (`hashlib` pour les sommes de contrôle, `pathlib`/`shutil` pour la copie et le renommage, `difflib` pour la recherche de dossiers candidats par titre proche, réutilisant la même approche que `specs/004-categorisation-dossiers`)
1616
17-**Storage**: ce module n'introduit aucune nouvelle base ; il consomme la base de contexte centralisée via `regine_core.camera_profile` (specs/002) et `regine_core.config.categories`/`regine_core.dossier.root` (specs/004). Le manifeste persistant par dossier (checksums, réconciliation — constitution § Workflow d'archivage) relève d'un futur module `archive`/`dossier` non encore spécifié ; ce plan ne le redéfinit pas (cf. Constraints ci-dessous)
17+**Storage**: ce module n'introduit aucune nouvelle base ; il consomme la base de contexte centralisée via `regine_core.camera_profile` (specs/002) et `regine_core.config.categories`/`regine_core.dossier.root` (specs/004). Le manifeste persistant par dossier (checksums, réconciliation) est désormais spécifié et planifié par `specs/005-checkout-reconciliation` (`regine_core.archive`/`regine_core.integrity`) ; ce module l'utilise pour FR-009 sans le redéfinir
1818
1919 **Testing**: pytest ; tests unitaires sur le découpage jour par jour et la détection de dates aberrantes, sur le renommage synchronisé, sur la construction du chemin final (délégué à `specs/004`) ; tests d'intégration sur le pipeline complet copie→analyse→destination→renommage→confirmation, en répertoires temporaires simulant carte/local/archive (aucune écriture réseau réelle)
2020
@@ -24,7 +24,7 @@ Pipeline complet depuis une carte mémoire jusqu'au premier archivage : copie v
2424
2525 **Performance Goals**: aucune lecture de la carte mémoire ne doit dépasser une seule passe par fichier (FR-001/Edge Case déjà tranché) ; pas d'autre cible chiffrée spécifique
2626
27-**Constraints**: le sous-scénario de fusion vers un dossier **déjà archivé** sur le NAS (FR-009, Acceptance Scenario 4 de User Story 3) dépend du mécanisme de checkout/réconciliation par hash (constitution § Workflow d'archivage, notes de conception section 8), qui n'a **aucune spec ni plan dans ce dépôt à ce jour** — ce plan implémente tout le reste du pipeline et documente ce sous-scénario comme bloqué (cf. Complexity Tracking) ; la fusion vers un dossier présent seulement en local reste pleinement implémentable
27+**Constraints**: le sous-scénario de fusion vers un dossier **déjà archivé** sur le NAS (FR-009, Acceptance Scenario 4 de User Story 3) appelle `regine_core.archive.checkout.checkout` (`specs/005-checkout-reconciliation`) sur le dossier ciblé avant d'y intégrer les nouveaux fichiers ; ce plan consomme cette API sans la redéfinir
2828
2929 **Scale/Scope**: import typique de quelques dizaines à quelques milliers de fichiers par carte mémoire, usage mono-utilisateur
3030
@@ -34,7 +34,7 @@ Pipeline complet depuis une carte mémoire jusqu'au premier archivage : copie v
3434
3535 | Principe / contrainte | Évaluation | Justification |
3636 |---|---|---|
37-| I. Fichier maître intouchable | PASS | Un fichier maître n'est renommé qu'une seule fois, avant son premier archivage (FR-013) ; aucune modification après archivage n'est effectuée par ce module (la vérification d'anomalie ultérieure relève du futur module de réconciliation, hors périmètre). |
37+| I. Fichier maître intouchable | PASS | Un fichier maître n'est renommé qu'une seule fois, avant son premier archivage (FR-013) ; aucune modification après archivage n'est effectuée par ce module (la vérification d'anomalie ultérieure relève de `specs/005-checkout-reconciliation`, qui l'implémente désormais). |
3838 | II. Confirmation explicite avant toute action à risque | PASS | FR-018 : résumé complet (fichiers, taille, dossier de destination, répertoire racine) présenté et confirmé avant toute écriture sur l'archive ; FR-012 : collision de nom traitée par suffixe ou confirmation, jamais écrasement silencieux. |
3939 | III. Identité par contenu, jamais par nom de fichier seul | PASS | FR-004 : seuls les fichiers réellement nouveaux (par somme de contrôle) entrent dans l'analyse ; FR-015 : désambiguïsation de boîtiers déléguée à `specs/002`, elle-même fondée sur les métadonnées, jamais sur le seul nom de fichier. |
4040 | IV. Métadonnées ouvertes et embarquées | PASS | FR-017 : l'identifiant pérenne est écrit dans les métadonnées XMP embarquées du fichier (réutilisation du champ standard `xmpMM:DocumentID`, cf. research.md § 3), pas dans une base séparée. |
@@ -45,17 +45,13 @@ Pipeline complet depuis une carte mémoire jusqu'au premier archivage : copie v
4545 | Formats ouverts et documentés | PASS | XMP pour l'identifiant pérenne ; aucune base propriétaire introduite. |
4646 | Vérification d'intégrité systématique | PASS | FR-001 : vérification par somme de contrôle sur toute copie carte→local et sur le transfert local→archive (FR-019). |
4747
48-**Violation à justifier** (Complexity Tracking) : le sous-scénario FR-009 (fusion vers dossier déjà archivé) ne peut pas être implémenté sans le mécanisme de checkout/réconciliation, absent du dépôt. Ce n'est pas une violation de principe mais une dépendance externe manquante — documentée ci-dessous plutôt que contournée par une implémentation partielle ou incorrecte.
48+Aucune violation identifiée ; la section Complexity Tracking reste vide.
4949
5050 ## Complexity Tracking
5151
52-> Rempli exceptionnellement pour documenter une dépendance bloquante, pas une violation de principe.
52+*Aucune violation de gate à justifier — section laissée vide intentionnellement.*
5353
54-| Élément | Pourquoi nécessaire | Alternative plus simple écartée |
55-|---|---|---|
56-| FR-009 (fusion vers dossier déjà archivé, nécessite un checkout) | Explicitement requis par `specs/001-import-photos` User Story 3 Acceptance Scenario 4 | Implémenter un checkout minimal ad hoc dans ce module plutôt que d'attendre une spec dédiée — écarté : dupliquerait par anticipation un mécanisme déjà nommé et cadré ailleurs (constitution § Workflow d'archivage, notes de conception section 8), avec un risque réel de divergence si le futur module `archive`/`dossier` retient une conception différente. Le sous-scénario reste documenté comme bloqué plutôt que mal implémenté. |
57-
58-**Re-check post Phase 1** (après génération de `data-model.md`, `contracts/`, `quickstart.md`) : le modèle de données (Groupe d'import, Destination, Renommage) et les contrats (CLI `regine import`, API interne `regine_core.import_carte`) ne modifient aucune évaluation ci-dessus ; le sous-scénario FR-009 reste explicitement marqué bloqué dans `contracts/cli-import.md` et `quickstart.md` plutôt que silencieusement omis.
54+**Re-check post Phase 1** (après génération de `data-model.md`, `contracts/`, `quickstart.md`) : le modèle de données (Groupe d'import, Destination, Renommage) et les contrats (CLI `regine import`, API interne `regine_core.import_carte`) confirment chaque évaluation ci-dessus. Le sous-scénario FR-009 est désormais implémentable via `specs/005-checkout-reconciliation` (cf. `contracts/cli-import.md` et `quickstart.md`, mis à jour en conséquence) — plus aucune dépendance manquante.
5955
6056 ## Project Structure
6157
@@ -84,7 +80,7 @@ packages/regine-core/
8480 │ ├── __init__.py
8581 │ ├── copie.py # copie vérifiée carte→local, une seule lecture (FR-001), filtrage nouveaux fichiers (FR-004)
8682 │ ├── groupage.py # répartition jour par jour, détection dates aberrantes, découpage en groupes (FR-002/003/005/006)
87-│ ├── destination.py # orchestration destination : simple/sous-dossier/fusion/parent + catégorie (FR-007/008/009), délègue à regine_core.dossier.root
83+│ ├── destination.py # orchestration destination : simple/sous-dossier/fusion/parent + catégorie (FR-007/008/009), délègue à regine_core.dossier.root et, pour une fusion vers un dossier déjà archivé, à regine_core.archive.checkout (specs/005)
8884 │ ├── nommage.py # construction titre/nom dossier, collision (FR-010/012), renommage synchronisé (FR-013/014)
8985 │ ├── identifiant.py # génération + écriture identifiant pérenne (FR-017)
9086 │ └── push.py # résumé + confirmation + transfert final (FR-018/019)
@@ -98,12 +94,12 @@ packages/regine-core/
9894 └── integration/
9995 ├── test_pipeline_import_simple.py # US1 : carte → dossier archivé, résumé confirmé
10096 ├── test_pipeline_multi_jours.py # US2 : détachement, groupes multiples
101- └── test_pipeline_voyage.py # US3 : dossier parent + sous-dossier + fusion locale + désambiguïsation boîtiers (fusion vers archive : bloquée, cf. Complexity Tracking)
97+ └── test_pipeline_voyage.py # US3 : dossier parent + sous-dossier + fusion locale ET fusion vers dossier archivé (via specs/005-checkout-reconciliation) + désambiguïsation boîtiers
10298
10399 packages/regine-cli/
104100 └── src/regine_cli/
105101 └── import_cmd.py # NOUVEAU : commande `regine import <carte>`, premier point d'intégration réel (dossier.root, camera_profile, metadata.exif)
106102 ```
107103
108-**Structure Decision**: Extension du monorepo existant — nouveau module `regine_core/import_carte/` (aucun code existant avant ce plan pour ce module), et première commande CLI concrète du projet (`regine import`), qui devient le point d'intégration réel des API déjà contractées par `specs/002-profil-boitiers-optionnel` et `specs/004-categorisation-dossiers` (jusqu'ici documentées mais non consommées). `regine_core/metadata/exif.py` (créé par specs/002) est étendu plutôt que dupliqué.
104+**Structure Decision**: Extension du monorepo existant — nouveau module `regine_core/import_carte/` (aucun code existant avant ce plan pour ce module), et première commande CLI concrète du projet (`regine import`), qui devient le point d'intégration réel des API déjà contractées par `specs/002-profil-boitiers-optionnel` et `specs/004-categorisation-dossiers` (jusqu'ici documentées mais non consommées). `regine_core/metadata/exif.py` (créé par specs/002) est étendu plutôt que dupliqué. **Mise à jour du 2026-09-19** : le sous-scénario FR-009 (fusion vers un dossier déjà archivé) consomme désormais `regine_core.archive.checkout` (`specs/005-checkout-reconciliation`, planifiée) au lieu d'être documenté comme bloqué.
109105
@@ -14,7 +14,7 @@ Pipeline complet depuis une carte mémoire jusqu'au premier archivage : copie v
14 14
15 **Primary Dependencies**: `exiftool` (déjà une dépendance actée par `specs/002-profil-boitiers-optionnel` pour `regine_core.metadata.exif`, étendu ici pour la lecture de la date de prise de vue et l'écriture de l'identifiant pérenne) ; bibliothèque standard uniquement sinon (`hashlib` pour les sommes de contrôle, `pathlib`/`shutil` pour la copie et le renommage, `difflib` pour la recherche de dossiers candidats par titre proche, réutilisant la même approche que `specs/004-categorisation-dossiers`)15 **Primary Dependencies**: `exiftool` (déjà une dépendance actée par `specs/002-profil-boitiers-optionnel` pour `regine_core.metadata.exif`, étendu ici pour la lecture de la date de prise de vue et l'écriture de l'identifiant pérenne) ; bibliothèque standard uniquement sinon (`hashlib` pour les sommes de contrôle, `pathlib`/`shutil` pour la copie et le renommage, `difflib` pour la recherche de dossiers candidats par titre proche, réutilisant la même approche que `specs/004-categorisation-dossiers`)
16 16
17-**Storage**: ce module n'introduit aucune nouvelle base ; il consomme la base de contexte centralisée via `regine_core.camera_profile` (specs/002) et `regine_core.config.categories`/`regine_core.dossier.root` (specs/004). Le manifeste persistant par dossier (checksums, réconciliation — constitution § Workflow d'archivage) relève d'un futur module `archive`/`dossier` non encore spécifié ; ce plan ne le redéfinit pas (cf. Constraints ci-dessous)17+**Storage**: ce module n'introduit aucune nouvelle base ; il consomme la base de contexte centralisée via `regine_core.camera_profile` (specs/002) et `regine_core.config.categories`/`regine_core.dossier.root` (specs/004). Le manifeste persistant par dossier (checksums, réconciliation) est désormais spécifié et planifié par `specs/005-checkout-reconciliation` (`regine_core.archive`/`regine_core.integrity`) ; ce module l'utilise pour FR-009 sans le redéfinir
18 18
19 **Testing**: pytest ; tests unitaires sur le découpage jour par jour et la détection de dates aberrantes, sur le renommage synchronisé, sur la construction du chemin final (délégué à `specs/004`) ; tests d'intégration sur le pipeline complet copie→analyse→destination→renommage→confirmation, en répertoires temporaires simulant carte/local/archive (aucune écriture réseau réelle)19 **Testing**: pytest ; tests unitaires sur le découpage jour par jour et la détection de dates aberrantes, sur le renommage synchronisé, sur la construction du chemin final (délégué à `specs/004`) ; tests d'intégration sur le pipeline complet copie→analyse→destination→renommage→confirmation, en répertoires temporaires simulant carte/local/archive (aucune écriture réseau réelle)
20 20
@@ -24,7 +24,7 @@ Pipeline complet depuis une carte mémoire jusqu'au premier archivage : copie v
24 24
25 **Performance Goals**: aucune lecture de la carte mémoire ne doit dépasser une seule passe par fichier (FR-001/Edge Case déjà tranché) ; pas d'autre cible chiffrée spécifique25 **Performance Goals**: aucune lecture de la carte mémoire ne doit dépasser une seule passe par fichier (FR-001/Edge Case déjà tranché) ; pas d'autre cible chiffrée spécifique
26 26
27-**Constraints**: le sous-scénario de fusion vers un dossier **déjà archivé** sur le NAS (FR-009, Acceptance Scenario 4 de User Story 3) dépend du mécanisme de checkout/réconciliation par hash (constitution § Workflow d'archivage, notes de conception section 8), qui n'a **aucune spec ni plan dans ce dépôt à ce jour** — ce plan implémente tout le reste du pipeline et documente ce sous-scénario comme bloqué (cf. Complexity Tracking) ; la fusion vers un dossier présent seulement en local reste pleinement implémentable27+**Constraints**: le sous-scénario de fusion vers un dossier **déjà archivé** sur le NAS (FR-009, Acceptance Scenario 4 de User Story 3) appelle `regine_core.archive.checkout.checkout` (`specs/005-checkout-reconciliation`) sur le dossier ciblé avant d'y intégrer les nouveaux fichiers ; ce plan consomme cette API sans la redéfinir
28 28
29 **Scale/Scope**: import typique de quelques dizaines à quelques milliers de fichiers par carte mémoire, usage mono-utilisateur29 **Scale/Scope**: import typique de quelques dizaines à quelques milliers de fichiers par carte mémoire, usage mono-utilisateur
30 30
@@ -34,7 +34,7 @@ Pipeline complet depuis une carte mémoire jusqu'au premier archivage : copie v
34 34
35 | Principe / contrainte | Évaluation | Justification |35 | Principe / contrainte | Évaluation | Justification |
36 |---|---|---|36 |---|---|---|
37-| I. Fichier maître intouchable | PASS | Un fichier maître n'est renommé qu'une seule fois, avant son premier archivage (FR-013) ; aucune modification après archivage n'est effectuée par ce module (la vérification d'anomalie ultérieure relève du futur module de réconciliation, hors périmètre). |37+| I. Fichier maître intouchable | PASS | Un fichier maître n'est renommé qu'une seule fois, avant son premier archivage (FR-013) ; aucune modification après archivage n'est effectuée par ce module (la vérification d'anomalie ultérieure relève de `specs/005-checkout-reconciliation`, qui l'implémente désormais). |
38 | II. Confirmation explicite avant toute action à risque | PASS | FR-018 : résumé complet (fichiers, taille, dossier de destination, répertoire racine) présenté et confirmé avant toute écriture sur l'archive ; FR-012 : collision de nom traitée par suffixe ou confirmation, jamais écrasement silencieux. |38 | II. Confirmation explicite avant toute action à risque | PASS | FR-018 : résumé complet (fichiers, taille, dossier de destination, répertoire racine) présenté et confirmé avant toute écriture sur l'archive ; FR-012 : collision de nom traitée par suffixe ou confirmation, jamais écrasement silencieux. |
39 | III. Identité par contenu, jamais par nom de fichier seul | PASS | FR-004 : seuls les fichiers réellement nouveaux (par somme de contrôle) entrent dans l'analyse ; FR-015 : désambiguïsation de boîtiers déléguée à `specs/002`, elle-même fondée sur les métadonnées, jamais sur le seul nom de fichier. |39 | III. Identité par contenu, jamais par nom de fichier seul | PASS | FR-004 : seuls les fichiers réellement nouveaux (par somme de contrôle) entrent dans l'analyse ; FR-015 : désambiguïsation de boîtiers déléguée à `specs/002`, elle-même fondée sur les métadonnées, jamais sur le seul nom de fichier. |
40 | IV. Métadonnées ouvertes et embarquées | PASS | FR-017 : l'identifiant pérenne est écrit dans les métadonnées XMP embarquées du fichier (réutilisation du champ standard `xmpMM:DocumentID`, cf. research.md § 3), pas dans une base séparée. |40 | IV. Métadonnées ouvertes et embarquées | PASS | FR-017 : l'identifiant pérenne est écrit dans les métadonnées XMP embarquées du fichier (réutilisation du champ standard `xmpMM:DocumentID`, cf. research.md § 3), pas dans une base séparée. |
@@ -45,17 +45,13 @@ Pipeline complet depuis une carte mémoire jusqu'au premier archivage : copie v
45 | Formats ouverts et documentés | PASS | XMP pour l'identifiant pérenne ; aucune base propriétaire introduite. |45 | Formats ouverts et documentés | PASS | XMP pour l'identifiant pérenne ; aucune base propriétaire introduite. |
46 | Vérification d'intégrité systématique | PASS | FR-001 : vérification par somme de contrôle sur toute copie carte→local et sur le transfert local→archive (FR-019). |46 | Vérification d'intégrité systématique | PASS | FR-001 : vérification par somme de contrôle sur toute copie carte→local et sur le transfert local→archive (FR-019). |
47 47
48-**Violation à justifier** (Complexity Tracking) : le sous-scénario FR-009 (fusion vers dossier déjà archivé) ne peut pas être implémenté sans le mécanisme de checkout/réconciliation, absent du dépôt. Ce n'est pas une violation de principe mais une dépendance externe manquante — documentée ci-dessous plutôt que contournée par une implémentation partielle ou incorrecte.48+Aucune violation identifiée ; la section Complexity Tracking reste vide.
49 49
50 ## Complexity Tracking50 ## Complexity Tracking
51 51
52-> Rempli exceptionnellement pour documenter une dépendance bloquante, pas une violation de principe.52+*Aucune violation de gate à justifier — section laissée vide intentionnellement.*
53 53
54-| Élément | Pourquoi nécessaire | Alternative plus simple écartée |54+**Re-check post Phase 1** (après génération de `data-model.md`, `contracts/`, `quickstart.md`) : le modèle de données (Groupe d'import, Destination, Renommage) et les contrats (CLI `regine import`, API interne `regine_core.import_carte`) confirment chaque évaluation ci-dessus. Le sous-scénario FR-009 est désormais implémentable via `specs/005-checkout-reconciliation` (cf. `contracts/cli-import.md` et `quickstart.md`, mis à jour en conséquence) — plus aucune dépendance manquante.
55-|---|---|---|
56-| FR-009 (fusion vers dossier déjà archivé, nécessite un checkout) | Explicitement requis par `specs/001-import-photos` User Story 3 Acceptance Scenario 4 | Implémenter un checkout minimal ad hoc dans ce module plutôt que d'attendre une spec dédiée — écarté : dupliquerait par anticipation un mécanisme déjà nommé et cadré ailleurs (constitution § Workflow d'archivage, notes de conception section 8), avec un risque réel de divergence si le futur module `archive`/`dossier` retient une conception différente. Le sous-scénario reste documenté comme bloqué plutôt que mal implémenté. |
57-
58-**Re-check post Phase 1** (après génération de `data-model.md`, `contracts/`, `quickstart.md`) : le modèle de données (Groupe d'import, Destination, Renommage) et les contrats (CLI `regine import`, API interne `regine_core.import_carte`) ne modifient aucune évaluation ci-dessus ; le sous-scénario FR-009 reste explicitement marqué bloqué dans `contracts/cli-import.md` et `quickstart.md` plutôt que silencieusement omis.
59 55
60 ## Project Structure56 ## Project Structure
61 57
@@ -84,7 +80,7 @@ packages/regine-core/
84 │ ├── __init__.py80 │ ├── __init__.py
85 │ ├── copie.py # copie vérifiée carte→local, une seule lecture (FR-001), filtrage nouveaux fichiers (FR-004)81 │ ├── copie.py # copie vérifiée carte→local, une seule lecture (FR-001), filtrage nouveaux fichiers (FR-004)
86 │ ├── groupage.py # répartition jour par jour, détection dates aberrantes, découpage en groupes (FR-002/003/005/006)82 │ ├── groupage.py # répartition jour par jour, détection dates aberrantes, découpage en groupes (FR-002/003/005/006)
87-│ ├── destination.py # orchestration destination : simple/sous-dossier/fusion/parent + catégorie (FR-007/008/009), délègue à regine_core.dossier.root83+│ ├── destination.py # orchestration destination : simple/sous-dossier/fusion/parent + catégorie (FR-007/008/009), délègue à regine_core.dossier.root et, pour une fusion vers un dossier déjà archivé, à regine_core.archive.checkout (specs/005)
88 │ ├── nommage.py # construction titre/nom dossier, collision (FR-010/012), renommage synchronisé (FR-013/014)84 │ ├── nommage.py # construction titre/nom dossier, collision (FR-010/012), renommage synchronisé (FR-013/014)
89 │ ├── identifiant.py # génération + écriture identifiant pérenne (FR-017)85 │ ├── identifiant.py # génération + écriture identifiant pérenne (FR-017)
90 │ └── push.py # résumé + confirmation + transfert final (FR-018/019)86 │ └── push.py # résumé + confirmation + transfert final (FR-018/019)
@@ -98,12 +94,12 @@ packages/regine-core/
98 └── integration/94 └── integration/
99 ├── test_pipeline_import_simple.py # US1 : carte → dossier archivé, résumé confirmé95 ├── test_pipeline_import_simple.py # US1 : carte → dossier archivé, résumé confirmé
100 ├── test_pipeline_multi_jours.py # US2 : détachement, groupes multiples96 ├── test_pipeline_multi_jours.py # US2 : détachement, groupes multiples
101- └── test_pipeline_voyage.py # US3 : dossier parent + sous-dossier + fusion locale + désambiguïsation boîtiers (fusion vers archive : bloquée, cf. Complexity Tracking)97+ └── test_pipeline_voyage.py # US3 : dossier parent + sous-dossier + fusion locale ET fusion vers dossier archivé (via specs/005-checkout-reconciliation) + désambiguïsation boîtiers
102 98
103 packages/regine-cli/99 packages/regine-cli/
104 └── src/regine_cli/100 └── src/regine_cli/
105 └── import_cmd.py # NOUVEAU : commande `regine import <carte>`, premier point d'intégration réel (dossier.root, camera_profile, metadata.exif)101 └── import_cmd.py # NOUVEAU : commande `regine import <carte>`, premier point d'intégration réel (dossier.root, camera_profile, metadata.exif)
106 ```102 ```
107 103
108-**Structure Decision**: Extension du monorepo existant — nouveau module `regine_core/import_carte/` (aucun code existant avant ce plan pour ce module), et première commande CLI concrète du projet (`regine import`), qui devient le point d'intégration réel des API déjà contractées par `specs/002-profil-boitiers-optionnel` et `specs/004-categorisation-dossiers` (jusqu'ici documentées mais non consommées). `regine_core/metadata/exif.py` (créé par specs/002) est étendu plutôt que dupliqué.104+**Structure Decision**: Extension du monorepo existant — nouveau module `regine_core/import_carte/` (aucun code existant avant ce plan pour ce module), et première commande CLI concrète du projet (`regine import`), qui devient le point d'intégration réel des API déjà contractées par `specs/002-profil-boitiers-optionnel` et `specs/004-categorisation-dossiers` (jusqu'ici documentées mais non consommées). `regine_core/metadata/exif.py` (créé par specs/002) est étendu plutôt que dupliqué. **Mise à jour du 2026-09-19** : le sous-scénario FR-009 (fusion vers un dossier déjà archivé) consomme désormais `regine_core.archive.checkout` (`specs/005-checkout-reconciliation`, planifiée) au lieu d'être documenté comme bloqué.
109 105
modified specs/001-import-photos/quickstart.md +2 -2
@@ -37,8 +37,8 @@ regine import ./fixtures/carte_etape2 --destination sous-dossier:<id_parent> --t
3737 regine import ./fixtures/carte_secours_meme_etape --destination fusion:<id_kotor>
3838 ```
3939
40-**Résultat attendu** : si `<id_kotor>` n'existe qu'en local, la fusion aboutit avec désambiguïsation automatique des boîtiers par modèle EXIF (ou question d'étiquetage manuel si nécessaire, cf. `specs/002-profil-boitiers-optionnel`). **Si `<id_kotor>` n'existe que dans l'archive (pas en local), la commande DOIT échouer explicitement** (`ChecoutNonDisponibleError`, cf. `contracts/regine-core-api.md`) — comportement attendu tant que le mécanisme de checkout/réconciliation n'a pas sa propre spec.
40+**Résultat attendu** : que `<id_kotor>` existe en local ou seulement dans l'archive, la fusion aboutit avec désambiguïsation automatique des boîtiers par modèle EXIF (ou question d'étiquetage manuel si nécessaire, cf. `specs/002-profil-boitiers-optionnel`). Si `<id_kotor>` n'existe que dans l'archive, un checkout automatique (`specs/005-checkout-reconciliation`) précède l'intégration des nouveaux fichiers — visible sur stdout comme une étape supplémentaire, pas un échec. Si ce dossier est déjà verrouillé par un autre checkout en cours, la commande échoue explicitement.
4141
4242 ## Critères de sortie
4343
44-Les scénarios 1 et 2, et la première partie du scénario 3 (fusion locale), doivent passer intégralement. Le cas de fusion vers un dossier déjà archivé reste un échec **attendu et documenté**, pas un bug — à retester une fois le mécanisme de checkout/réconciliation spécifié.
44+Les 3 scénarios, y compris la fusion vers un dossier déjà archivé, doivent passer intégralement — plus aucun sous-scénario documenté comme bloqué.
@@ -37,8 +37,8 @@ regine import ./fixtures/carte_etape2 --destination sous-dossier:<id_parent> --t
37 regine import ./fixtures/carte_secours_meme_etape --destination fusion:<id_kotor>37 regine import ./fixtures/carte_secours_meme_etape --destination fusion:<id_kotor>
38 ```38 ```
39 39
40-**Résultat attendu** : si `<id_kotor>` n'existe qu'en local, la fusion aboutit avec désambiguïsation automatique des boîtiers par modèle EXIF (ou question d'étiquetage manuel si nécessaire, cf. `specs/002-profil-boitiers-optionnel`). **Si `<id_kotor>` n'existe que dans l'archive (pas en local), la commande DOIT échouer explicitement** (`ChecoutNonDisponibleError`, cf. `contracts/regine-core-api.md`) — comportement attendu tant que le mécanisme de checkout/réconciliation n'a pas sa propre spec.40+**Résultat attendu** : que `<id_kotor>` existe en local ou seulement dans l'archive, la fusion aboutit avec désambiguïsation automatique des boîtiers par modèle EXIF (ou question d'étiquetage manuel si nécessaire, cf. `specs/002-profil-boitiers-optionnel`). Si `<id_kotor>` n'existe que dans l'archive, un checkout automatique (`specs/005-checkout-reconciliation`) précède l'intégration des nouveaux fichiers — visible sur stdout comme une étape supplémentaire, pas un échec. Si ce dossier est déjà verrouillé par un autre checkout en cours, la commande échoue explicitement.
41 41
42 ## Critères de sortie42 ## Critères de sortie
43 43
44-Les scénarios 1 et 2, et la première partie du scénario 3 (fusion locale), doivent passer intégralement. Le cas de fusion vers un dossier déjà archivé reste un échec **attendu et documenté**, pas un bug — à retester une fois le mécanisme de checkout/réconciliation spécifié.44+Les 3 scénarios, y compris la fusion vers un dossier déjà archivé, doivent passer intégralement — plus aucun sous-scénario documenté comme bloqué.
modified specs/001-import-photos/research.md +4 -4
@@ -46,14 +46,14 @@
4646 **Alternatives considered**:
4747 - Bibliothèque de détection d'anomalies (ex. détection de pics par écart-type glissant plus sophistiquée) — rejeté : complexité non justifiée pour une simple suggestion que l'utilisateur peut ignorer.
4848
49-## 6. Dépendance non résolue : fusion vers un dossier déjà archivé (FR-009)
49+## 6. Fusion vers un dossier déjà archivé (FR-009)
5050
51-**Decision**: Ne pas implémenter ce sous-scénario dans ce plan. Documenté comme bloqué (cf. `plan.md` § Complexity Tracking) en attendant une spec/plan dédiés au mécanisme de checkout/réconciliation (constitution § Workflow d'archivage, notes de conception section 8).
51+**Decision**: Appeler `regine_core.archive.checkout.checkout(dossier_cible, dest_locale)` (`specs/005-checkout-reconciliation`) avant d'intégrer les nouveaux fichiers, puis suivre le cycle normal de réconciliation (`regine_core.archive.reconciliation`) pour réarchiver. **Mise à jour du 2026-09-19** : ce mécanisme a désormais sa propre spec et son propre plan (`specs/005-checkout-reconciliation`) — ce sous-scénario n'est plus bloqué.
5252
53-**Rationale**: Ce mécanisme est explicitement nommé et cadré ailleurs dans le projet (verrouillage de dossier, manifeste persistant, réconciliation par hash) sans qu'aucune spec ne l'ait encore formalisé. L'implémenter partiellement ici risquerait une divergence de conception avec sa future spec dédiée.
53+**Rationale**: Ce mécanisme est maintenant formalisé (manifeste persistant, verrouillage, réconciliation par hash) ; le réimplémenter ici dupliquerait une logique déjà spécifiée et planifiée ailleurs, contraire au Principe VI.
5454
5555 **Alternatives considered**:
56-- Implémenter un checkout minimal ad hoc — rejeté, cf. `plan.md` § Complexity Tracking.
56+- Implémenter un checkout minimal ad hoc dans ce module plutôt que de consommer `specs/005` — rejeté : dupliquerait un mécanisme déjà spécifié, avec un risque de divergence de conception.
5757
5858 ## Résumé
5959
@@ -46,14 +46,14 @@
46 **Alternatives considered**:46 **Alternatives considered**:
47 - Bibliothèque de détection d'anomalies (ex. détection de pics par écart-type glissant plus sophistiquée) — rejeté : complexité non justifiée pour une simple suggestion que l'utilisateur peut ignorer.47 - Bibliothèque de détection d'anomalies (ex. détection de pics par écart-type glissant plus sophistiquée) — rejeté : complexité non justifiée pour une simple suggestion que l'utilisateur peut ignorer.
48 48
49-## 6. Dépendance non résolue : fusion vers un dossier déjà archivé (FR-009)49+## 6. Fusion vers un dossier déjà archivé (FR-009)
50 50
51-**Decision**: Ne pas implémenter ce sous-scénario dans ce plan. Documenté comme bloqué (cf. `plan.md` § Complexity Tracking) en attendant une spec/plan dédiés au mécanisme de checkout/réconciliation (constitution § Workflow d'archivage, notes de conception section 8).51+**Decision**: Appeler `regine_core.archive.checkout.checkout(dossier_cible, dest_locale)` (`specs/005-checkout-reconciliation`) avant d'intégrer les nouveaux fichiers, puis suivre le cycle normal de réconciliation (`regine_core.archive.reconciliation`) pour réarchiver. **Mise à jour du 2026-09-19** : ce mécanisme a désormais sa propre spec et son propre plan (`specs/005-checkout-reconciliation`) — ce sous-scénario n'est plus bloqué.
52 52
53-**Rationale**: Ce mécanisme est explicitement nommé et cadré ailleurs dans le projet (verrouillage de dossier, manifeste persistant, réconciliation par hash) sans qu'aucune spec ne l'ait encore formalisé. L'implémenter partiellement ici risquerait une divergence de conception avec sa future spec dédiée.53+**Rationale**: Ce mécanisme est maintenant formalisé (manifeste persistant, verrouillage, réconciliation par hash) ; le réimplémenter ici dupliquerait une logique déjà spécifiée et planifiée ailleurs, contraire au Principe VI.
54 54
55 **Alternatives considered**:55 **Alternatives considered**:
56-- Implémenter un checkout minimal ad hoc — rejeté, cf. `plan.md` § Complexity Tracking.56+- Implémenter un checkout minimal ad hoc dans ce module plutôt que de consommer `specs/005` — rejeté : dupliquerait un mécanisme déjà spécifié, avec un risque de divergence de conception.
57 57
58 ## Résumé58 ## Résumé
59 59
modified specs/004-categorisation-dossiers/data-model.md +1 -1
@@ -44,7 +44,7 @@ Dossier ──1────1── RootLocation (année ou catégorie, fixé à
4444 RootLocation fixé (immuable en écriture directe)
4545 │
4646 │ déplacement détecté par somme de contrôle de contenu inchangé
47- │ (mécanisme existant, section 8 — pas une opération dédiée de ce module)
47+ │ (regine_core.archive.reconciliation.comparer, specs/005-checkout-reconciliation — pas une opération dédiée de ce module)
4848 ▼
4949 RootLocation mis à jour (reflète le nouveau chemin détecté)
5050 ```
@@ -44,7 +44,7 @@ Dossier ──1────1── RootLocation (année ou catégorie, fixé à
44 RootLocation fixé (immuable en écriture directe)44 RootLocation fixé (immuable en écriture directe)
45 │45 │
46 │ déplacement détecté par somme de contrôle de contenu inchangé46 │ déplacement détecté par somme de contrôle de contenu inchangé
47- │ (mécanisme existant, section 8 — pas une opération dédiée de ce module)47+ │ (regine_core.archive.reconciliation.comparer, specs/005-checkout-reconciliation — pas une opération dédiée de ce module)
48 ▼48 ▼
49 RootLocation mis à jour (reflète le nouveau chemin détecté)49 RootLocation mis à jour (reflète le nouveau chemin détecté)
50 ```50 ```
modified specs/004-categorisation-dossiers/quickstart.md +2 -2
@@ -56,8 +56,8 @@ assert sous_dossier_root.nom == "voyage"
5656
5757 ## Scénario 5 — Recatégorisation a posteriori (User Story 4, P4)
5858
59-Ce scénario dépend du mécanisme de réconciliation par hash (section 8, pas encore couvert par une spec dédiée) : à valider une fois ce mécanisme disponible, en déplaçant un dossier de `2026/` vers `mariage/` dans l'espace de travail local et en vérifiant que la réconciliation le détecte comme un déplacement de contenu inchangé plutôt qu'une anomalie.
59+Ce scénario dépend de l'implémentation de `regine_core.archive.reconciliation` (`specs/005-checkout-reconciliation`, désormais spécifiée et planifiée) : à valider une fois ce module implémenté, en déplaçant un dossier de `2026/` vers `mariage/` dans l'espace de travail local et en vérifiant que `comparer()` le classe `deplacement` plutôt qu'anomalie.
6060
6161 ## Critères de sortie
6262
63-Les scénarios 1 à 4 doivent passer sans accès réseau (fonctionnement local uniquement, cf. Performance Goals du plan). Le scénario 5 reste bloqué tant que le mécanisme de réconciliation par hash n'a pas sa propre implémentation — à retester à ce moment-là.
63+Les scénarios 1 à 4 doivent passer sans accès réseau (fonctionnement local uniquement, cf. Performance Goals du plan). Le scénario 5 dépend de l'implémentation de `specs/005-checkout-reconciliation` (spécifiée et planifiée, pas encore implémentée) — à exécuter une fois celle-ci disponible.
@@ -56,8 +56,8 @@ assert sous_dossier_root.nom == "voyage"
56 56
57 ## Scénario 5 — Recatégorisation a posteriori (User Story 4, P4)57 ## Scénario 5 — Recatégorisation a posteriori (User Story 4, P4)
58 58
59-Ce scénario dépend du mécanisme de réconciliation par hash (section 8, pas encore couvert par une spec dédiée) : à valider une fois ce mécanisme disponible, en déplaçant un dossier de `2026/` vers `mariage/` dans l'espace de travail local et en vérifiant que la réconciliation le détecte comme un déplacement de contenu inchangé plutôt qu'une anomalie.59+Ce scénario dépend de l'implémentation de `regine_core.archive.reconciliation` (`specs/005-checkout-reconciliation`, désormais spécifiée et planifiée) : à valider une fois ce module implémenté, en déplaçant un dossier de `2026/` vers `mariage/` dans l'espace de travail local et en vérifiant que `comparer()` le classe `deplacement` plutôt qu'anomalie.
60 60
61 ## Critères de sortie61 ## Critères de sortie
62 62
63-Les scénarios 1 à 4 doivent passer sans accès réseau (fonctionnement local uniquement, cf. Performance Goals du plan). Le scénario 5 reste bloqué tant que le mécanisme de réconciliation par hash n'a pas sa propre implémentation — à retester à ce moment-là.63+Les scénarios 1 à 4 doivent passer sans accès réseau (fonctionnement local uniquement, cf. Performance Goals du plan). Le scénario 5 dépend de l'implémentation de `specs/005-checkout-reconciliation` (spécifiée et planifiée, pas encore implémentée) — à exécuter une fois celle-ci disponible.
modified specs/004-categorisation-dossiers/spec.md +1 -1
@@ -113,6 +113,6 @@ Un photographe se rend compte, après coup, qu'un dossier rangé par erreur sous
113113 ## Assumptions
114114
115115 - Cette spécification étend `specs/001-import-photos` (étape "destination de chaque groupe" et nommage/archivage du dossier) : une fois validée, `specs/001-import-photos` devra être alignée pour inclure cette question de placement racine dans son propre flux — mise à jour à faire séparément, pas incluse dans cette spécification.
116-- Le mécanisme de détection de déplacement par somme de contrôle de contenu et de réconciliation lors d'un checkout (notes de conception, section 8) est supposé disponible indépendamment de cette spécification ; FR-008 s'appuie dessus sans le redéfinir.
116+- Le mécanisme de détection de déplacement par somme de contrôle de contenu et de réconciliation lors d'un checkout est désormais spécifié et planifié séparément (`specs/005-checkout-reconciliation`, cf. son FR-010) ; FR-008 s'appuie dessus sans le redéfinir. Sa validation de bout en bout dépend de l'implémentation de `specs/005`.
117117 - Les conventions déjà spécifiées pour le nom du dossier lui-même (`AAAA-MM-JJ_Titre` etc.), sa structure interne par format et sa racine de sélection (`docs/archivage-photo-elements-cles.md` sections 9-10, `specs/001-import-photos`) restent inchangées ; cette spécification ajoute uniquement le niveau racine (année ou catégorie) au-dessus de ces conventions.
118118 - Une catégorie est un simple nom de répertoire choisi par l'utilisateur ; Régine n'effectue aucune validation métier de ce qui "compte" comme un mariage ou un voyage, au-delà du nettoyage de nom déjà prévu pour tout nom de dossier (espaces, caractères interdits).
@@ -113,6 +113,6 @@ Un photographe se rend compte, après coup, qu'un dossier rangé par erreur sous
113 ## Assumptions113 ## Assumptions
114 114
115 - Cette spécification étend `specs/001-import-photos` (étape "destination de chaque groupe" et nommage/archivage du dossier) : une fois validée, `specs/001-import-photos` devra être alignée pour inclure cette question de placement racine dans son propre flux — mise à jour à faire séparément, pas incluse dans cette spécification.115 - Cette spécification étend `specs/001-import-photos` (étape "destination de chaque groupe" et nommage/archivage du dossier) : une fois validée, `specs/001-import-photos` devra être alignée pour inclure cette question de placement racine dans son propre flux — mise à jour à faire séparément, pas incluse dans cette spécification.
116-- Le mécanisme de détection de déplacement par somme de contrôle de contenu et de réconciliation lors d'un checkout (notes de conception, section 8) est supposé disponible indépendamment de cette spécification ; FR-008 s'appuie dessus sans le redéfinir.116+- Le mécanisme de détection de déplacement par somme de contrôle de contenu et de réconciliation lors d'un checkout est désormais spécifié et planifié séparément (`specs/005-checkout-reconciliation`, cf. son FR-010) ; FR-008 s'appuie dessus sans le redéfinir. Sa validation de bout en bout dépend de l'implémentation de `specs/005`.
117 - Les conventions déjà spécifiées pour le nom du dossier lui-même (`AAAA-MM-JJ_Titre` etc.), sa structure interne par format et sa racine de sélection (`docs/archivage-photo-elements-cles.md` sections 9-10, `specs/001-import-photos`) restent inchangées ; cette spécification ajoute uniquement le niveau racine (année ou catégorie) au-dessus de ces conventions.117 - Les conventions déjà spécifiées pour le nom du dossier lui-même (`AAAA-MM-JJ_Titre` etc.), sa structure interne par format et sa racine de sélection (`docs/archivage-photo-elements-cles.md` sections 9-10, `specs/001-import-photos`) restent inchangées ; cette spécification ajoute uniquement le niveau racine (année ou catégorie) au-dessus de ces conventions.
118 - Une catégorie est un simple nom de répertoire choisi par l'utilisateur ; Régine n'effectue aucune validation métier de ce qui "compte" comme un mariage ou un voyage, au-delà du nettoyage de nom déjà prévu pour tout nom de dossier (espaces, caractères interdits).118 - Une catégorie est un simple nom de répertoire choisi par l'utilisateur ; Régine n'effectue aucune validation métier de ce qui "compte" comme un mariage ou un voyage, au-delà du nettoyage de nom déjà prévu pour tout nom de dossier (espaces, caractères interdits).
modified specs/004-categorisation-dossiers/tasks.md +7 -6
@@ -111,11 +111,12 @@ Aucun code n'existe encore dans ce dépôt (seuls `docs/`, `specs/`, `.specify/`
111111
112112 **Goal**: Un déplacement d'un dossier entre répertoires racines est reconnu comme tel par le mécanisme de réconciliation par hash (FR-008) — pas de mécanisme dédié à construire ici.
113113
114-**Statut** : **Bloqué**. Le mécanisme de détection de déplacement par somme de contrôle (notes de conception, section 8) n'a pas encore de spécification ni d'implémentation dans ce dépôt. Aucune tâche de code n'est exécutable pour cette user story à ce stade.
114+**Statut** : **Mise à jour du 2026-09-19** — le mécanisme de détection de déplacement par somme de contrôle a désormais sa propre spec et son propre plan (`specs/005-checkout-reconciliation`, dont FR-010 couvre exactement ce cas). Ce n'est plus une dépendance bloquante au niveau spécification : seulement une dépendance d'ordonnancement (l'implémentation de `specs/005` doit précéder la validation de bout en bout de cette user story).
115115
116-- [ ] T024 [US4] Documenter (docstring) dans `packages/regine-core/src/regine_core/dossier/root.py` qu'il n'existe aucune opération d'écriture directe sur `RootLocation` : tout changement de répertoire racine passe exclusivement par un déplacement physique détecté à la réconciliation (mécanisme externe, hors périmètre de ce module)
116+- [ ] T024 [US4] Documenter (docstring) dans `packages/regine-core/src/regine_core/dossier/root.py` qu'il n'existe aucune opération d'écriture directe sur `RootLocation` : tout changement de répertoire racine passe exclusivement par `regine_core.archive.reconciliation.comparer` (`specs/005-checkout-reconciliation`), qui classe un déplacement de dossier entier comme `deplacement` sans code supplémentaire ici
117+- [ ] T024b [US4] Test d'intégration (à exécuter une fois `specs/005-checkout-reconciliation` implémentée) : déplacer un dossier de travail entre deux répertoires racine (année → catégorie) et vérifier que `regine_core.archive.reconciliation.comparer` le classe `deplacement`, dans `packages/regine-core/tests/integration/test_root_recategorisation.py`
117118
118-**Checkpoint**: US4 documentée comme dépendance externe ; à reprendre quand le mécanisme de réconciliation aura sa propre spec/plan/tasks.
119+**Checkpoint**: US4 documentée et prête à être validée dès que `specs/005-checkout-reconciliation` est implémentée — plus aucune spécification manquante.
119120
120121 ---
121122
@@ -137,7 +138,7 @@ Aucun code n'existe encore dans ce dépôt (seuls `docs/`, `specs/`, `.specify/`
137138 - US1 (P1) : aucune dépendance sur une autre user story.
138139 - US2 (P2) : dépend techniquement de T012 (branche année déjà en place dans `determine_root`) mais reste testable et livrable indépendamment de US1 côté utilisateur.
139140 - US3 (P3) : dépend de US1 et US2 étant données (réutilise leurs `RootLocation`), mais n'ajoute aucune nouvelle logique de calcul.
140- - US4 (P4) : bloquée par une dépendance externe (mécanisme de réconciliation non spécifié) — aucune tâche de code à ce stade.
141+ - US4 (P4) : dépend de l'implémentation de `specs/005-checkout-reconciliation` (désormais spécifiée et planifiée) pour sa validation de bout en bout ; la tâche de documentation T024 reste exécutable dès maintenant.
141142 - **Polish (Phase 7)** : dépend des user stories livrées (au minimum US1+US2).
142143
143144 ### Parallel Opportunities
@@ -178,11 +179,11 @@ Task: "Implémenter suggest_categories(saisie) dans packages/regine-core/src/reg
178179 2. US1 → placement par année (MVP) → valider Scénario 1.
179180 3. US2 → catégories thématiques → valider Scénario 2 et 3.
180181 4. US3 → héritage voyage → valider Scénario 4.
181-5. US4 → reste bloquée en documentation jusqu'à ce que le mécanisme de réconciliation par hash ait sa propre spec.
182+5. US4 → documentation exécutable dès maintenant (T024) ; validation de bout en bout (T024b) une fois `specs/005-checkout-reconciliation` implémentée.
182183
183184 ## Notes
184185
185186 - [P] = fichiers différents, sans dépendance non résolue.
186-- Chaque user story est livrable et testable indépendamment, à l'exception de US4 (dépendance externe non encore spécifiée).
187+- Chaque user story est livrable et testable indépendamment ; US4 dépend de l'implémentation de `specs/005-checkout-reconciliation` (désormais spécifiée et planifiée) pour sa validation de bout en bout, mais sa tâche de documentation (T024) ne l'est pas.
187188 - Committer après chaque tâche ou groupe logique de tâches.
188189 - Le squelette monorepo (Phase 1) et la base de contexte (T009) sont des fondations partagées : ne pas les recréer lors des futures exécutions de `/speckit-tasks` sur `specs/001`, `002` ou `003` — les étendre.
@@ -111,11 +111,12 @@ Aucun code n'existe encore dans ce dépôt (seuls `docs/`, `specs/`, `.specify/`
111 111
112 **Goal**: Un déplacement d'un dossier entre répertoires racines est reconnu comme tel par le mécanisme de réconciliation par hash (FR-008) — pas de mécanisme dédié à construire ici.112 **Goal**: Un déplacement d'un dossier entre répertoires racines est reconnu comme tel par le mécanisme de réconciliation par hash (FR-008) — pas de mécanisme dédié à construire ici.
113 113
114-**Statut** : **Bloqué**. Le mécanisme de détection de déplacement par somme de contrôle (notes de conception, section 8) n'a pas encore de spécification ni d'implémentation dans ce dépôt. Aucune tâche de code n'est exécutable pour cette user story à ce stade.114+**Statut** : **Mise à jour du 2026-09-19** — le mécanisme de détection de déplacement par somme de contrôle a désormais sa propre spec et son propre plan (`specs/005-checkout-reconciliation`, dont FR-010 couvre exactement ce cas). Ce n'est plus une dépendance bloquante au niveau spécification : seulement une dépendance d'ordonnancement (l'implémentation de `specs/005` doit précéder la validation de bout en bout de cette user story).
115 115
116-- [ ] T024 [US4] Documenter (docstring) dans `packages/regine-core/src/regine_core/dossier/root.py` qu'il n'existe aucune opération d'écriture directe sur `RootLocation` : tout changement de répertoire racine passe exclusivement par un déplacement physique détecté à la réconciliation (mécanisme externe, hors périmètre de ce module)116+- [ ] T024 [US4] Documenter (docstring) dans `packages/regine-core/src/regine_core/dossier/root.py` qu'il n'existe aucune opération d'écriture directe sur `RootLocation` : tout changement de répertoire racine passe exclusivement par `regine_core.archive.reconciliation.comparer` (`specs/005-checkout-reconciliation`), qui classe un déplacement de dossier entier comme `deplacement` sans code supplémentaire ici
117+- [ ] T024b [US4] Test d'intégration (à exécuter une fois `specs/005-checkout-reconciliation` implémentée) : déplacer un dossier de travail entre deux répertoires racine (année → catégorie) et vérifier que `regine_core.archive.reconciliation.comparer` le classe `deplacement`, dans `packages/regine-core/tests/integration/test_root_recategorisation.py`
117 118
118-**Checkpoint**: US4 documentée comme dépendance externe ; à reprendre quand le mécanisme de réconciliation aura sa propre spec/plan/tasks.119+**Checkpoint**: US4 documentée et prête à être validée dès que `specs/005-checkout-reconciliation` est implémentée — plus aucune spécification manquante.
119 120
120 ---121 ---
121 122
@@ -137,7 +138,7 @@ Aucun code n'existe encore dans ce dépôt (seuls `docs/`, `specs/`, `.specify/`
137 - US1 (P1) : aucune dépendance sur une autre user story.138 - US1 (P1) : aucune dépendance sur une autre user story.
138 - US2 (P2) : dépend techniquement de T012 (branche année déjà en place dans `determine_root`) mais reste testable et livrable indépendamment de US1 côté utilisateur.139 - US2 (P2) : dépend techniquement de T012 (branche année déjà en place dans `determine_root`) mais reste testable et livrable indépendamment de US1 côté utilisateur.
139 - US3 (P3) : dépend de US1 et US2 étant données (réutilise leurs `RootLocation`), mais n'ajoute aucune nouvelle logique de calcul.140 - US3 (P3) : dépend de US1 et US2 étant données (réutilise leurs `RootLocation`), mais n'ajoute aucune nouvelle logique de calcul.
140- - US4 (P4) : bloquée par une dépendance externe (mécanisme de réconciliation non spécifié) — aucune tâche de code à ce stade.141+ - US4 (P4) : dépend de l'implémentation de `specs/005-checkout-reconciliation` (désormais spécifiée et planifiée) pour sa validation de bout en bout ; la tâche de documentation T024 reste exécutable dès maintenant.
141 - **Polish (Phase 7)** : dépend des user stories livrées (au minimum US1+US2).142 - **Polish (Phase 7)** : dépend des user stories livrées (au minimum US1+US2).
142 143
143 ### Parallel Opportunities144 ### Parallel Opportunities
@@ -178,11 +179,11 @@ Task: "Implémenter suggest_categories(saisie) dans packages/regine-core/src/reg
178 2. US1 → placement par année (MVP) → valider Scénario 1.179 2. US1 → placement par année (MVP) → valider Scénario 1.
179 3. US2 → catégories thématiques → valider Scénario 2 et 3.180 3. US2 → catégories thématiques → valider Scénario 2 et 3.
180 4. US3 → héritage voyage → valider Scénario 4.181 4. US3 → héritage voyage → valider Scénario 4.
181-5. US4 → reste bloquée en documentation jusqu'à ce que le mécanisme de réconciliation par hash ait sa propre spec.182+5. US4 → documentation exécutable dès maintenant (T024) ; validation de bout en bout (T024b) une fois `specs/005-checkout-reconciliation` implémentée.
182 183
183 ## Notes184 ## Notes
184 185
185 - [P] = fichiers différents, sans dépendance non résolue.186 - [P] = fichiers différents, sans dépendance non résolue.
186-- Chaque user story est livrable et testable indépendamment, à l'exception de US4 (dépendance externe non encore spécifiée).187+- Chaque user story est livrable et testable indépendamment ; US4 dépend de l'implémentation de `specs/005-checkout-reconciliation` (désormais spécifiée et planifiée) pour sa validation de bout en bout, mais sa tâche de documentation (T024) ne l'est pas.
187 - Committer après chaque tâche ou groupe logique de tâches.188 - Committer après chaque tâche ou groupe logique de tâches.
188 - Le squelette monorepo (Phase 1) et la base de contexte (T009) sont des fondations partagées : ne pas les recréer lors des futures exécutions de `/speckit-tasks` sur `specs/001`, `002` ou `003` — les étendre.189 - Le squelette monorepo (Phase 1) et la base de contexte (T009) sont des fondations partagées : ne pas les recréer lors des futures exécutions de `/speckit-tasks` sur `specs/001`, `002` ou `003` — les étendre.
added specs/005-checkout-reconciliation/contracts/cli-checkout-reconcile.md +35 -0
new file mode 100644
@@ -0,0 +1,35 @@
1+# Contrat CLI : `regine checkout` / `regine reconcile`
2+
3+Protocole texte in/out (Principe CLI-first) : arguments en entrée, résultat sur stdout, erreurs sur stderr, code de sortie non-zéro en cas d'échec.
4+
5+## `regine checkout <dossier> [--formats jpeg,raw] [--local-dest CHEMIN]`
6+
7+**Comportement** :
8+- Sans `--formats` : checkout complet (FR-001).
9+- Avec `--formats` : checkout partiel (FR-019), limité aux sous-répertoires de format indiqués plus la racine de sélection.
10+
11+**Sorties** :
12+- Dossier déjà verrouillé (FR-004) : message explicite (« dossier déjà en cours d'édition ») sur stderr, code non-zéro, rien n'est copié.
13+- Succès : chemin de la copie de travail locale sur stdout, code `0`, manifeste snapshot enregistré (FR-002), verrou posé (FR-003).
14+- Échec de vérification d'un fichier pendant la copie : signalé sur stderr, checkout interrompu pour ce dossier, aucun verrou posé si la copie n'a pas abouti intégralement.
15+
16+## `regine reconcile <dossier>`
17+
18+**Comportement** :
19+1. Recalcule les empreintes de la copie de travail et compare au manifeste (FR-006).
20+2. Affiche le « point avant archive » : liste des changements par catégorie (normal, anomalie, déplacement, suppression, nouveau fichier).
21+3. Pour chaque anomalie, demande une décision explicite (confirmer / restaurer) avant de la considérer traitée.
22+4. Pour chaque nouveau fichier sans correspondance, demande explicitement archiver / laisser local (FR-012).
23+5. Demande une confirmation globale avant toute écriture (FR-013).
24+6. Écrit sur l'archive, vérifie chaque transfert (FR-015), met à jour le manifeste, lève le verrou (FR-016).
25+
26+**Sorties** :
27+- Dossier jamais checkouté (pas de manifeste de référence, Edge Case) : erreur explicite sur stderr, code non-zéro.
28+- Résumé non confirmé : aucune écriture, code `0` (sortie normale, pas une erreur — l'utilisateur peut reprendre plus tard), copie de travail conservée.
29+- Anomalie non résolue à la sortie de la session : reste signalée, n'empêche pas l'archivage des autres changements déjà confirmés (FR-014), mais le verrou n'est levé qu'une fois **toutes** les écritures de la session terminées.
30+- Interruption réseau pendant l'écriture (Edge Case) : aucune écriture partielle visible comme définitive ; message d'échec clair, code non-zéro, verrou conservé pour permettre une reprise.
31+
32+## Notes de scriptabilité
33+
34+- `--formats` accepte une liste séparée par virgules (ex. `jpeg,raw`) ; en son absence, checkout complet.
35+- Le mode interactif (anomalies, nouveaux fichiers, confirmation finale) n'a pas de mode `--yes` global dans ce contrat : une anomalie ou un nouveau fichier sans correspondance doit toujours faire l'objet d'une décision tracée, jamais d'un défaut automatique (FR-012, Principe V) — un mode scripté pourra être ajouté ultérieurement avec des flags explicites par décision, hors périmètre de ce plan.
new file mode 100644
@@ -0,0 +1,35 @@
1+# Contrat CLI : `regine checkout` / `regine reconcile`
2+
3+Protocole texte in/out (Principe CLI-first) : arguments en entrée, résultat sur stdout, erreurs sur stderr, code de sortie non-zéro en cas d'échec.
4+
5+## `regine checkout <dossier> [--formats jpeg,raw] [--local-dest CHEMIN]`
6+
7+**Comportement** :
8+- Sans `--formats` : checkout complet (FR-001).
9+- Avec `--formats` : checkout partiel (FR-019), limité aux sous-répertoires de format indiqués plus la racine de sélection.
10+
11+**Sorties** :
12+- Dossier déjà verrouillé (FR-004) : message explicite (« dossier déjà en cours d'édition ») sur stderr, code non-zéro, rien n'est copié.
13+- Succès : chemin de la copie de travail locale sur stdout, code `0`, manifeste snapshot enregistré (FR-002), verrou posé (FR-003).
14+- Échec de vérification d'un fichier pendant la copie : signalé sur stderr, checkout interrompu pour ce dossier, aucun verrou posé si la copie n'a pas abouti intégralement.
15+
16+## `regine reconcile <dossier>`
17+
18+**Comportement** :
19+1. Recalcule les empreintes de la copie de travail et compare au manifeste (FR-006).
20+2. Affiche le « point avant archive » : liste des changements par catégorie (normal, anomalie, déplacement, suppression, nouveau fichier).
21+3. Pour chaque anomalie, demande une décision explicite (confirmer / restaurer) avant de la considérer traitée.
22+4. Pour chaque nouveau fichier sans correspondance, demande explicitement archiver / laisser local (FR-012).
23+5. Demande une confirmation globale avant toute écriture (FR-013).
24+6. Écrit sur l'archive, vérifie chaque transfert (FR-015), met à jour le manifeste, lève le verrou (FR-016).
25+
26+**Sorties** :
27+- Dossier jamais checkouté (pas de manifeste de référence, Edge Case) : erreur explicite sur stderr, code non-zéro.
28+- Résumé non confirmé : aucune écriture, code `0` (sortie normale, pas une erreur — l'utilisateur peut reprendre plus tard), copie de travail conservée.
29+- Anomalie non résolue à la sortie de la session : reste signalée, n'empêche pas l'archivage des autres changements déjà confirmés (FR-014), mais le verrou n'est levé qu'une fois **toutes** les écritures de la session terminées.
30+- Interruption réseau pendant l'écriture (Edge Case) : aucune écriture partielle visible comme définitive ; message d'échec clair, code non-zéro, verrou conservé pour permettre une reprise.
31+
32+## Notes de scriptabilité
33+
34+- `--formats` accepte une liste séparée par virgules (ex. `jpeg,raw`) ; en son absence, checkout complet.
35+- Le mode interactif (anomalies, nouveaux fichiers, confirmation finale) n'a pas de mode `--yes` global dans ce contrat : une anomalie ou un nouveau fichier sans correspondance doit toujours faire l'objet d'une décision tracée, jamais d'un défaut automatique (FR-012, Principe V) — un mode scripté pourra être ajouté ultérieurement avec des flags explicites par décision, hors périmètre de ce plan.
added specs/005-checkout-reconciliation/contracts/regine-core-api.md +42 -0
new file mode 100644
@@ -0,0 +1,42 @@
1+# Contrat d'API interne : `regine_core.integrity` / `regine_core.archive`
2+
3+Objets structurés, jamais de texte à parser (Principe VI). Consommée par `regine-cli` (`contracts/cli-checkout-reconcile.md`) et, plus tard, par le point d'intégration de `specs/001-import-photos` (FR-009) et `specs/004-categorisation-dossiers` (User Story 4).
4+
5+## `regine_core.integrity.hash.hash_fichier_entier(chemin: Path) -> str`
6+
7+SHA-256 du fichier entier (stdlib `hashlib`).
8+
9+## `regine_core.integrity.hash.hash_image_only(chemin: Path) -> str | None`
10+
11+`None` si le format n'est pas DNG/TIFF/JPEG. Sinon, délègue à `regine_core.metadata.exif.read_image_data_hash` (cf. research.md § 5).
12+
13+## `regine_core.integrity.anomalie.est_maitre_modifie(format: str, ref: FichierManifeste, actuel: FichierManifeste) -> bool`
14+
15+Implémente le Principe I : pour un RAW propriétaire, compare `hash_fichier_entier` seul. Pour DNG/TIFF/JPEG, compare `hash_image_only` (si `None` des deux côtés, replie sur `hash_fichier_entier`). Retourne `True` uniquement si le contenu pertinent a changé.
16+
17+## `regine_core.archive.manifest.ouvrir_ou_creer(dossier: Path) -> ManifestHandle`
18+
19+Ouvre le manifeste SQLite à la racine de `dossier` (le crée si absent — premier checkout). Vérifie `PRAGMA user_version` (cf. research.md § 2) ; lève une erreur explicite si la version structurelle du manifeste est plus récente que ce que le code sait lire.
20+
21+## `regine_core.archive.verrou.poser(manifest: ManifestHandle) -> None` / `verifier(manifest) -> bool` / `lever(manifest) -> None`
22+
23+Gèrent la table `verrou` (FR-003/004/016). `poser` échoue (exception dédiée) si déjà verrouillé.
24+
25+## `regine_core.archive.checkout.checkout(dossier_archive: Path, dest_locale: Path, formats: list[str] | None = None) -> ManifestSnapshot`
26+
27+Copie vérifiée (FR-001/015/019/020), pose le verrou, retourne le snapshot du manifeste au moment du checkout. Lève une exception dédiée si le dossier est déjà verrouillé (FR-004).
28+
29+## `regine_core.archive.reconciliation.comparer(manifest: ManifestSnapshot, copie_locale: Path) -> RapportReconciliation`
30+
31+Implémente FR-006/007/008/009/010/011/012 : construit les index hash→chemin (manifeste et copie locale, cf. research.md § 4), classe chaque fichier en `normal`/`anomalie`/`deplacement`/`suppression`/`nouveau`.
32+
33+## `regine_core.archive.reconciliation.archiver(dossier_archive: Path, rapport: RapportReconciliation, decisions: DecisionsUtilisateur) -> None`
34+
35+Écrit sur l'archive uniquement les changements couverts par `decisions` (FR-013/014), vérifie chaque transfert (FR-015), met à jour le manifeste et lève le verrou une fois toutes les écritures de la session terminées (FR-016). Ne DOIT jamais être appelée sans qu'un « point avant archive » ait été présenté et confirmé côté appelant.
36+
37+## Point d'intégration côté façades (futur, hors périmètre de ce plan)
38+
39+- `specs/001-import-photos` FR-009 (fusion vers un dossier déjà archivé) DEVRA appeler `checkout` sur le dossier ciblé avant d'y intégrer les nouveaux fichiers, plutôt que de lever `ChecoutNonDisponibleError` comme actuellement documenté dans son plan.
40+- `specs/004-categorisation-dossiers` User Story 4 (recatégorisation a posteriori) DEVRA s'appuyer sur `comparer` : un dossier déplacé entre répertoires racine y est déjà classé `deplacement` (FR-010 de cette spec), sans code supplémentaire à écrire côté `specs/004`.
41+
42+Ces deux points d'intégration ne sont pas implémentés par ce plan ; ils sont documentés ici pour que leur future implémentation consomme cette API sans la redéfinir.
new file mode 100644
@@ -0,0 +1,42 @@
1+# Contrat d'API interne : `regine_core.integrity` / `regine_core.archive`
2+
3+Objets structurés, jamais de texte à parser (Principe VI). Consommée par `regine-cli` (`contracts/cli-checkout-reconcile.md`) et, plus tard, par le point d'intégration de `specs/001-import-photos` (FR-009) et `specs/004-categorisation-dossiers` (User Story 4).
4+
5+## `regine_core.integrity.hash.hash_fichier_entier(chemin: Path) -> str`
6+
7+SHA-256 du fichier entier (stdlib `hashlib`).
8+
9+## `regine_core.integrity.hash.hash_image_only(chemin: Path) -> str | None`
10+
11+`None` si le format n'est pas DNG/TIFF/JPEG. Sinon, délègue à `regine_core.metadata.exif.read_image_data_hash` (cf. research.md § 5).
12+
13+## `regine_core.integrity.anomalie.est_maitre_modifie(format: str, ref: FichierManifeste, actuel: FichierManifeste) -> bool`
14+
15+Implémente le Principe I : pour un RAW propriétaire, compare `hash_fichier_entier` seul. Pour DNG/TIFF/JPEG, compare `hash_image_only` (si `None` des deux côtés, replie sur `hash_fichier_entier`). Retourne `True` uniquement si le contenu pertinent a changé.
16+
17+## `regine_core.archive.manifest.ouvrir_ou_creer(dossier: Path) -> ManifestHandle`
18+
19+Ouvre le manifeste SQLite à la racine de `dossier` (le crée si absent — premier checkout). Vérifie `PRAGMA user_version` (cf. research.md § 2) ; lève une erreur explicite si la version structurelle du manifeste est plus récente que ce que le code sait lire.
20+
21+## `regine_core.archive.verrou.poser(manifest: ManifestHandle) -> None` / `verifier(manifest) -> bool` / `lever(manifest) -> None`
22+
23+Gèrent la table `verrou` (FR-003/004/016). `poser` échoue (exception dédiée) si déjà verrouillé.
24+
25+## `regine_core.archive.checkout.checkout(dossier_archive: Path, dest_locale: Path, formats: list[str] | None = None) -> ManifestSnapshot`
26+
27+Copie vérifiée (FR-001/015/019/020), pose le verrou, retourne le snapshot du manifeste au moment du checkout. Lève une exception dédiée si le dossier est déjà verrouillé (FR-004).
28+
29+## `regine_core.archive.reconciliation.comparer(manifest: ManifestSnapshot, copie_locale: Path) -> RapportReconciliation`
30+
31+Implémente FR-006/007/008/009/010/011/012 : construit les index hash→chemin (manifeste et copie locale, cf. research.md § 4), classe chaque fichier en `normal`/`anomalie`/`deplacement`/`suppression`/`nouveau`.
32+
33+## `regine_core.archive.reconciliation.archiver(dossier_archive: Path, rapport: RapportReconciliation, decisions: DecisionsUtilisateur) -> None`
34+
35+Écrit sur l'archive uniquement les changements couverts par `decisions` (FR-013/014), vérifie chaque transfert (FR-015), met à jour le manifeste et lève le verrou une fois toutes les écritures de la session terminées (FR-016). Ne DOIT jamais être appelée sans qu'un « point avant archive » ait été présenté et confirmé côté appelant.
36+
37+## Point d'intégration côté façades (futur, hors périmètre de ce plan)
38+
39+- `specs/001-import-photos` FR-009 (fusion vers un dossier déjà archivé) DEVRA appeler `checkout` sur le dossier ciblé avant d'y intégrer les nouveaux fichiers, plutôt que de lever `ChecoutNonDisponibleError` comme actuellement documenté dans son plan.
40+- `specs/004-categorisation-dossiers` User Story 4 (recatégorisation a posteriori) DEVRA s'appuyer sur `comparer` : un dossier déplacé entre répertoires racine y est déjà classé `deplacement` (FR-010 de cette spec), sans code supplémentaire à écrire côté `specs/004`.
41+
42+Ces deux points d'intégration ne sont pas implémentés par ce plan ; ils sont documentés ici pour que leur future implémentation consomme cette API sans la redéfinir.
added specs/005-checkout-reconciliation/data-model.md +81 -0
new file mode 100644
@@ -0,0 +1,81 @@
1+# Data Model: Checkout et réconciliation d'un dossier de l'archive
2+
3+Entités dérivées de `spec.md` § Key Entities et Functional Requirements.
4+
5+## Manifeste (fichier SQLite, un par dossier principal — cf. research.md § 1)
6+
7+**Table `fichiers`**
8+
9+| Champ | Type | Règles |
10+|---|---|---|
11+| `chemin_relatif` | texte | Relatif à la racine du dossier principal (traverse sous-dossiers de format et sous-dossiers d'étape) |
12+| `taille` | entier | Octets |
13+| `hash_fichier_entier` | texte (SHA-256) | Toujours calculé |
14+| `hash_image_only` | texte, nullable | Calculé uniquement pour DNG/TIFF/JPEG maîtres (cf. Principe I de la constitution) |
15+| `identifiant_perenne` | texte | Repris du champ XMP `xmpMM:DocumentID` (specs/001-import-photos) |
16+
17+**Table `verrou`**
18+
19+| Champ | Type | Règles |
20+|---|---|---|
21+| `verrouille` | bool | `True` entre un checkout et sa réconciliation réussie |
22+| `identifiant_session` | texte, nullable | Identifie l'instance ayant posé le verrou (pour message d'erreur explicite, pas pour arbitrage distribué) |
23+| `horodatage` | horodatage, nullable | Date de pose du verrou |
24+
25+**Métadonnées du fichier SQLite** : `PRAGMA user_version` (schéma `structurel*1000 + additif`, cf. research.md § 2), `PRAGMA application_id` (identifiant Régine).
26+
27+## Objets d'échange (non persistés)
28+
29+**`ManifestSnapshot`** (retour de `checkout`) : copie en mémoire du contenu du manifeste au moment du checkout, plus le chemin de la copie de travail locale.
30+
31+**`ChangementClasse`** :
32+
33+| Champ | Type |
34+|---|---|
35+| `chemin` | Path |
36+| `categorie` | énumération : `normal` \| `anomalie` \| `deplacement` \| `suppression` \| `nouveau` |
37+| `chemin_ancien` | `Path \| None` (renseigné si `categorie == deplacement`) |
38+
39+**`RapportReconciliation`** (retour de `comparer`) :
40+
41+| Champ | Type |
42+|---|---|
43+| `changements` | `list[ChangementClasse]` |
44+| `anomalies` | `list[ChangementClasse]` (sous-ensemble filtré, pour affichage séparé — FR-014) |
45+
46+## Relations
47+
48+```text
49+Dossier principal (archive) ──1────1── Manifeste (fichiers + verrou)
50+ │ checkout (FR-001)
51+ ▼
52+Copie de travail locale ──(édition libre, aucune contrainte)──▶ Copie de travail modifiée
53+ │ réconciliation : comparer(Manifeste, Copie de travail modifiée)
54+ ▼
55+RapportReconciliation (5 catégories) ──▶ Point avant archive (FR-013) ──▶ confirmation ──▶ archiver()
56+ │
57+ ▼
58+Manifeste mis à jour + verrou levé (FR-016)
59+```
60+
61+## État / transitions
62+
63+```text
64+[Dossier de l'archive]
65+ Disponible (verrou=False)
66+ │ checkout (FR-001/003)
67+ ▼
68+ Verrouillé (verrou=True, ManifestSnapshot créé)
69+ │ édition locale libre (FR-005, archive inchangée)
70+ │
71+ │ réconciliation → RapportReconciliation
72+ │
73+ ├── changements "normal"/"deplacement"/"suppression confirmée"/"nouveau confirmé" → archivés
74+ └── "anomalie" → décision explicite (confirmer quand même | restaurer depuis l'archive)
75+ │
76+ │ toutes les écritures confirmées effectuées et vérifiées (FR-015)
77+ ▼
78+ Disponible (verrou=False, manifeste mis à jour)
79+```
80+
81+Une tentative de checkout pendant l'état **Verrouillé** est refusée (FR-004) — pas de transition, retour d'erreur explicite.
new file mode 100644
@@ -0,0 +1,81 @@
1+# Data Model: Checkout et réconciliation d'un dossier de l'archive
2+
3+Entités dérivées de `spec.md` § Key Entities et Functional Requirements.
4+
5+## Manifeste (fichier SQLite, un par dossier principal — cf. research.md § 1)
6+
7+**Table `fichiers`**
8+
9+| Champ | Type | Règles |
10+|---|---|---|
11+| `chemin_relatif` | texte | Relatif à la racine du dossier principal (traverse sous-dossiers de format et sous-dossiers d'étape) |
12+| `taille` | entier | Octets |
13+| `hash_fichier_entier` | texte (SHA-256) | Toujours calculé |
14+| `hash_image_only` | texte, nullable | Calculé uniquement pour DNG/TIFF/JPEG maîtres (cf. Principe I de la constitution) |
15+| `identifiant_perenne` | texte | Repris du champ XMP `xmpMM:DocumentID` (specs/001-import-photos) |
16+
17+**Table `verrou`**
18+
19+| Champ | Type | Règles |
20+|---|---|---|
21+| `verrouille` | bool | `True` entre un checkout et sa réconciliation réussie |
22+| `identifiant_session` | texte, nullable | Identifie l'instance ayant posé le verrou (pour message d'erreur explicite, pas pour arbitrage distribué) |
23+| `horodatage` | horodatage, nullable | Date de pose du verrou |
24+
25+**Métadonnées du fichier SQLite** : `PRAGMA user_version` (schéma `structurel*1000 + additif`, cf. research.md § 2), `PRAGMA application_id` (identifiant Régine).
26+
27+## Objets d'échange (non persistés)
28+
29+**`ManifestSnapshot`** (retour de `checkout`) : copie en mémoire du contenu du manifeste au moment du checkout, plus le chemin de la copie de travail locale.
30+
31+**`ChangementClasse`** :
32+
33+| Champ | Type |
34+|---|---|
35+| `chemin` | Path |
36+| `categorie` | énumération : `normal` \| `anomalie` \| `deplacement` \| `suppression` \| `nouveau` |
37+| `chemin_ancien` | `Path \| None` (renseigné si `categorie == deplacement`) |
38+
39+**`RapportReconciliation`** (retour de `comparer`) :
40+
41+| Champ | Type |
42+|---|---|
43+| `changements` | `list[ChangementClasse]` |
44+| `anomalies` | `list[ChangementClasse]` (sous-ensemble filtré, pour affichage séparé — FR-014) |
45+
46+## Relations
47+
48+```text
49+Dossier principal (archive) ──1────1── Manifeste (fichiers + verrou)
50+ │ checkout (FR-001)
51+ ▼
52+Copie de travail locale ──(édition libre, aucune contrainte)──▶ Copie de travail modifiée
53+ │ réconciliation : comparer(Manifeste, Copie de travail modifiée)
54+ ▼
55+RapportReconciliation (5 catégories) ──▶ Point avant archive (FR-013) ──▶ confirmation ──▶ archiver()
56+ │
57+ ▼
58+Manifeste mis à jour + verrou levé (FR-016)
59+```
60+
61+## État / transitions
62+
63+```text
64+[Dossier de l'archive]
65+ Disponible (verrou=False)
66+ │ checkout (FR-001/003)
67+ ▼
68+ Verrouillé (verrou=True, ManifestSnapshot créé)
69+ │ édition locale libre (FR-005, archive inchangée)
70+ │
71+ │ réconciliation → RapportReconciliation
72+ │
73+ ├── changements "normal"/"deplacement"/"suppression confirmée"/"nouveau confirmé" → archivés
74+ └── "anomalie" → décision explicite (confirmer quand même | restaurer depuis l'archive)
75+ │
76+ │ toutes les écritures confirmées effectuées et vérifiées (FR-015)
77+ ▼
78+ Disponible (verrou=False, manifeste mis à jour)
79+```
80+
81+Une tentative de checkout pendant l'état **Verrouillé** est refusée (FR-004) — pas de transition, retour d'erreur explicite.
added specs/005-checkout-reconciliation/plan.md +108 -0
new file mode 100644
@@ -0,0 +1,108 @@
1+# Implementation Plan: Checkout et réconciliation d'un dossier de l'archive
2+
3+**Branch**: `005-checkout-reconciliation` | **Date**: 2026-09-18 | **Spec**: [spec.md](./spec.md)
4+
5+**Input**: Feature specification from `/specs/005-checkout-reconciliation/spec.md`
6+
7+## Summary
8+
9+Implémente le cycle complet checkout → édition libre → réconciliation → réarchivage pour un dossier déjà présent dans l'archive : copie vérifiée vers un espace de travail local avec enregistrement d'un état de référence persistant, verrouillage côté archive, comparaison par empreinte de contenu classant chaque changement (normal, anomalie sur fichier maître, renommage/déplacement, suppression, nouveau fichier), présentation d'un « point avant archive » et écriture confirmée uniquement. Approche technique : deux nouveaux modules de la bibliothèque centrale déjà nommés dans l'architecture du projet — `regine_core.integrity` (hash à deux niveaux, décision anomalie/pas-anomalie) et `regine_core.archive` (manifeste persistant par dossier, verrouillage, checkout, réconciliation) — consommés par une nouvelle façade CLI (`regine checkout`, `regine reconcile`). Débloque le sous-scénario FR-009 de `specs/001-import-photos` et la User Story 4 de `specs/004-categorisation-dossiers`, tous deux documentés comme bloqués en attendant ce plan.
10+
11+## Technical Context
12+
13+**Language/Version**: Python 3.11+ (cohérent avec `regine-core`, cf. `specs/002/003/004`)
14+
15+**Primary Dependencies**: `exiftool` (déjà acté par `specs/002-profil-boitiers-optionnel`, étendu ici pour le calcul de `ImageDataHash` sur DNG/TIFF/JPEG) ; bibliothèque standard uniquement sinon (`hashlib` pour l'empreinte fichier entier SHA-256, `sqlite3` pour le manifeste persistant, `pathlib`/`shutil` pour les copies vérifiées)
16+
17+**Storage**: SQLite — un fichier de manifeste par dossier principal (parent + tous ses sous-dossiers), à sa racine sur l'archive, selon les conventions déjà actées par la constitution (`PRAGMA user_version`/`application_id`, écriture transactionnelle) ; le verrou de dossier vit dans la même base (table dédiée), pas un fichier séparé (cf. research.md § 2)
18+
19+**Testing**: pytest ; tests unitaires sur la classification de réconciliation (les 5 catégories de changement), sur la décision anomalie/pas-anomalie par format, sur le versionnement de schéma ; tests d'intégration sur le cycle complet checkout→édition simulée→réconciliation→réarchivage en répertoires temporaires (archive et local simulés par le système de fichiers)
20+
21+**Target Platform**: poste de bureau, identique aux autres modules ; l'archive est accédée comme un partage réseau monté (cf. `specs/003-config-contexte-travail`), aucune primitive de verrouillage réseau spécifique supposée disponible (cf. research.md § 3, limitation documentée)
22+
23+**Project Type**: Monorepo existant — deux nouveaux modules `regine_core/integrity/` et `regine_core/archive/` (derniers modules de la bibliothèque centrale nommés dans `docs/interface-cli-gui-architecture.md` mais pas encore construits), plus une nouvelle commande CLI
24+
25+**Performance Goals**: la réconciliation d'un dossier de quelques centaines à quelques milliers de fichiers DOIT rester praticable (minutes, pas heures) ; pas de cible chiffrée au-delà, cohérent avec l'absence de cible chiffrée dans la spec elle-même
26+
27+**Constraints**: aucune primitive de verrouillage distribué fiable n'est supposée disponible sur un partage SMB ordinaire — le verrouillage reste une protection applicative de bonne foi (toutes les instances de Régine le respectent), pas une garantie absolue contre une écriture concurrente hors de Régine (cf. research.md § 3, limitation déjà acceptée par la constitution : « protections filesystem... en filet de sécurité complémentaire ») ; le travail à plusieurs avec verrouillage fin reste hors périmètre (Assumptions de la spec)
28+
29+**Scale/Scope**: dossier archivé typique de quelques dizaines à quelques milliers de fichiers ; usage mono-utilisateur avec protection best-effort contre une seconde instance
30+
31+## Constitution Check
32+
33+*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
34+
35+| Principe / contrainte | Évaluation | Justification |
36+|---|---|---|
37+| I. Fichier maître intouchable | PASS | C'est l'implémentation directe de ce principe : FR-007/FR-008 appliquent exactement le mécanisme de hash à deux niveaux déjà décrit par le Principe I (empreinte fichier entier pour RAW propriétaires, empreinte image-only pour DNG/TIFF/JPEG), et ne réarchivent jamais silencieusement une anomalie. |
38+| II. Confirmation explicite avant toute action à risque | PASS | FR-013 : aucune écriture sur l'archive sans confirmation explicite du « point avant archive » ; FR-011 : aucune suppression propagée automatiquement. |
39+| III. Identité par contenu, jamais par nom de fichier seul | PASS | C'est également une implémentation directe : FR-009/FR-010 détectent tout renommage/déplacement par empreinte de contenu, jamais par chemin ou nom. |
40+| IV. Métadonnées ouvertes et embarquées | N/A | Ce module ne touche pas aux métadonnées descriptives/de droits (légende, mots-clés, etc.) ; il lit l'identifiant pérenne déjà embarqué (specs/001) sans le redéfinir. |
41+| V. L'utilisateur décide, Régine suggère | PASS | FR-012 : tout fichier sans correspondance reste une décision explicite de l'utilisateur, jamais un défaut imposé (cf. spec.md Assumptions, tranché sur ce principe précisément). |
42+| VI. Bibliothèque centrale, façades minces | PASS | `regine_core.integrity` et `regine_core.archive` portent toute la logique ; la façade CLI n'orchestre que l'appel et l'affichage du récapitulatif. |
43+| CLI-first | PASS | `regine checkout`/`regine reconcile` sont les premières façades construites pour ce mécanisme. |
44+| Exécution sans démon | PASS | Chaque commande est un processus isolé ; le verrou est un état persistant en base, pas un service actif. |
45+| Vérification d'intégrité systématique | PASS | FR-015 : toute copie (checkout et réarchivage) vérifiée par empreinte de contenu. |
46+| Vérification périodique indépendante (scrub) | N/A (hors périmètre) | Explicitement exclu par les Assumptions de la spec ; ce plan ne le construit pas, mais `regine_core.integrity` (hash à deux niveaux) est conçu pour être réutilisé par un futur scrub sans redéfinition. |
47+| Formats ouverts et documentés | PASS | SQLite, déjà retenu par la constitution pour ce besoin précis (manifeste persistant). |
48+
49+Aucune violation identifiée ; la section Complexity Tracking reste vide.
50+
51+**Re-check post Phase 1** (après génération de `data-model.md`, `contracts/`, `quickstart.md`) : le schéma du manifeste (une table par dossier principal, versionnée) et les contrats CLI/API interne (classification en 5 catégories exposée comme objets structurés, jamais de texte à parser) confirment chaque évaluation PASS ci-dessus. Aucune violation nouvelle.
52+
53+## Project Structure
54+
55+### Documentation (this feature)
56+
57+```text
58+specs/005-checkout-reconciliation/
59+├── plan.md # This file (/speckit-plan command output)
60+├── research.md # Phase 0 output (/speckit-plan command)
61+├── data-model.md # Phase 1 output (/speckit-plan command)
62+├── quickstart.md # Phase 1 output (/speckit-plan command)
63+├── contracts/ # Phase 1 output (/speckit-plan command)
64+│ ├── cli-checkout-reconcile.md
65+│ └── regine-core-api.md
66+└── tasks.md # Phase 2 output (/speckit-tasks command - NOT created by /speckit-plan)
67+```
68+
69+### Source Code (repository root) — extension du monorepo posé par specs/002/003/004
70+
71+```text
72+packages/regine-core/
73+├── src/regine_core/
74+│ ├── metadata/
75+│ │ └── exif.py # (existant, specs/002) étendu : read_image_data_hash() pour DNG/TIFF/JPEG
76+│ │
77+│ ├── integrity/ # NOUVEAU module (dernier module nommé dans l'architecture, pas encore construit)
78+│ │ ├── __init__.py
79+│ │ ├── hash.py # hash_fichier_entier() [SHA-256 stdlib], hash_image_only() [délègue à metadata.exif]
80+│ │ └── anomalie.py # est_maitre_modifie(format, ref, actuel) -> bool (FR-007/FR-008, Principe I)
81+│ │
82+│ └── archive/ # NOUVEAU module
83+│ ├── __init__.py
84+│ ├── manifest.py # ouverture/initialisation SQLite par dossier principal, PRAGMA user_version/application_id (FR-002/017/018)
85+│ ├── verrou.py # pose/vérifie/lève le verrou dans la même base (FR-003/004/016)
86+│ ├── checkout.py # checkout(dossier, dest_locale, formats=None) -> ManifestSnapshot (FR-001/019/020)
87+│ └── reconciliation.py # comparer() -> RapportReconciliation (FR-006/009/010/011/012/014), archiver() (FR-013/015/016)
88+│
89+└── tests/
90+ ├── unit/
91+ │ ├── test_hash_deux_niveaux.py # RAW propriétaire vs DNG/TIFF/JPEG, anomalie vs édition légitime
92+ │ ├── test_manifest_versioning.py # PRAGMA user_version, refus sur évolution structurelle, tolérance additive
93+ │ ├── test_verrou.py # pose, refus de double checkout, levée après réconciliation
94+ │ └── test_reconciliation_classification.py # les 5 catégories, y compris déplacement de dossier entier (specs/004)
95+ └── integration/
96+ ├── test_cycle_checkout_reconciliation.py # US1+US2 : checkout → édition simulée → réconciliation → réarchivage
97+ └── test_checkout_partiel.py # US6 : formats exclus jamais signalés comme anormaux
98+
99+packages/regine-cli/
100+└── src/regine_cli/
101+ └── archive_cmd.py # NOUVEAU : commandes `regine checkout <dossier>` / `regine reconcile <dossier>`
102+```
103+
104+**Structure Decision**: Extension du monorepo existant — deux modules qui achèvent la liste des modules de bibliothèque centrale déjà documentée (`docs/interface-cli-gui-architecture.md` : `metadata`, `archive`, `import`, `dossier`, `integrity`, `camera_profile` — seuls `archive` et `integrity` manquaient encore). `regine_core.metadata.exif` (specs/002) est étendu plutôt que dupliqué pour le calcul `ImageDataHash`. Ce plan complète aussi les points d'intégration laissés en attente par `specs/001-import-photos` (FR-009) et `specs/004-categorisation-dossiers` (User Story 4).
105+
106+## Complexity Tracking
107+
108+*Aucune violation de gate à justifier — section laissée vide intentionnellement.*
new file mode 100644
@@ -0,0 +1,108 @@
1+# Implementation Plan: Checkout et réconciliation d'un dossier de l'archive
2+
3+**Branch**: `005-checkout-reconciliation` | **Date**: 2026-09-18 | **Spec**: [spec.md](./spec.md)
4+
5+**Input**: Feature specification from `/specs/005-checkout-reconciliation/spec.md`
6+
7+## Summary
8+
9+Implémente le cycle complet checkout → édition libre → réconciliation → réarchivage pour un dossier déjà présent dans l'archive : copie vérifiée vers un espace de travail local avec enregistrement d'un état de référence persistant, verrouillage côté archive, comparaison par empreinte de contenu classant chaque changement (normal, anomalie sur fichier maître, renommage/déplacement, suppression, nouveau fichier), présentation d'un « point avant archive » et écriture confirmée uniquement. Approche technique : deux nouveaux modules de la bibliothèque centrale déjà nommés dans l'architecture du projet — `regine_core.integrity` (hash à deux niveaux, décision anomalie/pas-anomalie) et `regine_core.archive` (manifeste persistant par dossier, verrouillage, checkout, réconciliation) — consommés par une nouvelle façade CLI (`regine checkout`, `regine reconcile`). Débloque le sous-scénario FR-009 de `specs/001-import-photos` et la User Story 4 de `specs/004-categorisation-dossiers`, tous deux documentés comme bloqués en attendant ce plan.
10+
11+## Technical Context
12+
13+**Language/Version**: Python 3.11+ (cohérent avec `regine-core`, cf. `specs/002/003/004`)
14+
15+**Primary Dependencies**: `exiftool` (déjà acté par `specs/002-profil-boitiers-optionnel`, étendu ici pour le calcul de `ImageDataHash` sur DNG/TIFF/JPEG) ; bibliothèque standard uniquement sinon (`hashlib` pour l'empreinte fichier entier SHA-256, `sqlite3` pour le manifeste persistant, `pathlib`/`shutil` pour les copies vérifiées)
16+
17+**Storage**: SQLite — un fichier de manifeste par dossier principal (parent + tous ses sous-dossiers), à sa racine sur l'archive, selon les conventions déjà actées par la constitution (`PRAGMA user_version`/`application_id`, écriture transactionnelle) ; le verrou de dossier vit dans la même base (table dédiée), pas un fichier séparé (cf. research.md § 2)
18+
19+**Testing**: pytest ; tests unitaires sur la classification de réconciliation (les 5 catégories de changement), sur la décision anomalie/pas-anomalie par format, sur le versionnement de schéma ; tests d'intégration sur le cycle complet checkout→édition simulée→réconciliation→réarchivage en répertoires temporaires (archive et local simulés par le système de fichiers)
20+
21+**Target Platform**: poste de bureau, identique aux autres modules ; l'archive est accédée comme un partage réseau monté (cf. `specs/003-config-contexte-travail`), aucune primitive de verrouillage réseau spécifique supposée disponible (cf. research.md § 3, limitation documentée)
22+
23+**Project Type**: Monorepo existant — deux nouveaux modules `regine_core/integrity/` et `regine_core/archive/` (derniers modules de la bibliothèque centrale nommés dans `docs/interface-cli-gui-architecture.md` mais pas encore construits), plus une nouvelle commande CLI
24+
25+**Performance Goals**: la réconciliation d'un dossier de quelques centaines à quelques milliers de fichiers DOIT rester praticable (minutes, pas heures) ; pas de cible chiffrée au-delà, cohérent avec l'absence de cible chiffrée dans la spec elle-même
26+
27+**Constraints**: aucune primitive de verrouillage distribué fiable n'est supposée disponible sur un partage SMB ordinaire — le verrouillage reste une protection applicative de bonne foi (toutes les instances de Régine le respectent), pas une garantie absolue contre une écriture concurrente hors de Régine (cf. research.md § 3, limitation déjà acceptée par la constitution : « protections filesystem... en filet de sécurité complémentaire ») ; le travail à plusieurs avec verrouillage fin reste hors périmètre (Assumptions de la spec)
28+
29+**Scale/Scope**: dossier archivé typique de quelques dizaines à quelques milliers de fichiers ; usage mono-utilisateur avec protection best-effort contre une seconde instance
30+
31+## Constitution Check
32+
33+*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
34+
35+| Principe / contrainte | Évaluation | Justification |
36+|---|---|---|
37+| I. Fichier maître intouchable | PASS | C'est l'implémentation directe de ce principe : FR-007/FR-008 appliquent exactement le mécanisme de hash à deux niveaux déjà décrit par le Principe I (empreinte fichier entier pour RAW propriétaires, empreinte image-only pour DNG/TIFF/JPEG), et ne réarchivent jamais silencieusement une anomalie. |
38+| II. Confirmation explicite avant toute action à risque | PASS | FR-013 : aucune écriture sur l'archive sans confirmation explicite du « point avant archive » ; FR-011 : aucune suppression propagée automatiquement. |
39+| III. Identité par contenu, jamais par nom de fichier seul | PASS | C'est également une implémentation directe : FR-009/FR-010 détectent tout renommage/déplacement par empreinte de contenu, jamais par chemin ou nom. |
40+| IV. Métadonnées ouvertes et embarquées | N/A | Ce module ne touche pas aux métadonnées descriptives/de droits (légende, mots-clés, etc.) ; il lit l'identifiant pérenne déjà embarqué (specs/001) sans le redéfinir. |
41+| V. L'utilisateur décide, Régine suggère | PASS | FR-012 : tout fichier sans correspondance reste une décision explicite de l'utilisateur, jamais un défaut imposé (cf. spec.md Assumptions, tranché sur ce principe précisément). |
42+| VI. Bibliothèque centrale, façades minces | PASS | `regine_core.integrity` et `regine_core.archive` portent toute la logique ; la façade CLI n'orchestre que l'appel et l'affichage du récapitulatif. |
43+| CLI-first | PASS | `regine checkout`/`regine reconcile` sont les premières façades construites pour ce mécanisme. |
44+| Exécution sans démon | PASS | Chaque commande est un processus isolé ; le verrou est un état persistant en base, pas un service actif. |
45+| Vérification d'intégrité systématique | PASS | FR-015 : toute copie (checkout et réarchivage) vérifiée par empreinte de contenu. |
46+| Vérification périodique indépendante (scrub) | N/A (hors périmètre) | Explicitement exclu par les Assumptions de la spec ; ce plan ne le construit pas, mais `regine_core.integrity` (hash à deux niveaux) est conçu pour être réutilisé par un futur scrub sans redéfinition. |
47+| Formats ouverts et documentés | PASS | SQLite, déjà retenu par la constitution pour ce besoin précis (manifeste persistant). |
48+
49+Aucune violation identifiée ; la section Complexity Tracking reste vide.
50+
51+**Re-check post Phase 1** (après génération de `data-model.md`, `contracts/`, `quickstart.md`) : le schéma du manifeste (une table par dossier principal, versionnée) et les contrats CLI/API interne (classification en 5 catégories exposée comme objets structurés, jamais de texte à parser) confirment chaque évaluation PASS ci-dessus. Aucune violation nouvelle.
52+
53+## Project Structure
54+
55+### Documentation (this feature)
56+
57+```text
58+specs/005-checkout-reconciliation/
59+├── plan.md # This file (/speckit-plan command output)
60+├── research.md # Phase 0 output (/speckit-plan command)
61+├── data-model.md # Phase 1 output (/speckit-plan command)
62+├── quickstart.md # Phase 1 output (/speckit-plan command)
63+├── contracts/ # Phase 1 output (/speckit-plan command)
64+│ ├── cli-checkout-reconcile.md
65+│ └── regine-core-api.md
66+└── tasks.md # Phase 2 output (/speckit-tasks command - NOT created by /speckit-plan)
67+```
68+
69+### Source Code (repository root) — extension du monorepo posé par specs/002/003/004
70+
71+```text
72+packages/regine-core/
73+├── src/regine_core/
74+│ ├── metadata/
75+│ │ └── exif.py # (existant, specs/002) étendu : read_image_data_hash() pour DNG/TIFF/JPEG
76+│ │
77+│ ├── integrity/ # NOUVEAU module (dernier module nommé dans l'architecture, pas encore construit)
78+│ │ ├── __init__.py
79+│ │ ├── hash.py # hash_fichier_entier() [SHA-256 stdlib], hash_image_only() [délègue à metadata.exif]
80+│ │ └── anomalie.py # est_maitre_modifie(format, ref, actuel) -> bool (FR-007/FR-008, Principe I)
81+│ │
82+│ └── archive/ # NOUVEAU module
83+│ ├── __init__.py
84+│ ├── manifest.py # ouverture/initialisation SQLite par dossier principal, PRAGMA user_version/application_id (FR-002/017/018)
85+│ ├── verrou.py # pose/vérifie/lève le verrou dans la même base (FR-003/004/016)
86+│ ├── checkout.py # checkout(dossier, dest_locale, formats=None) -> ManifestSnapshot (FR-001/019/020)
87+│ └── reconciliation.py # comparer() -> RapportReconciliation (FR-006/009/010/011/012/014), archiver() (FR-013/015/016)
88+│
89+└── tests/
90+ ├── unit/
91+ │ ├── test_hash_deux_niveaux.py # RAW propriétaire vs DNG/TIFF/JPEG, anomalie vs édition légitime
92+ │ ├── test_manifest_versioning.py # PRAGMA user_version, refus sur évolution structurelle, tolérance additive
93+ │ ├── test_verrou.py # pose, refus de double checkout, levée après réconciliation
94+ │ └── test_reconciliation_classification.py # les 5 catégories, y compris déplacement de dossier entier (specs/004)
95+ └── integration/
96+ ├── test_cycle_checkout_reconciliation.py # US1+US2 : checkout → édition simulée → réconciliation → réarchivage
97+ └── test_checkout_partiel.py # US6 : formats exclus jamais signalés comme anormaux
98+
99+packages/regine-cli/
100+└── src/regine_cli/
101+ └── archive_cmd.py # NOUVEAU : commandes `regine checkout <dossier>` / `regine reconcile <dossier>`
102+```
103+
104+**Structure Decision**: Extension du monorepo existant — deux modules qui achèvent la liste des modules de bibliothèque centrale déjà documentée (`docs/interface-cli-gui-architecture.md` : `metadata`, `archive`, `import`, `dossier`, `integrity`, `camera_profile` — seuls `archive` et `integrity` manquaient encore). `regine_core.metadata.exif` (specs/002) est étendu plutôt que dupliqué pour le calcul `ImageDataHash`. Ce plan complète aussi les points d'intégration laissés en attente par `specs/001-import-photos` (FR-009) et `specs/004-categorisation-dossiers` (User Story 4).
105+
106+## Complexity Tracking
107+
108+*Aucune violation de gate à justifier — section laissée vide intentionnellement.*
added specs/005-checkout-reconciliation/quickstart.md +57 -0
new file mode 100644
@@ -0,0 +1,57 @@
1+# Quickstart : validation du checkout/réconciliation
2+
3+Ce guide valide les 6 user stories de `spec.md` via `regine checkout`/`regine reconcile` (cf. `contracts/cli-checkout-reconcile.md`). À exécuter une fois `regine_core.integrity`, `regine_core.archive` et `regine_cli.archive_cmd` implémentés.
4+
5+## Prérequis
6+
7+- `regine-core`/`regine-cli` installés, `exiftool` disponible.
8+- Un dossier de test déjà « archivé » (simple répertoire local simulant le NAS pour ce test) contenant quelques RAW, JPEG, et un DNG.
9+
10+## Scénario 1 — Checkout et verrouillage (User Story 1, P1)
11+
12+```bash
13+regine checkout ./fixtures/archive/2026/2026-08-15_Sortie-parc
14+regine checkout ./fixtures/archive/2026/2026-08-15_Sortie-parc # second essai, doit échouer
15+```
16+
17+**Résultat attendu** : le premier checkout copie tout le contenu et affiche le chemin local ; le second échoue explicitement (« dossier déjà en cours d'édition »), code non-zéro (US1 + US5).
18+
19+## Scénario 2 — Réconciliation normale (User Story 2, P2)
20+
21+```bash
22+# Modifier uniquement un sidecar XMP dans la copie de travail locale, puis :
23+regine reconcile ./fixtures/archive/2026/2026-08-15_Sortie-parc
24+```
25+
26+**Résultat attendu** : le changement est classé « normal », le point avant archive s'affiche, aucune écriture sans confirmation ; après confirmation, le verrou est levé et un nouveau checkout redevient possible.
27+
28+## Scénario 3 — Anomalie sur un fichier maître (User Story 3, P3)
29+
30+```bash
31+# Modifier le contenu pixel d'un RAW dans la copie de travail (pas seulement ses métadonnées), puis :
32+regine reconcile ./fixtures/archive/2026/2026-08-15_Sortie-parc
33+```
34+
35+**Résultat attendu** : le fichier est signalé comme anomalie, jamais réarchivé sans décision explicite. Un test complémentaire modifie uniquement les métadonnées d'un DNG (réglages non destructifs) et vérifie qu'aucune anomalie n'est signalée (hash image-only inchangé).
36+
37+## Scénario 4 — Renommage/déplacement détecté par contenu (User Story 4, P4)
38+
39+```bash
40+# Renommer un fichier dans la copie de travail (contenu inchangé), puis :
41+regine reconcile ./fixtures/archive/2026/2026-08-15_Sortie-parc
42+```
43+
44+**Résultat attendu** : classé « déplacement », jamais suppression + nouveau fichier. Un second test déplace tout le dossier de travail de `2026/` vers `mariage/` (simulant une recatégorisation, `specs/004-categorisation-dossiers`) et vérifie la même détection au niveau du dossier entier.
45+
46+## Scénario 5 — Checkout partiel (User Story 6, P6)
47+
48+```bash
49+regine checkout ./fixtures/archive/2026/2026-08-15_Sortie-parc --formats jpeg
50+regine reconcile ./fixtures/archive/2026/2026-08-15_Sortie-parc
51+```
52+
53+**Résultat attendu** : seuls les JPEG et la racine sont copiés localement ; la réconciliation qui suit ne signale aucun RAW comme supprimé ou anormal.
54+
55+## Critères de sortie
56+
57+Les 5 scénarios doivent passer sans accès réseau réel (fixtures locales). Le verrouillage (research.md § 3) reste une protection de bonne foi entre instances de Régine — ce guide ne teste pas la résistance à une modification externe concurrente hors de Régine, explicitement hors périmètre.
new file mode 100644
@@ -0,0 +1,57 @@
1+# Quickstart : validation du checkout/réconciliation
2+
3+Ce guide valide les 6 user stories de `spec.md` via `regine checkout`/`regine reconcile` (cf. `contracts/cli-checkout-reconcile.md`). À exécuter une fois `regine_core.integrity`, `regine_core.archive` et `regine_cli.archive_cmd` implémentés.
4+
5+## Prérequis
6+
7+- `regine-core`/`regine-cli` installés, `exiftool` disponible.
8+- Un dossier de test déjà « archivé » (simple répertoire local simulant le NAS pour ce test) contenant quelques RAW, JPEG, et un DNG.
9+
10+## Scénario 1 — Checkout et verrouillage (User Story 1, P1)
11+
12+```bash
13+regine checkout ./fixtures/archive/2026/2026-08-15_Sortie-parc
14+regine checkout ./fixtures/archive/2026/2026-08-15_Sortie-parc # second essai, doit échouer
15+```
16+
17+**Résultat attendu** : le premier checkout copie tout le contenu et affiche le chemin local ; le second échoue explicitement (« dossier déjà en cours d'édition »), code non-zéro (US1 + US5).
18+
19+## Scénario 2 — Réconciliation normale (User Story 2, P2)
20+
21+```bash
22+# Modifier uniquement un sidecar XMP dans la copie de travail locale, puis :
23+regine reconcile ./fixtures/archive/2026/2026-08-15_Sortie-parc
24+```
25+
26+**Résultat attendu** : le changement est classé « normal », le point avant archive s'affiche, aucune écriture sans confirmation ; après confirmation, le verrou est levé et un nouveau checkout redevient possible.
27+
28+## Scénario 3 — Anomalie sur un fichier maître (User Story 3, P3)
29+
30+```bash
31+# Modifier le contenu pixel d'un RAW dans la copie de travail (pas seulement ses métadonnées), puis :
32+regine reconcile ./fixtures/archive/2026/2026-08-15_Sortie-parc
33+```
34+
35+**Résultat attendu** : le fichier est signalé comme anomalie, jamais réarchivé sans décision explicite. Un test complémentaire modifie uniquement les métadonnées d'un DNG (réglages non destructifs) et vérifie qu'aucune anomalie n'est signalée (hash image-only inchangé).
36+
37+## Scénario 4 — Renommage/déplacement détecté par contenu (User Story 4, P4)
38+
39+```bash
40+# Renommer un fichier dans la copie de travail (contenu inchangé), puis :
41+regine reconcile ./fixtures/archive/2026/2026-08-15_Sortie-parc
42+```
43+
44+**Résultat attendu** : classé « déplacement », jamais suppression + nouveau fichier. Un second test déplace tout le dossier de travail de `2026/` vers `mariage/` (simulant une recatégorisation, `specs/004-categorisation-dossiers`) et vérifie la même détection au niveau du dossier entier.
45+
46+## Scénario 5 — Checkout partiel (User Story 6, P6)
47+
48+```bash
49+regine checkout ./fixtures/archive/2026/2026-08-15_Sortie-parc --formats jpeg
50+regine reconcile ./fixtures/archive/2026/2026-08-15_Sortie-parc
51+```
52+
53+**Résultat attendu** : seuls les JPEG et la racine sont copiés localement ; la réconciliation qui suit ne signale aucun RAW comme supprimé ou anormal.
54+
55+## Critères de sortie
56+
57+Les 5 scénarios doivent passer sans accès réseau réel (fixtures locales). Le verrouillage (research.md § 3) reste une protection de bonne foi entre instances de Régine — ce guide ne teste pas la résistance à une modification externe concurrente hors de Régine, explicitement hors périmètre.
added specs/005-checkout-reconciliation/research.md +51 -0
new file mode 100644
@@ -0,0 +1,51 @@
1+# Research: Checkout et réconciliation d'un dossier de l'archive
2+
3+## 1. Schéma du manifeste persistant
4+
5+**Decision**: Un fichier SQLite unique par dossier principal (dossier simple, ou dossier parent avec tous ses sous-dossiers), à sa racine sur l'archive — conventions déjà actées par la constitution du projet (`PRAGMA user_version` pour le numéro de schéma, `PRAGMA application_id` pour marquer le format Régine). Table `fichiers` : `chemin_relatif`, `taille`, `hash_fichier_entier`, `hash_image_only` (nullable, DNG/TIFF/JPEG uniquement), `identifiant_perenne`. Table `verrou` : `verrouille` (bool), `identifiant_session`, `horodatage` (cf. research.md § 3).
6+
7+**Rationale**: La constitution du projet (§ Workflow d'archivage) et les notes de conception (section 12) ont déjà tranché ce choix pour ce besoin précis — pas une nouvelle décision à ce stade, une application directe. Regrouper manifeste et verrou dans le même fichier évite d'inventer un second mécanisme de persistance pour un état étroitement lié (le verrou n'a de sens que par rapport à un manifeste existant).
8+
9+**Alternatives considered**:
10+- Fichier JSON pour le manifeste — déjà écarté par la constitution (pas d'écriture transactionnelle, risque de corruption en cas de coupure, cf. § Workflow d'archivage).
11+- Verrou dans un fichier séparé (ex. `.regine-lock`) — rejeté : duplique un mécanisme de persistance pour un état qui vit naturellement à côté du manifeste, dans le même fichier.
12+
13+## 2. Versionnement du schéma du manifeste (FR-018)
14+
15+**Decision**: Encoder `PRAGMA user_version` comme un entier `structurel * 1000 + additif`. À l'ouverture, le code refuse explicitement (erreur claire, pas de tentative d'interprétation) si `structurel` lu est supérieur au `structurel` que la version courante du logiciel sait traiter ; il tolère un `additif` supérieur (ignore les champs qu'il ne connaît pas encore).
16+
17+**Rationale**: Répond à FR-018 (distinguer évolution additive tolérée / évolution structurelle refusée) avec un mécanisme simple, entièrement dans la bibliothèque standard (`sqlite3` expose `PRAGMA user_version` nativement), sans dépendance à un outil de migration externe. Cohérent avec la référence de la constitution à `core.repositoryformatversion` de Git comme précédent de conception pour ce genre de garde-fou.
18+
19+**Alternatives considered**:
20+- Outil de migration de schéma tiers (ex. `alembic`) — rejeté : complexité disproportionnée pour un schéma aussi simple et un besoin de version déjà entièrement couvert par les pragmas SQLite natifs.
21+
22+## 3. Verrouillage de dossier sur un partage réseau ordinaire
23+
24+**Decision**: Le verrou est une ligne dans la table `verrou` du manifeste (posée au checkout, vérifiée avant tout nouveau checkout, levée à la réconciliation réussie). C'est une protection applicative de bonne foi : toute instance de Régine la respecte, mais rien n'empêche techniquement une écriture concurrente hors de Régine (édition directe du fichier NAS, autre logiciel) — limitation assumée et déjà annoncée par la constitution elle-même (« protections filesystem... en filet de sécurité complémentaire, plus comme mécanisme principal »).
25+
26+**Rationale**: Un partage SMB ordinaire n'offre pas de primitive de verrouillage distribué fiable et portable entre systèmes d'exploitation ; construire un mécanisme de verrouillage réseau robuste (ex. bail avec expiration, élection) serait une complexité disproportionnée pour un usage mono-utilisateur avec, au pire, deux instances de la même personne (cf. Assumptions de la spec : le travail à plusieurs avec verrouillage fin est explicitement hors périmètre).
27+
28+**Alternatives considered**:
29+- Verrouillage au niveau du système de fichiers (ex. fichier `.lock` avec `flock`) — rejeté : `flock` n'est pas fiable sur tous les montages SMB selon la configuration du serveur et du client, alors qu'une ligne dans une base déjà lue/écrite de façon transactionnelle par Régine est un mécanisme plus prévisible.
30+- Service de verrouillage distribué (ex. base centrale accessible par API réseau) — rejeté : introduirait un démon/service, contraire à la contrainte « exécution sans démon » déjà actée par la constitution.
31+
32+## 4. Détection de renommage/déplacement par contenu
33+
34+**Decision**: Construire, pour la comparaison, un index `hash → chemin(s)` à la fois pour le manifeste et pour la copie de travail (`hash_image_only` quand disponible pour DNG/TIFF/JPEG, sinon `hash_fichier_entier`). Une entrée présente dans les deux index mais sous un chemin différent est un renommage/déplacement ; ce même mécanisme couvre indifféremment un renommage de fichier, une promotion vers la racine de sélection, et un déplacement de dossier entier vers un autre répertoire racine (`specs/004-categorisation-dossiers` FR-010).
35+
36+**Rationale**: Un seul algorithme couvre tous les cas de FR-009/FR-010 sans traitement spécial, cohérent avec la spec elle-même qui insiste sur ce point (« pas un cas à part »).
37+
38+**Alternatives considered**:
39+- Traiter la promotion à la racine ou le déplacement de dossier comme des opérations dédiées, détectées séparément — rejeté : complexité et risque de divergence de comportement pour des cas fondamentalement identiques (contenu inchangé, chemin différent).
40+
41+## 5. Calcul de `ImageDataHash` pour DNG/TIFF/JPEG
42+
43+**Decision**: Étendre `regine_core.metadata.exif` (créé par `specs/002-profil-boitiers-optionnel`) avec `read_image_data_hash(chemin) -> str`, invoquant `exiftool -api ImageHashType=SHA256 -ImageDataHash` sur le même processus persistant déjà utilisé pour `Model`/`BodySerialNumber`/`DateTimeOriginal`.
44+
45+**Rationale**: Réutilise le mécanisme déjà en place plutôt que d'en créer un second ; les notes de conception du projet (section 11) documentent déjà ce choix précis (`ImageDataHash` d'ExifTool) comme la solution retenue.
46+
47+**Alternatives considered**: aucune — la décision est déjà actée par les notes de conception du projet, pas un nouveau choix à faire ici.
48+
49+## Résumé
50+
51+Tous les points du Technical Context sont résolus. Aucune dépendance tierce Python nouvelle. Une limitation est explicitement documentée plutôt que masquée : le verrouillage reste une protection de bonne foi entre instances de Régine, pas une garantie absolue contre toute écriture concurrente externe.
new file mode 100644
@@ -0,0 +1,51 @@
1+# Research: Checkout et réconciliation d'un dossier de l'archive
2+
3+## 1. Schéma du manifeste persistant
4+
5+**Decision**: Un fichier SQLite unique par dossier principal (dossier simple, ou dossier parent avec tous ses sous-dossiers), à sa racine sur l'archive — conventions déjà actées par la constitution du projet (`PRAGMA user_version` pour le numéro de schéma, `PRAGMA application_id` pour marquer le format Régine). Table `fichiers` : `chemin_relatif`, `taille`, `hash_fichier_entier`, `hash_image_only` (nullable, DNG/TIFF/JPEG uniquement), `identifiant_perenne`. Table `verrou` : `verrouille` (bool), `identifiant_session`, `horodatage` (cf. research.md § 3).
6+
7+**Rationale**: La constitution du projet (§ Workflow d'archivage) et les notes de conception (section 12) ont déjà tranché ce choix pour ce besoin précis — pas une nouvelle décision à ce stade, une application directe. Regrouper manifeste et verrou dans le même fichier évite d'inventer un second mécanisme de persistance pour un état étroitement lié (le verrou n'a de sens que par rapport à un manifeste existant).
8+
9+**Alternatives considered**:
10+- Fichier JSON pour le manifeste — déjà écarté par la constitution (pas d'écriture transactionnelle, risque de corruption en cas de coupure, cf. § Workflow d'archivage).
11+- Verrou dans un fichier séparé (ex. `.regine-lock`) — rejeté : duplique un mécanisme de persistance pour un état qui vit naturellement à côté du manifeste, dans le même fichier.
12+
13+## 2. Versionnement du schéma du manifeste (FR-018)
14+
15+**Decision**: Encoder `PRAGMA user_version` comme un entier `structurel * 1000 + additif`. À l'ouverture, le code refuse explicitement (erreur claire, pas de tentative d'interprétation) si `structurel` lu est supérieur au `structurel` que la version courante du logiciel sait traiter ; il tolère un `additif` supérieur (ignore les champs qu'il ne connaît pas encore).
16+
17+**Rationale**: Répond à FR-018 (distinguer évolution additive tolérée / évolution structurelle refusée) avec un mécanisme simple, entièrement dans la bibliothèque standard (`sqlite3` expose `PRAGMA user_version` nativement), sans dépendance à un outil de migration externe. Cohérent avec la référence de la constitution à `core.repositoryformatversion` de Git comme précédent de conception pour ce genre de garde-fou.
18+
19+**Alternatives considered**:
20+- Outil de migration de schéma tiers (ex. `alembic`) — rejeté : complexité disproportionnée pour un schéma aussi simple et un besoin de version déjà entièrement couvert par les pragmas SQLite natifs.
21+
22+## 3. Verrouillage de dossier sur un partage réseau ordinaire
23+
24+**Decision**: Le verrou est une ligne dans la table `verrou` du manifeste (posée au checkout, vérifiée avant tout nouveau checkout, levée à la réconciliation réussie). C'est une protection applicative de bonne foi : toute instance de Régine la respecte, mais rien n'empêche techniquement une écriture concurrente hors de Régine (édition directe du fichier NAS, autre logiciel) — limitation assumée et déjà annoncée par la constitution elle-même (« protections filesystem... en filet de sécurité complémentaire, plus comme mécanisme principal »).
25+
26+**Rationale**: Un partage SMB ordinaire n'offre pas de primitive de verrouillage distribué fiable et portable entre systèmes d'exploitation ; construire un mécanisme de verrouillage réseau robuste (ex. bail avec expiration, élection) serait une complexité disproportionnée pour un usage mono-utilisateur avec, au pire, deux instances de la même personne (cf. Assumptions de la spec : le travail à plusieurs avec verrouillage fin est explicitement hors périmètre).
27+
28+**Alternatives considered**:
29+- Verrouillage au niveau du système de fichiers (ex. fichier `.lock` avec `flock`) — rejeté : `flock` n'est pas fiable sur tous les montages SMB selon la configuration du serveur et du client, alors qu'une ligne dans une base déjà lue/écrite de façon transactionnelle par Régine est un mécanisme plus prévisible.
30+- Service de verrouillage distribué (ex. base centrale accessible par API réseau) — rejeté : introduirait un démon/service, contraire à la contrainte « exécution sans démon » déjà actée par la constitution.
31+
32+## 4. Détection de renommage/déplacement par contenu
33+
34+**Decision**: Construire, pour la comparaison, un index `hash → chemin(s)` à la fois pour le manifeste et pour la copie de travail (`hash_image_only` quand disponible pour DNG/TIFF/JPEG, sinon `hash_fichier_entier`). Une entrée présente dans les deux index mais sous un chemin différent est un renommage/déplacement ; ce même mécanisme couvre indifféremment un renommage de fichier, une promotion vers la racine de sélection, et un déplacement de dossier entier vers un autre répertoire racine (`specs/004-categorisation-dossiers` FR-010).
35+
36+**Rationale**: Un seul algorithme couvre tous les cas de FR-009/FR-010 sans traitement spécial, cohérent avec la spec elle-même qui insiste sur ce point (« pas un cas à part »).
37+
38+**Alternatives considered**:
39+- Traiter la promotion à la racine ou le déplacement de dossier comme des opérations dédiées, détectées séparément — rejeté : complexité et risque de divergence de comportement pour des cas fondamentalement identiques (contenu inchangé, chemin différent).
40+
41+## 5. Calcul de `ImageDataHash` pour DNG/TIFF/JPEG
42+
43+**Decision**: Étendre `regine_core.metadata.exif` (créé par `specs/002-profil-boitiers-optionnel`) avec `read_image_data_hash(chemin) -> str`, invoquant `exiftool -api ImageHashType=SHA256 -ImageDataHash` sur le même processus persistant déjà utilisé pour `Model`/`BodySerialNumber`/`DateTimeOriginal`.
44+
45+**Rationale**: Réutilise le mécanisme déjà en place plutôt que d'en créer un second ; les notes de conception du projet (section 11) documentent déjà ce choix précis (`ImageDataHash` d'ExifTool) comme la solution retenue.
46+
47+**Alternatives considered**: aucune — la décision est déjà actée par les notes de conception du projet, pas un nouveau choix à faire ici.
48+
49+## Résumé
50+
51+Tous les points du Technical Context sont résolus. Aucune dépendance tierce Python nouvelle. Une limitation est explicitement documentée plutôt que masquée : le verrouillage reste une protection de bonne foi entre instances de Régine, pas une garantie absolue contre toute écriture concurrente externe.