docs: VOICE_DEPLOYMENT_SPEC §4 requis — multi-patients/tablette → exclusivité on-device

Décidé : une tablette héberge plusieurs profils patients. La localisation du .cvps ne
suffit pas → mécanisme sidecar cosyvoice/<id>.owner (profileId, absent=généraliste)
poussé par Kazeia-central ; app lit le sidecar, expose owner_profile_id à /voices,
filtre le picker, repli runtime sur DEFAULT_VOICE_ID si profil non-propriétaire.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
alf 2026-06-19 20:21:34 +02:00
parent 342966e867
commit 0924174c7b
1 changed files with 34 additions and 28 deletions

View File

@ -70,43 +70,49 @@ propre, sans aller-retour WebDAV ni redémarrage.
--- ---
## 3. Politique de déploiement (RAPPEL — appliquée côté Kazeia-central) ## 3. Politique de déploiement (appliquée côté Kazeia-central)
Pour mémoire, **pas une demande de code app** : la règle est **enforced par La règle de **localisation** est enforced par Kazeia-central au déploiement —
Kazeia-central au moment du déploiement** —
- une voix **verrouillée** (exclusive à un profil patient) n'est poussée **que** sur - une voix **verrouillée** (exclusive à un profil patient) n'est poussée **que** sur
la/les tablette(s) où ce profil est déployé ; la/les tablette(s) où ce profil est déployé ;
- seules les voix **globales/non-exclusives** sont déployées sur les tablettes (parc). - seules les voix **globales/non-exclusives** sont déployées sur le parc.
L'app n'a donc **rien à faire** pour cette règle dans le cas **1 patient ↔ 1 tablette** ⚠️ Mais une tablette héberge **plusieurs profils patients** (confirmé). La localisation
(modèle de prod implicite, cf. `CLAUDE.md §9` « serial → label patient ») : l'exclusivité ne suffit donc **pas** : un `.cvps` exclusif à A, présent sur la tablette partagée
= la localisation du `.cvps`, décidée par le PC. (parce que A y est), serait techniquement sélectionnable par B. → l'exclusivité doit
aussi être enforced **on-device** : c'est l'objet du §4, qui devient **requis**.
--- ---
## 4. Adaptation **CONDITIONNELLE** — exclusivité on-device (tablettes multi-patients) ## 4. Adaptation **REQUISE** — exclusivité on-device (tablettes multi-patients)
⚠️ **À trancher (Richard / dev)** : si une tablette héberge **plusieurs profils Décidé : une tablette héberge **plusieurs profils patients**. Une voix exclusive ne doit
patients** (observé sur la tablette de dev : `p_test01`, `p_test02`), alors un `.cvps` donc être **ni sélectionnable, ni audible** par un profil qui n'en est pas propriétaire,
exclusif à A, présent sur la tablette parce que A y est, est **techniquement chargeable même si son `.cvps` est physiquement sur la tablette.
par le profil B** (résolution par nom de fichier, aucun contrôle de propriété). La règle
« déploiement uniquement où le profil est » ne suffit alors plus à garantir l'exclusivité
*à l'intérieur* d'une tablette partagée.
Deux options : ### 4.1 Marqueur de propriété — sidecar `.owner`
- **(a) Mono-patient par tablette (recommandé, défaut supposé)** : aucune adaptation. Kazeia-central pousse, **à côté** du `.cvps`, un fichier sidecar :
L'exclusivité = la localisation du `.cvps`. C'est le modèle « une tablette = un ```
patient » du parc. cosyvoice/<voiceId>.cvps # l'artefact voix (inchangé)
- **(b) Multi-patients par tablette** : l'app doit connaître le **propriétaire** d'une cosyvoice/<voiceId>.owner # présent SI exclusive → contient le profileId propriétaire
voix exclusive et empêcher un autre profil de la sélectionner. Nécessite : ```
- un marqueur de propriété par voix (ex. fichier `cosyvoice/<id>.owner` = profileId, - **Absence de `.owner`** ⇒ voix **généraliste** : sélectionnable par tout profil.
poussé par Kazeia-central, ou colonne `owner_profile_id` à `/voices`) ; - **Présence de `.owner`** ⇒ voix **exclusive** au `profileId` qu'il contient (UTF-8,
- côté picker/`setVoiceId` : masquer/refuser une voix exclusive pour un profil ≠ une ligne). Cohérent avec le modèle « tout par fichier + `adb push` » (comme le `.cvps`).
propriétaire (repli sur la voix par défaut).
> Décision attendue : **(a)** suffit si le parc est mono-patient/tablette. Sinon **(b)** ### 4.2 Enforcement côté app
> et on étend cette spec. Kazeia-central est prêt pour les deux (il connaît déjà - **`/voices`** : exposer une colonne **`owner_profile_id`** (lue depuis `<id>.owner`,
> `locked_profile_id` par voix). vide si généraliste) — pour que le picker filtre.
- **Picker / assignation `Profile.voiceId`** : ne pas proposer/permettre l'assignation
d'une voix exclusive à un profil ≠ propriétaire.
- **Runtime (`KazeiaService.setVoiceId` / `CosyVoiceTtsEngine.ensureVoice`)** : garde-fou
défensif — si le profil actif n'est pas propriétaire d'une voix exclusive demandée,
**repli sur `DEFAULT_VOICE_ID`** (ne jamais faire parler un patient avec la voix
exclusive d'un autre).
> Kazeia-central connaît déjà `locked_profile_id` par voix et poussera/retirera le
> sidecar `.owner` au verrouillage/déverrouillage. L'app n'a qu'à **lire** le sidecar et
> appliquer le filtrage + le garde-fou runtime.
--- ---
@ -116,7 +122,7 @@ Deux options :
| --- | --- | --- | --- | | --- | --- | --- | --- |
| 1 | `/voices` scanne WAV (« à convertir ») **et** `.cvps` (« prête »), col. `cvps_exists` | inventaire voulu | curseur `voicesCursor()` | | 1 | `/voices` scanne WAV (« à convertir ») **et** `.cvps` (« prête »), col. `cvps_exists` | inventaire voulu | curseur `voicesCursor()` |
| 2 | `voices_reload` (rescan + `RELOAD_VOICES`, constante `ACTION_RELOAD_VOICES`) | important | méthode `call()` | | 2 | `voices_reload` (rescan + `RELOAD_VOICES`, constante `ACTION_RELOAD_VOICES`) | important | méthode `call()` |
| 4 | exclusivité on-device | conditionnel (si multi-patients/tablette) | à décider §4 | | 4 | exclusivité on-device : lire `cosyvoice/<id>.owner`, col. `owner_profile_id`, filtrer picker + repli runtime | **requis** (multi-patients) | `voicesCursor()` + picker + `setVoiceId` |
Côté Kazeia-central (déjà fait / à faire, **pas** côté app) : enrôlement, archive Côté Kazeia-central (déjà fait / à faire, **pas** côté app) : enrôlement, archive
chiffrée du WAV, suppression du WAV device, **politique de déploiement verrou-conscient**, chiffrée du WAV, suppression du WAV device, **politique de déploiement verrou-conscient**,