Commit Graph

19 Commits

Author SHA1 Message Date
alf 6640cfd79e feat(voice): catalogue GLOBAL (flotte) + déploiement par tablette
Refonte : une voix n'est plus liée à sa tablette d'enregistrement. Catalogue central
clé voice_id, .ovsp stocké chiffré → déployable sur N tablettes sans ré-enrôler.
- store : voices PK voice_id (+ origin_serial, ovsp_enc) + table voice_deployments
  (many-to-many) + migration de l'ancien schéma (serial,voice_id)→global. Méthodes
  globales + add/remove_deployment, voices_on, deployments_of, store/voice_ovsp_bytes.
- orchestrator : catalog(), deploy_voice/undeploy_voice (scope-conscient), list_for_tablet
  (chargées + chargeables), ingest_voice/sync_admin au catalogue global. Suppr. legacy
  /voices-based (list_voices/prepare/enroll_deploy/sync_device).
- API : GET /voices/catalog, POST /voices/catalog/{vid}/lock|unlock, GET /voices/{serial}
  (chargées+chargeables), POST /voices/{serial}/deploy|undeploy.
- UI : carte Catalogue flotte (visible sans tablette) + par tablette chargées/chargeables
  avec charger/retirer.
- tests réécrits (40/40). Validé live : catalog [], /voices/{serial} chargées+chargeables.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 19:39:00 +02:00
alf cdee5151f4 docs: PROFILE_LANGUAGE_SPEC — langue par profil patient (handoff dev kazeia-android)
Le pipeline est déjà paramétré par langue (context.language → STT/TTS OmniVoice) mais
figé sur 'fr' ; Profile n'a pas de champ langue. Spec : ajouter Profile.language
(déf. fr), l'exposer/accepter dans /profiles, et le câbler dans KazeiaService
(activeProfile.language au lieu du 'fr' codé en dur, ~l.1100/1351). Fiche clinique =
central-only, hors app.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 19:08:37 +02:00
alf 783bca1ba4 feat(voice): UI panneau voix + fix dérivation dossier omnivoice
- web : bouton "🎙 Voix" + vue par tablette : déclencheur auto (armer/désarmer +
  delete_source + statut), voix admin à enrôler (nom/scope/propriétaire/statut/
  consentement + bouton "Synchroniser"), voix déployées (statut/verrou/profils +
  verrouiller/déverrouiller).
- fix : list_voices et enroll_deploy dérivaient le dossier omnivoice depuis
  /voices.wav_path (VIDE post-migration → chemin relatif → 502/échec). Passent
  désormais par _patient_omnivoice_dir (env → wav_path legacy → sonde MODELS_DIR).
- 46/46 ; vérifié live : /voices/{serial} et /admin répondent 200, UI servie.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 11:35:38 +02:00
alf 06b5cda214 feat(voice): ingestion depuis le stockage ADMIN (manifestes record-time §4.2)
Comble le gap : les voix fraîches + leur manifeste (scope/owner/reference_text) vivent
dans Android/data/com.kazeia.admin/files/voix/ (VoiceStorage.kt), pas dans /voices.
- admin_ingest.py : list_manifests (ls + pull .json), delete_admin_voice. Schéma fondé
  sur VoiceStorage.save().
- orchestrator : list_admin_voices, ingest_voice (pull→archive→scope/owner/consentement
  →verrou si exclusive→enroll ASR→deploy selon scope), sync_admin. pending = archivée,
  ni enrôlée ni déployée. exclusive owner absent = verrou_profil_absent.
- _patient_omnivoice_dir robuste : env → wav_path legacy → sonde MODELS_DIR device
  (post-migration /voices.wav_path est VIDE pour une voix déployée).
- store : colonnes scope/owner_name/consent_text_enc (consentement chiffré, PII) +
  migration + set_voice_manifest. consent_text déchiffré dans voice_record/voices.
- autosync → sync_admin (le déclencheur auto ingère depuis l'admin).
- API : GET /voices/{serial}/admin, POST /voices/{serial}/admin/sync.
- enroll = ASR du 16s (migration §6) ; reference_text gardé comme trace de consentement.
- tests +8. 46/46. Validé live : manifeste synthétique → .ovsp déployé sur tablette.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 10:30:58 +02:00
alf 7861abd734 docs(voice): bandeaux migration OmniVoice sur les specs voix
- VOICE_DEPLOYMENT_SPEC : bandeau de substitution .cvps→.ovsp, cosyvoice/→omnivoice/voices/,
  GGUF→OVSP (modèle d'exclusivité §3-4 inchangé, non touché).
- VOICE_ENROLLMENT_SPEC : marqué SUPERSEDED → OmniVoice (make_ovsp + migration doc).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 10:17:54 +02:00
alf adc0904009 feat(voice): migration CosyVoice → OmniVoice (.cvps → .ovsp)
Suit docs/OMNIVOICE_VOICE_MIGRATION.md (app patiente migrée vc17+, 22/06).
- enroll.py : réécriture encodeur OmniVoice (create_voice_clone_prompt → format binaire
  OVSP : magic|T_ref|text_len|tokens[8*T_ref]|ref_text). Cap réf 16s. prepare_wav 24kHz.
- transcribe.py : ASR interne OmniVoice (plus d'openai-whisper, absent de ov_venv).
- transfer.py : omnivoice_dir_for (<MODELS_DIR>/omnivoice/voices), deployed_ovsp,
  push_ovsp, suffixe .ovsp.
- bridge.py : OV_PYTHON (/opt/Kazeia/ov_venv). worker dispatch inchangé.
- orchestrator.py : .ovsp, ovsp_bytes ; verrou d'exclusivité + archive WAV INCHANGÉS.
- store : colonne cvps_bytes → ovsp_bytes (+ migration ALTER RENAME idempotente).
- tests adaptés. 39/39.
- validé live sous ov_venv : enroll → .ovsp format byte-identique à damien.ovsp (réf
  engine) ; auto-ASR FR correcte ; bridge ping/transcribe/enroll chaud OK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 10:15:11 +02:00
Kazeia 46343c9ad5 docs(voice): texte d'enrôlement = phrase de consentement connue + exclusivité dure
- VOICE_ENROLLMENT_SPEC : le texte de référence est CONNU (phrase de consentement
  admin), pas à transcrire → Whisper sort du chemin critique (fallback legacy).
  Fenêtre CV_TARGET_S 15→22s pour que consentement + fin riche entrent dans le .cvps.
- VOICE_DEPLOYMENT_SPEC §4 : exclusivité DURE (consentement/RGPD), capturée
  record-time (scope+owner au manifeste). Gap acté : ingérer depuis le stockage
  ADMIN (pas le provider /voices). Phase 2 = garde-fou on-device. Récap des états.
- enroll.py : CV_TARGET_S=22.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 08:52:37 +02:00
alf 0eb1fd12bc 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>
2026-06-19 20:24:52 +02:00
alf 0924174c7b 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>
2026-06-19 20:21:34 +02:00
alf 342966e867 docs: clarifie VOICE_DEPLOYMENT_SPEC §1 — /voices scanne WAV (à convertir) + cvps (prête)
Recadrage suite échange : pas un blocage fonctionnel ; c'est l'inventaire voulu
(distinguer voix à convertir vs prêtes, garder les prêtes visibles après suppression WAV).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 20:17:43 +02:00
alf 94f8dc0993 docs: VOICE_DEPLOYMENT_SPEC — adaptations app patiente pour déploiement .cvps
Spec livrée au dev kazeia-android (stack figée §11) :
- BLOQUANT : /voices doit inclure cosyvoice/*.cvps (cvps_exists, state=ready, ids∪cvps)
  — sinon une voix dont le WAV est supprimé (workflow voulu) DISPARAÎT de l'inventaire.
- voices_reload (rescan + ACTION_RELOAD_VOICES) après push direct.
- conditionnel : exclusivité on-device si tablettes multi-patients (sinon la règle de
  déploiement côté central suffit en mono-patient/tablette).
Fondé sur le code réel (voicesCursor blind aux .cvps, résolution par nom de fichier,
pas de notion de propriété).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 18:40:50 +02:00
alf 23033384c7 feat(voice): verrou voix↔profil + sync verrou-conscient + déclencheur auto
- store : colonne locked_profile_id (+ migration ALTER idempotente), lock_voice/
  unlock_voice. Verrou = assignation exclusive à un profil ; NULL = voix généraliste.
- orchestrator : list_voices expose locked_profile_id + used_by_profiles (croise
  profile.voice_id) ; sync_device enrôle+déploie les voix en attente, REFUSE de
  déployer une voix verrouillée sur une tablette sans le profil propriétaire.
- autosync : VoiceAutoSync — watcher de connexions armable depuis l'UI (désarmé par
  défaut), statut par tablette, ne fait rien si store verrouillé (PII), delete opt-in.
- API : lock/unlock, POST /{serial}/sync, GET/POST /voices/autosync (déclarées AVANT
  /{serial} pour éviter la capture "autosync" comme serial).
- tests : +8 (verrou, sync verrou-conscient, autosync). 39/39.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 16:20:55 +02:00
alf 4ad513afda feat(voice): orchestration enrôlement — pull/archive/transcribe/enroll/deploy
- store : table voices + WAV chiffré sur disque (archive_voice_wav, voice_wav_bytes,
  transcription chiffrée, marques archived/enrolled/deployed/wav_deleted).
- voice/transfer.py : dérivation du dossier cosyvoice depuis wav_path (device-dépendant),
  pull WAV / push .cvps / delete WAV device (retour vérifié §9).
- voice/orchestrator.py : list_voices (statut par voix), prepare (pull+archive+whisper),
  enroll_deploy (enroll+push + suppression WAV device optionnelle, opt-in).
- API : GET /api/voices/{serial}, /voices/health, POST .../prepare, .../enroll.
- tests : +10 (chemins, archive chiffrée, orchestration via faux adb/bridge). 33/33.
- validé live : jerome (muet) → transcrit FR → .cvps déployé sur tablette + WAV
  archivé chiffré ; WAV device conservé (delete_source=false).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 14:39:29 +02:00
alf 92505a4cac feat(voice): moteur d'enrôlement — worker cv_venv chaud + IPC + transcription
- worker.py : process persistant sous cv_venv (teacher + Whisper chauds), protocole
  JSON-lines. Canal stdout propre (fd1→stderr) pour ne pas corrompre l'IPC avec le
  bruit torch/librosa.
- bridge.py : pont côté API py3.14 (sans torch) — lance/pilote le worker en
  subprocess, jobs sérialisés, démarrage paresseux.
- transcribe.py : Whisper sur le segment PRÉPARÉ (texte == segment enrôlé), défaut
  "small" ("base" transcrit mal le FR, validé).
- Validé live : 1 worker chaud sert ping+transcribe(fr)+enroll, réutilisé (même PID),
  protocole non corrompu (bruit libs → stderr).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 14:24:24 +02:00
alf 8fa7fe4f47 feat(voice): enroll.py — WAV → .cvps (CosyVoice), cœur prouvé sur device réel
- enroll.py : réimplémentation configurable de cv_make_prompt (env CV_REPO/CV_TEACHER),
  teacher chargé une fois (singleton), + préparation WAV obligatoire (mono/16k/~15s)
  car le tokenizer s3 plante >30s et la similarité chute <8s.
- Sortie GGUF arch cosyvoice-prompt-speech, 4 tenseurs aux dtypes exacts.
- Validé live : richard.wav (111s stéréo) → richard.cvps structurellement identique à
  damien.cvps (feat F32[80,750], embedding F32[192,1], tokens I32[375], text I8).
- Hygiène stdout (museler numba/librosa) en prévision de l'IPC worker.

NB : tourne sous cv_venv (torch), PAS sous l'API py3.14 → worker subprocess à venir.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 10:32:11 +02:00
alf 4b38efca4f feat(fleet): fan-out parc — vue d'ensemble + OTA groupé, rapport par device
- fleet.py : fan_out() séquentiel isolant les échecs par tablette (§9), et
  device_overview() (résumé santé partiel, sous-requêtes isolées).
- API : GET /api/fleet/overview (enrichi des labels), POST /api/fleet/update-check,
  POST /api/fleet/update-install (audité).
- UI : bouton « Vue parc » → tableau d'ensemble (état, RAM, MAJ, RAG, profils,
  sessions, crashes) + vérification OTA sur tout le parc.
- tests : +6 (isolation des échecs, overview partiel, endpoint). 25/25.
- validé live : overview + update-check sur tablette réelle (parse_call_bundle OK).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 22:52:11 +02:00
alf 7f999a06f0 feat(store): branchement API — déverrouillage opérateur, labels patients, audit
- API : /api/lock-status, /api/unlock, /api/lock, PUT /api/devices/{serial}/label,
  /api/audit ; /api/devices enrichi du label patient quand le store est déverrouillé.
- db.py thread-safe (check_same_thread=False + RLock) pour le threadpool FastAPI ;
  ajout lock(), device_labels().
- UI : barre de déverrouillage opérateur, affichage 👤 label, édition ✎ du nom.
- Sécurité : labels chiffrés (jamais en clair en base ni dans l'audit), opérations
  journalisées, store re-verrouillable.
- tests : +5 intégration API↔store (dont non-fuite PII dans l'audit). 19/19.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 22:49:43 +02:00
alf 465ddd4e7a feat: store chiffré, dashboard web flotte, accès réseau + durcissements
- store/ : archive clinique chiffrée (sqlite3 + PyNaCl SecretBox/Argon2id),
  mapping serial→label patient, audit. Choix sqlite+PyNaCl plutôt que SQLCipher
  (wheels Linux-only) pour rester facilement installable Mac/Win.
- web/ + montage StaticFiles : dashboard flotte en lecture (état, profils,
  sessions, RAG, updates, crashes), HTML+JS vanilla sans build.
- __main__.py : `python -m kazeia_central`, host/port via env + auth HTTP Basic
  optionnelle (KAZEIA_USER/PASS) pour exposition réseau (0.0.0.0).
- adb : devices() robuste au bruit daemon, réveil/retry du provider (§3.5).
- models : champs non-clés optionnels (résilience flotte hétérogène).
- spec : RAG à plat (miroir ConfigStore.toJson), ajout rag_dump_json, note PIN.
- tests : 14/14 (parsing adb + store chiffré).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 22:41:55 +02:00
Kazeia Team 311236cecc chore: socle Kazeia-central (étapes 0-1 lecture) + spec provider RPC
- CLAUDE.md fondateur (archi, contrat protocole §4, décisions figées)
- docs/PROVIDER_RPC_SPEC.md : extensions call() base64-JSON à livrer à l'app
  patiente (dump/apply config·presets·profils·RAG, export conversations file-drop
  chiffré, handshake capabilities, token d'accès) — 100% additif, compat FROZEN
- kazeia_central/adb : wrapper adb (découverte, content query/call, push/pull)
- kazeia_central/provider : client typé pydantic des endpoints provider existants
  (lecture) ; écritures/texte riche en NotImplemented explicite (dépend du RPC)
- kazeia_central/api : squelette FastAPI (lecture de flotte)
- tests/ : parsing content query + coercion pydantic

Repo autonome sibling de /opt/Kazeia et /opt/Kazeia-engine.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 14:51:41 +02:00