Commit Graph
2 Commits
Author SHA1 Message Date
Palash Debnath 4500dcb6b4 feat(persona): .ovsvoice build/parse core + embed_watermark(force=) (#29 slice A) (#460)
Extends the merged persona-bundle nucleus (constants, normalize_spdx,
build_manifest, build_consent_json) with the model-coupled core that the
export/import router (next slice) will sit on:

- `build_persona_bundle(profile, *, license_spdx, tags, include_reference,
  embed_fn, …)` → assembles the .ovsvoice ZIP in memory: a watermarked
  preview.wav (24 kHz mono 16-bit, downmixed + resampled + trimmed ≤8 s),
  manifest.json, a legacy-shaped metadata.json (so an older OmniVoice can still
  import the ref audio), optional consent.json, and the raw ref/locked/consent
  members unless include_reference=False (privacy / preview-only, A12). Raises
  NoPreviewSource (router → 503) when no source clip is readable (A2-A5).
- `parse_persona_bundle(bytes)` → validates the ZIP, prefers manifest.json and
  falls back to legacy metadata.json, resolves audio members by prefix
  (last-wins, B9; member names never build paths — zip-slip safe), normalizes
  the SPDX id, flags preview-only / future-schema_version. Raises
  BundleError(400|413) for B1-B11. No DB, no file writes.
- `ParsedPersona` dataclass with `extract_member(prefix, dest_path)` — the
  router derives dest_path from the server-generated id, never the member name.
- `embed_watermark(..., *, force=False)`: keyword-only flag that bypasses the
  user's invisible-watermark preference for the mandatory persona preview, but
  still no-ops without AudioSeal. All existing positional call sites are
  unchanged (default force=False) — default cross-platform behaviour identical.

All heavy imports (torch/torchaudio/watermark/audio_io) are lazy so the module
stays model-free at collection (avoids the local torch/Triton segfault).

tests/test_persona_bundle.py: +31 cases — parse validation (manifest/legacy
selection, preview-only, future-schema, missing/malformed/no-audio → 400,
oversize → 413, bad-SPDX normalize, last-wins dup, advisory consent), build
round-trip (identity fields, metadata sibling, no-source → NoPreviewSource,
include_reference=False, stereo/off-rate downmix+resample), and the force=
unit (D1/D3). 25 pure cases pass locally; the 6 torchaudio-coupled cases run on
CI (local torch+pytest segfault is pre-existing). CJK guard green.
2026-06-14 17:55:17 +05:30
Palash DebnathandClaude Opus 4.8 4a75d694e6 feat(persona): .ovsvoice manifest + SPDX + consent core (#29 / parity §R3 G1, pure) (#453)
The model-free nucleus of the portable .ovsvoice persona-bundle format: format
constants, SPDX normalization, and the manifest/consent builders — all pure (no
torch, no I/O), fully locally testable. The audio preview + ZIP pack/unpack +
watermark `force=` param + router + frontend are follow-on slices.

- Constants: OVSVOICE_FORMAT/SCHEMA_VERSION, MAX_BUNDLE_BYTES (100 MB),
  DEFAULT_LICENSE (`LicenseRef-OmniVoice-Personal`), the SPDX allowlist.
- `normalize_spdx()` — membership + `LicenseRef-` prefix; junk/None/injection →
  DEFAULT_LICENSE, never raises/400s. No regex over the SPDX string (CodeQL-clean).
- `build_manifest()` — mirrors the legacy `_bundle_metadata` persona fields into
  the manifest + format discriminator + normalized license + tags + engine /
  preview / members blocks. seed/vd_states pass through (None-safe; vd_states is
  a JSON string, never re-parsed). `BundleError(status, detail)` for the router.
- `build_consent_json()` — designed-synthetic for `kind='design'`, self-recorded
  for an attested clone, None when nothing to attest; `recorded_at` coerced.
  Fields are advisory by design — real verification needs the actual consent
  audio member, so verified-own-voice can't be forged by editing a manifest.

Tests: 14 cases (SPDX allowlist/prefix/junk/strip, manifest schema + field
mirror + None-passthrough + bad-license-normalized, consent design/clone/none/
coerce). Backend pytest green; CJK guard green.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 13:57:39 +05:30