fonzarely/regine-photos-archiverpublic Fork
⑂ main
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.

Download ZIP
refactor 19cf29c · on main · Fabien Champigny · Sep 24, 2026
research.md · 75 lines · 12.2 KBmarkdown
Blame HistoryOpen raw

Research: Configuration du contexte de travail de Régine

1. Plateforme cible et déclenchement du montage natif SMB

Decision: Prioriser macOS pour cette itération. Le déclenchement du montage se fait via open smb://<hôte>/<partage> (ouvre le mécanisme natif de montage de Finder, avec authentification gérée entièrement par l'OS) ; la détection de disponibilité consiste à vérifier l'accessibilité du point de montage local avant chaque opération nécessitant l'archive. Le mécanisme est isolé derrière une interface (src/regine/config/smb.py) pour permettre d'ajouter Windows (net use / explorateur) et Linux (gio mount smb://...) plus tard sans toucher au reste du module.

Rationale: Le poste de développement et d'usage actuel du projet est macOS ; Windows/Linux ne sont mentionnés dans les notes de conception que comme options futures pour la GUI (PySide6 vs web), jamais comme cible immédiate. Isoler le mécanisme derrière une interface respecte le Principe VI de la constitution (bibliothèque centrale, façades minces) et le principe de simplicité déjà appliqué ailleurs dans le projet (pas de mécanisme pour des cas hypothétiques non encore demandés).

Alternatives considered:

  • Gérer soi-même le montage SMB multiplateforme via une bibliothèque tierce (pysmb, smbprotocol) — rejeté : la clarification du 2026-09-18 sur cette spec exclut explicitement que Régine gère ou stocke des identifiants réseau ; cette responsabilité reste déléguée à l'OS.
  • Implémenter les trois OS dès cette itération — rejeté pour limiter la portée initiale ; l'abstraction retenue permet de le faire plus tard sans re-conception.

2. Schéma de la base de contexte centralisée (SQLite)

Decision: Réutiliser les conventions déjà actées par la constitution pour le manifeste persistant par dossier (§ Workflow d'archivage, section 12 des notes de conception) : un fichier SQLite unique, PRAGMA user_version pour le numéro de schéma, PRAGMA application_id pour marquer le format Régine, écriture transactionnelle. Table principale boitiers : identifiant interne, modèle EXIF, numéro de série EXIF (nullable), nom lisible (nullable), horodatage de première rencontre. Unicité sur (modèle, numéro de série) quand le numéro de série est exploitable, sinon sur le modèle seul tant qu'aucune collision réelle ne force une distinction plus fine (cohérent avec specs/002-profil-boitiers-optionnel).

Rationale: Cohérence avec le mécanisme déjà retenu pour les bases par dossier (mêmes garanties transactionnelles, même stratégie de version de format) plutôt que d'inventer un second mécanisme de persistance pour un besoin très proche ; SQLite est explicitement le format recommandé par la constitution pour l'archivage long terme.

Alternatives considered:

  • Fichier JSON pour la base de contexte — rejeté : pas d'écriture transactionnelle, risque de corruption en cas de coupure, alors que c'est précisément ce que le choix SQLite du projet cherche à éviter ailleurs.
  • Une table dans la même base SQLite qu'un manifeste de dossier — rejeté : la base de contexte centralisée n'est pas liée à un dossier particulier et ne doit pas être archivée avec lui (cf. Assumptions de la spec).

3. Validation des répertoires (existence, écriture, espace disque)

Decision: Utiliser les primitives standard de Python (pathlib.Path.exists/mkdir, os.access, shutil.disk_usage) pour vérifier l'existence, la capacité d'écriture et l'espace disponible avant toute opération de configuration ou d'initialisation de la base de données de travail.

Rationale: Ces vérifications sont simples et entièrement couvertes par la bibliothèque standard ; le projet préfère des dépendances minimales (cf. Principe VI, pas de complexité non justifiée).

Alternatives considered:

  • Bibliothèque tierce de gestion de systèmes de fichiers multiplateforme — rejeté, complexité non justifiée pour un besoin déjà couvert nativement.

4. Convention de surface CLI

Decision: Sous-commandes regine config set-paths, regine config show, regine config cameras list, regine config cameras rename, suivant les conventions CLI Unix standard déjà actées par la constitution (arguments/flags en entrée, texte sur stdout, erreurs sur stderr, code de sortie non-zéro en cas d'échec).

Rationale: Cohérence avec la contrainte technique CLI-first déjà actée (protocole texte in/out) ; pas de nouvelle convention à inventer.

Alternatives considered:

  • Fichier de configuration statique édité à la main (YAML/TOML), sans commande dédiée — rejeté : contredit FR-003 (proposer de créer les répertoires), FR-004/FR-005 (détection et mémorisation dynamiques du montage SMB) et FR-010 (refus actif d'un changement tant qu'un travail n'est pas réconcilié), qui impliquent une logique active plutôt qu'un simple fichier statique.

5. Organisation monorepo (bibliothèque centrale + 3 façades)

Decision: Adopter un monorepo à quatre paquets Python indépendants et installables séparément : regine-core (bibliothèque centrale, toute la logique métier), regine-cli, regine-gui, regine-agent (façades, chacune dépendant de regine-core sans logique métier propre). Outillage de workspace : uv (gestion de dépendances et lockfile unique au niveau du dépôt, uv.lock), avec un pyproject.toml par paquet sous packages/<nom>/ suivant le format standard PEP 621. regine-gui et regine-agent sont créés comme paquets vides (dépendance déclarée, aucun code métier) pour réserver la structure dès cette itération, sans être implémentés maintenant.

Rationale: La demande explicite est de penser dès maintenant la place des 4 parties techniques (lib, CLI, GUI, agent IA) plutôt que de les découvrir au fur et à mesure. Un monorepo à paquets séparés rend le Principe VI (bibliothèque centrale, façades minces) vérifiable structurellement : une façade qui importerait autre chose que l'API publique de regine-core serait une violation visible dès la déclaration des dépendances du paquet, pas seulement une convention à surveiller par relecture. uv est l'outil de gestion de paquets/workspace Python le plus rapide et le plus simple à opérer à la date de ce plan, avec un support natif des workspaces multi-paquets et un lockfile unique — cohérent avec la préférence du projet pour des dépendances minimales et une exécution sans démon (pas de service d'outillage à faire tourner, juste un binaire).

Alternatives considered:

  • Un seul paquet src/regine/ avec des sous-modules cli/, gui/, agent/ à l'intérieur (structure initialement retenue avant cette clarification) — rejeté : ne matérialise pas physiquement la séparation bibliothèque/façades, une façade pourrait importer un module interne d'une autre façade sans qu'aucun outil ne le signale.
  • Poetry avec dépendances de chemin (path = "../regine-core") — alternative valable, écosystème plus ancien et plus répandu ; non retenue par préférence pour la rapidité et la simplicité de configuration de uv, mais ce choix n'est pas structurant pour la conception elle-même et pourrait être révisé sans impact sur data-model.md/contracts/.
  • Dépôts séparés (un par façade) — rejeté : complique la coordination des changements d'API de regine-core avec ses façades, alors que le projet est encore à un stade où bibliothèque et façades évoluent ensemble ; un monorepo reste plus simple à faire évoluer tant qu'aucune façade n'a de cycle de release indépendant.

6. Séparation configuration globale / base de contexte centralisée (User Story 5, 2026-09-23/24)

Decision: Deux fichiers SQLite distincts plutôt qu'un fichier unique à deux emplacements possibles. ~/.regine/config.sqlite3 (nouveau) ne porte que la table contexte (chemins, état SMB) — mêmes conventions PRAGMA application_id/user_version que l'existant, juste un schéma réduit. <local_work_dir>/contexte.sqlite3 (ex-~/.regine/contexte.sqlite3, déplacé) porte boitiers/categories/checksums_importes. Un nouveau point d'entrée unique, regine_core.config.environnement.ouvrir_environnement(), résout les deux dans l'ordre (config globale d'abord, puis contexte centralisée si local_work_dir est déjà connu) et remplace les appels épars à open_context_db(None) dans chaque façade CLI/GUI.

Rationale: SQLite plutôt que JSON pour la configuration globale, malgré sa taille minime (3 chemins + 1 état) : cohérent avec le principe déjà établi dans ce module (research.md § 2, « inventer un second mécanisme de persistance pour un besoin très proche » a déjà été rejeté une fois) — réutiliser exactement le même bootstrap PRAGMA application_id/ensure_schema déjà écrit et testé, plutôt qu'introduire un second format de fichier (JSON) et sa propre gestion d'erreurs (fichier corrompu, absent, verrouillé) pour un gain marginal. Un point d'entrée unique (ouvrir_environnement) plutôt que de dupliquer la logique de résolution en deux temps dans chaque commande CLI et chaque écran GUI : exactement le même raisonnement déjà appliqué à smb.resoudre_archive_locale (specs/006 research.md § 6) pour éviter qu'une façade diverge silencieusement d'une autre.

Alternatives considered:

  • Fichier JSON pour la configuration globale — rejeté, cf. rationale (deuxième format de fichier pour un gain marginal, alors qu'un schéma SQLite minimal coûte une seule table).
  • Garder un unique fichier à un unique emplacement, avec un simple booléen « à répliquer ou non » par table — rejeté : ne résout pas le vrai besoin (un nouvel ordinateur doit pouvoir localiser ce fichier sans déjà le connaître, cf. FR-007) ; deux emplacements physiques distincts sont la façon la plus directe de garantir cette indépendance.
  • Continuer à appeler open_context_db(None) séparément dans chaque façade, chacune recalculant local_work_dir à partir de la config globale — rejeté : duplique la logique de résolution en deux temps à N endroits, avec le risque déjà matérialisé une fois dans ce projet (bug réel : import_cmd.py utilisait un chemin par défaut différent de toutes les autres commandes, jamais détecté avant ce jour faute de point d'entrée unique).

7. Déclenchement de la réplication/restauration : orchestration en façade, pas dans regine_core.archive

Decision: sauvegarder_contexte()/restaurer_contexte_si_absent() vivent dans regine_core.config.context ; elles sont appelées explicitement par les commandes CLI (archive_cmd._cmd_reconcile/_cmd_checkout) juste après un reconcile/checkout réussi — jamais depuis l'intérieur de regine_core.archive.reconciliation/checkout elles-mêmes.

Rationale: regine_core.archive (specs/005) n'a aujourd'hui aucune dépendance vers regine_core.config ; lui en ajouter une pour ce seul besoin couplerait deux modules déjà stables et testés indépendamment, pour une préoccupation (sauvegarde du contexte) qui n'a rien à voir avec la réconciliation de fichiers elle-même. Orchestrer depuis la façade CLI (qui connaît déjà les deux modules, cf. import_cmd.py orchestrant import_carte/camera_profile/config.categories sans qu'aucun de ces modules ne se connaisse entre eux) garde chaque module regine_core focalisé sur une seule responsabilité (Principe VI).

Alternatives considered:

  • Un hook/callback enregistré dans regine_core.archive — rejeté, complexité d'indirection non justifiée pour deux points d'appel connus et fixes.
  • Fusionner la sauvegarde dans reconciliation.archiver() elle-même — rejeté, cf. rationale (couplage inter-modules non nécessaire).

Résumé

Tous les points marqués NEEDS CLARIFICATION dans le Technical Context du plan sont résolus par les décisions ci-dessus. Aucune dépendance tierce Python nouvelle n'est introduite pour le code de regine-core/regine-cli ; uv est un outil de développement/workspace (pas une dépendance runtime des paquets) introduit par la décision § 5. L'extension du 2026-09-23/24 (User Story 5) n'introduit elle non plus aucune dépendance nouvelle — uniquement une réorganisation de fichiers SQLite déjà en place et deux fonctions ajoutées à un module existant.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# Research: Configuration du contexte de travail de Régine

## 1. Plateforme cible et déclenchement du montage natif SMB

**Decision**: Prioriser macOS pour cette itération. Le déclenchement du montage se fait via `open smb://<hôte>/<partage>` (ouvre le mécanisme natif de montage de Finder, avec authentification gérée entièrement par l'OS) ; la détection de disponibilité consiste à vérifier l'accessibilité du point de montage local avant chaque opération nécessitant l'archive. Le mécanisme est isolé derrière une interface (`src/regine/config/smb.py`) pour permettre d'ajouter Windows (`net use` / explorateur) et Linux (`gio mount smb://...`) plus tard sans toucher au reste du module.

**Rationale**: Le poste de développement et d'usage actuel du projet est macOS ; Windows/Linux ne sont mentionnés dans les notes de conception que comme options futures pour la GUI (PySide6 vs web), jamais comme cible immédiate. Isoler le mécanisme derrière une interface respecte le Principe VI de la constitution (bibliothèque centrale, façades minces) et le principe de simplicité déjà appliqué ailleurs dans le projet (pas de mécanisme pour des cas hypothétiques non encore demandés).

**Alternatives considered**:
- Gérer soi-même le montage SMB multiplateforme via une bibliothèque tierce (`pysmb`, `smbprotocol`) — rejeté : la clarification du 2026-09-18 sur cette spec exclut explicitement que Régine gère ou stocke des identifiants réseau ; cette responsabilité reste déléguée à l'OS.
- Implémenter les trois OS dès cette itération — rejeté pour limiter la portée initiale ; l'abstraction retenue permet de le faire plus tard sans re-conception.

## 2. Schéma de la base de contexte centralisée (SQLite)

**Decision**: Réutiliser les conventions déjà actées par la constitution pour le manifeste persistant par dossier (§ Workflow d'archivage, section 12 des notes de conception) : un fichier SQLite unique, `PRAGMA user_version` pour le numéro de schéma, `PRAGMA application_id` pour marquer le format Régine, écriture transactionnelle. Table principale `boitiers` : identifiant interne, modèle EXIF, numéro de série EXIF (nullable), nom lisible (nullable), horodatage de première rencontre. Unicité sur (modèle, numéro de série) quand le numéro de série est exploitable, sinon sur le modèle seul tant qu'aucune collision réelle ne force une distinction plus fine (cohérent avec `specs/002-profil-boitiers-optionnel`).

**Rationale**: Cohérence avec le mécanisme déjà retenu pour les bases par dossier (mêmes garanties transactionnelles, même stratégie de version de format) plutôt que d'inventer un second mécanisme de persistance pour un besoin très proche ; SQLite est explicitement le format recommandé par la constitution pour l'archivage long terme.

**Alternatives considered**:
- Fichier JSON pour la base de contexte — rejeté : pas d'écriture transactionnelle, risque de corruption en cas de coupure, alors que c'est précisément ce que le choix SQLite du projet cherche à éviter ailleurs.
- Une table dans la même base SQLite qu'un manifeste de dossier — rejeté : la base de contexte centralisée n'est pas liée à un dossier particulier et ne doit pas être archivée avec lui (cf. Assumptions de la spec).

## 3. Validation des répertoires (existence, écriture, espace disque)

**Decision**: Utiliser les primitives standard de Python (`pathlib.Path.exists`/`mkdir`, `os.access`, `shutil.disk_usage`) pour vérifier l'existence, la capacité d'écriture et l'espace disponible avant toute opération de configuration ou d'initialisation de la base de données de travail.

**Rationale**: Ces vérifications sont simples et entièrement couvertes par la bibliothèque standard ; le projet préfère des dépendances minimales (cf. Principe VI, pas de complexité non justifiée).

**Alternatives considered**:
- Bibliothèque tierce de gestion de systèmes de fichiers multiplateforme — rejeté, complexité non justifiée pour un besoin déjà couvert nativement.

## 4. Convention de surface CLI

**Decision**: Sous-commandes `regine config set-paths`, `regine config show`, `regine config cameras list`, `regine config cameras rename`, suivant les conventions CLI Unix standard déjà actées par la constitution (arguments/flags en entrée, texte sur stdout, erreurs sur stderr, code de sortie non-zéro en cas d'échec).

**Rationale**: Cohérence avec la contrainte technique CLI-first déjà actée (protocole texte in/out) ; pas de nouvelle convention à inventer.

**Alternatives considered**:
- Fichier de configuration statique édité à la main (YAML/TOML), sans commande dédiée — rejeté : contredit FR-003 (proposer de créer les répertoires), FR-004/FR-005 (détection et mémorisation dynamiques du montage SMB) et FR-010 (refus actif d'un changement tant qu'un travail n'est pas réconcilié), qui impliquent une logique active plutôt qu'un simple fichier statique.

## 5. Organisation monorepo (bibliothèque centrale + 3 façades)

**Decision**: Adopter un monorepo à quatre paquets Python indépendants et installables séparément : `regine-core` (bibliothèque centrale, toute la logique métier), `regine-cli`, `regine-gui`, `regine-agent` (façades, chacune dépendant de `regine-core` sans logique métier propre). Outillage de workspace : `uv` (gestion de dépendances et lockfile unique au niveau du dépôt, `uv.lock`), avec un `pyproject.toml` par paquet sous `packages/<nom>/` suivant le format standard PEP 621. `regine-gui` et `regine-agent` sont créés comme paquets vides (dépendance déclarée, aucun code métier) pour réserver la structure dès cette itération, sans être implémentés maintenant.

**Rationale**: La demande explicite est de penser dès maintenant la place des 4 parties techniques (lib, CLI, GUI, agent IA) plutôt que de les découvrir au fur et à mesure. Un monorepo à paquets séparés rend le Principe VI (bibliothèque centrale, façades minces) vérifiable structurellement : une façade qui importerait autre chose que l'API publique de `regine-core` serait une violation visible dès la déclaration des dépendances du paquet, pas seulement une convention à surveiller par relecture. `uv` est l'outil de gestion de paquets/workspace Python le plus rapide et le plus simple à opérer à la date de ce plan, avec un support natif des workspaces multi-paquets et un lockfile unique — cohérent avec la préférence du projet pour des dépendances minimales et une exécution sans démon (pas de service d'outillage à faire tourner, juste un binaire).

**Alternatives considered**:
- Un seul paquet `src/regine/` avec des sous-modules `cli/`, `gui/`, `agent/` à l'intérieur (structure initialement retenue avant cette clarification) — rejeté : ne matérialise pas physiquement la séparation bibliothèque/façades, une façade pourrait importer un module interne d'une autre façade sans qu'aucun outil ne le signale.
- Poetry avec dépendances de chemin (`path = "../regine-core"`) — alternative valable, écosystème plus ancien et plus répandu ; non retenue par préférence pour la rapidité et la simplicité de configuration de `uv`, mais ce choix n'est pas structurant pour la conception elle-même et pourrait être révisé sans impact sur `data-model.md`/`contracts/`.
- Dépôts séparés (un par façade) — rejeté : complique la coordination des changements d'API de `regine-core` avec ses façades, alors que le projet est encore à un stade où bibliothèque et façades évoluent ensemble ; un monorepo reste plus simple à faire évoluer tant qu'aucune façade n'a de cycle de release indépendant.

## 6. Séparation configuration globale / base de contexte centralisée (User Story 5, 2026-09-23/24)

**Decision**: Deux fichiers SQLite distincts plutôt qu'un fichier unique à deux emplacements possibles. `~/.regine/config.sqlite3` (nouveau) ne porte que la table `contexte` (chemins, état SMB) — mêmes conventions `PRAGMA application_id`/`user_version` que l'existant, juste un schéma réduit. `<local_work_dir>/contexte.sqlite3` (ex-`~/.regine/contexte.sqlite3`, déplacé) porte `boitiers`/`categories`/`checksums_importes`. Un nouveau point d'entrée unique, `regine_core.config.environnement.ouvrir_environnement()`, résout les deux dans l'ordre (config globale d'abord, puis contexte centralisée si `local_work_dir` est déjà connu) et remplace les appels épars à `open_context_db(None)` dans chaque façade CLI/GUI.

**Rationale**: SQLite plutôt que JSON pour la configuration globale, malgré sa taille minime (3 chemins + 1 état) : cohérent avec le principe déjà établi dans ce module (research.md § 2, « inventer un second mécanisme de persistance pour un besoin très proche » a déjà été rejeté une fois) — réutiliser exactement le même bootstrap `PRAGMA application_id`/`ensure_schema` déjà écrit et testé, plutôt qu'introduire un second format de fichier (JSON) et sa propre gestion d'erreurs (fichier corrompu, absent, verrouillé) pour un gain marginal. Un point d'entrée unique (`ouvrir_environnement`) plutôt que de dupliquer la logique de résolution en deux temps dans chaque commande CLI et chaque écran GUI : exactement le même raisonnement déjà appliqué à `smb.resoudre_archive_locale` (specs/006 research.md § 6) pour éviter qu'une façade diverge silencieusement d'une autre.

**Alternatives considered**:
- Fichier JSON pour la configuration globale — rejeté, cf. rationale (deuxième format de fichier pour un gain marginal, alors qu'un schéma SQLite minimal coûte une seule table).
- Garder un unique fichier à un unique emplacement, avec un simple booléen « à répliquer ou non » par table — rejeté : ne résout pas le vrai besoin (un nouvel ordinateur doit pouvoir localiser ce fichier sans déjà le connaître, cf. FR-007) ; deux emplacements physiques distincts sont la façon la plus directe de garantir cette indépendance.
- Continuer à appeler `open_context_db(None)` séparément dans chaque façade, chacune recalculant `local_work_dir` à partir de la config globale — rejeté : duplique la logique de résolution en deux temps à N endroits, avec le risque déjà matérialisé une fois dans ce projet (bug réel : `import_cmd.py` utilisait un chemin par défaut différent de toutes les autres commandes, jamais détecté avant ce jour faute de point d'entrée unique).

## 7. Déclenchement de la réplication/restauration : orchestration en façade, pas dans `regine_core.archive`

**Decision**: `sauvegarder_contexte()`/`restaurer_contexte_si_absent()` vivent dans `regine_core.config.context` ; elles sont appelées explicitement par les commandes CLI (`archive_cmd._cmd_reconcile`/`_cmd_checkout`) juste après un `reconcile`/`checkout` réussi — jamais depuis l'intérieur de `regine_core.archive.reconciliation`/`checkout` elles-mêmes.

**Rationale**: `regine_core.archive` (specs/005) n'a aujourd'hui aucune dépendance vers `regine_core.config` ; lui en ajouter une pour ce seul besoin couplerait deux modules déjà stables et testés indépendamment, pour une préoccupation (sauvegarde du contexte) qui n'a rien à voir avec la réconciliation de fichiers elle-même. Orchestrer depuis la façade CLI (qui connaît déjà les deux modules, cf. `import_cmd.py` orchestrant `import_carte`/`camera_profile`/`config.categories` sans qu'aucun de ces modules ne se connaisse entre eux) garde chaque module `regine_core` focalisé sur une seule responsabilité (Principe VI).

**Alternatives considered**:
- Un hook/callback enregistré dans `regine_core.archive` — rejeté, complexité d'indirection non justifiée pour deux points d'appel connus et fixes.
- Fusionner la sauvegarde dans `reconciliation.archiver()` elle-même — rejeté, cf. rationale (couplage inter-modules non nécessaire).

## Résumé

Tous les points marqués `NEEDS CLARIFICATION` dans le Technical Context du plan sont résolus par les décisions ci-dessus. Aucune dépendance tierce Python nouvelle n'est introduite pour le code de `regine-core`/`regine-cli` ; `uv` est un outil de développement/workspace (pas une dépendance runtime des paquets) introduit par la décision § 5. L'extension du 2026-09-23/24 (User Story 5) n'introduit elle non plus aucune dépendance nouvelle — uniquement une réorganisation de fichiers SQLite déjà en place et deux fonctions ajoutées à un module existant.