docs: VOICE_DEPLOYMENT_SPEC §4 — exclusivité via Profile.voiceId, suppression sidecar .owner

L'exclusivité s'appuie sur le mapping voix↔profil EXISTANT, pas un artefact parallèle :
l'app joue toujours activeProfile.voiceId (playback déjà exclusif par profil). Central =
autorité d'assignation (verrouiller = assigner Profile.voiceId au propriétaire, jamais
ailleurs + déploiement ciblé). Aucun code app requis (garde-fou picker optionnel sans fichier).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
alf 2026-06-19 20:24:52 +02:00
parent 0924174c7b
commit 0eb1fd12bc
1 changed files with 30 additions and 31 deletions

View File

@ -77,42 +77,40 @@ La règle de **localisation** est enforced par Kazeia-central au déploiement
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 le parc. - seules les voix **globales/non-exclusives** sont déployées sur le parc.
⚠️ Mais une tablette héberge **plusieurs profils patients** (confirmé). La localisation ⚠️ Une tablette héberge **plusieurs profils patients** (confirmé). Mais l'exclusivité au
ne suffit donc **pas** : un `.cvps` exclusif à A, présent sur la tablette partagée playback est **déjà garantie** par le binding existant : l'app sélectionne toujours la
(parce que A y est), serait techniquement sélectionnable par B. → l'exclusivité doit voix du **profil actif** (`KazeiaService` : `activeProfile.voiceId ?: DEFAULT_VOICE_ID`).
aussi être enforced **on-device** : c'est l'objet du §4, qui devient **requis**. Un patient B n'utilise donc une voix exclusive à A **que si** `B.voiceId` pointe dessus.
Le point de contrôle est donc l'**assignation de `Profile.voiceId`**, pas un marqueur
parallèle. Voir §4 — l'exclusivité s'appuie sur le mapping voix↔profil existant, sans
nouvel artefact app.
--- ---
## 4. Adaptation **REQUISE** — exclusivité on-device (tablettes multi-patients) ## 4. Exclusivité — via le mapping `Profile.voiceId` existant (pas de nouvel artefact)
Décidé : une tablette héberge **plusieurs profils patients**. Une voix exclusive ne doit L'exclusivité s'appuie sur le binding **voix↔profil déjà présent** (`Profile.voiceId`),
donc être **ni sélectionnable, ni audible** par un profil qui n'en est pas propriétaire, pas sur un marqueur parallèle. Kazeia-central est l'**autorité d'assignation** (il a le
même si son `.cvps` est physiquement sur la tablette. CRUD profils via le provider) :
### 4.1 Marqueur de propriété — sidecar `.owner` - **Verrouiller** une voix sur un patient = central assigne `Profile.voiceId = <voix>` au
Kazeia-central pousse, **à côté** du `.cvps`, un fichier sidecar : profil propriétaire, **et ne l'assigne à aucun autre profil** (sur la tablette ni
``` ailleurs). C'est ça l'exclusivité.
cosyvoice/<voiceId>.cvps # l'artefact voix (inchangé) - **Déploiement** : central pousse le `.cvps` uniquement sur la/les tablette(s) du
cosyvoice/<voiceId>.owner # présent SI exclusive → contient le profileId propriétaire propriétaire (cf. §3).
``` - **Playback** : déjà exclusif — l'app n'utilise que `activeProfile.voiceId`.
- **Absence de `.owner`** ⇒ voix **généraliste** : sélectionnable par tout profil.
- **Présence de `.owner`** ⇒ voix **exclusive** au `profileId` qu'il contient (UTF-8,
une ligne). Cohérent avec le modèle « tout par fichier + `adb push` » (comme le `.cvps`).
### 4.2 Enforcement côté app ### Côté app — rien de nouveau requis pour l'exclusivité
- **`/voices`** : exposer une colonne **`owner_profile_id`** (lue depuis `<id>.owner`, Le playback par profil actif suffit. **Optionnel** (garde-fou de gouvernance, si le
vide si généraliste) — pour que le picker filtre. picker admin on-device autorise la (ré)assignation des voix) : l'app peut, à partir de
- **Picker / assignation `Profile.voiceId`** : ne pas proposer/permettre l'assignation la liste des profils qu'elle a déjà, signaler qu'une voix est **déjà assignée à un autre
d'une voix exclusive à un profil ≠ propriétaire. profil** et déconseiller/empêcher de la réassigner — **sans aucun fichier ni colonne
- **Runtime (`KazeiaService.setVoiceId` / `CosyVoiceTtsEngine.ensureVoice`)** : garde-fou supplémentaire**, juste en croisant `Profile.voiceId`. À implémenter seulement si
défensif — si le profil actif n'est pas propriétaire d'une voix exclusive demandée, l'assignation reste possible on-device ; sinon l'autorité centrale suffit.
**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 > Kazeia-central garde `locked_profile_id` par voix **en interne** (sa politique) et le
> sidecar `.owner` au verrouillage/déverrouillage. L'app n'a qu'à **lire** le sidecar et > matérialise par l'assignation `Profile.voiceId` + la localisation du `.cvps`. Aucun
> appliquer le filtrage + le garde-fou runtime. > sidecar `.owner`, aucune ontologie d'exclusivité à dupliquer dans l'app.
--- ---
@ -122,11 +120,12 @@ cosyvoice/<voiceId>.owner # présent SI exclusive → contient le profileId
| --- | --- | --- | --- | | --- | --- | --- | --- |
| 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 : lire `cosyvoice/<id>.owner`, col. `owner_profile_id`, filtrer picker + repli runtime | **requis** (multi-patients) | `voicesCursor()` + picker + `setVoiceId` | | 4 | exclusivité = via `Profile.voiceId` existant + autorité d'assignation centrale | **aucun code app** (garde-fou picker optionnel, sans artefact) | central / §4 |
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**,
et la **distribution flotte** (pousser les voix globales sur le parc, les exclusives chez **autorité d'assignation** (`Profile.voiceId` du propriétaire, jamais ailleurs pour une
voix exclusive), et la **distribution flotte** (voix globales sur le parc, exclusives chez
leur patient) — en cours. leur patient) — en cours.
--- ---