Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions pmoves/docs/AGENTS/AGNOTE4482PHI.t1.md
Original file line number Diff line number Diff line change
Expand Up @@ -1626,6 +1626,10 @@ edo for slice 6 (4th-commit pattern). (2) functional: pmoves/tools/creator-colla

<!-- GRAPHITI_MARK: Mavis::VALIDATE-DOCKERFILE-PATHS-RATCHET-2026-08-05 -->

- `2026-08-07T15:45:00Z` CLAIM+RELEASE `5090-CLAUDE` scope: **VOICE-SAMPLER FOUNDATIONS shipped + creator-pipeline H3 lane landed (operator-directed "ok to proceed yes" / "lots landed... update lanes claim new").** RELEASED: (1) PMOVES-Creator#10 MERGED (MiniMax H3 Ultra blueprints + reviewed installers + model matrix; gitlink promoted ae174f7f in #2471); Maestro updated 1.5.5->1.6.0 (H3 Omni reference conditioning). (2) Monorepo PR #2471 (armed): OmniVoice ref-voice catalog bind live (healthz catalog:true), media-audio diarization stack repaired on the DOCUMENTED pyannote stack (pyannote 3.4 + hub 0.36 + torch 2.7.1+cu128 + transformers<5 LOAD-BEARING; pyannote-4/torchcodec path aborts on import - 202-restart crash loop reproduced in isolation), env.shared empty-TLS-var trust fix scoped to media-audio, VOICE_SAMPLER_SPEC.md (media->diarize->audition->pub-gate->publish; Voice Vault room app). (3) PR #2455 (up-juicefs-recreate) + #2458 (PMOVES-pinokio submodule, hardened branch; upstream 1307/209 drift = manual CRITICAL merge queued). (4) SEAP: 2 deep-dive videos transcribed (whisper), 10-motif Art Direction Brief in room inbox; Open Notebook stale-token healed (recreate), SEAP notebook created + 10 sources ingested w/ embeddings (viewable :8503). (5) Reference stock staged rooms/darkxside/creator/references/ (11 objects + 2 transcripts). GATED: diarization final green on operator HF-token rotation (both pyannote gates accepted; whoami 401 on current token). CLAIMED (new lanes, 5090): voice-sampler worker build (per spec), SEAP-answers merge into skeleton (Perplexity DAO blueprint + grant template -> counselor questions), DARKXSIDE persona optimal-config review (GLM 5.2 vs KiloClaw via TensorZero), Veo-blueprints reformat -> H3/Maestro format. Cross-node: Z890 juicefs+maker-suite lane flagged; Crush-on-SPARK browser use-agent available for PR review; a2ui-renderer worktree has a WEDGED interactive rebase (commit editor open since ~09:49, holds worktree lock - Crush/operator attention). Three-body: delivery=5090-CLAUDE, control=operator directives + PR gates + pub-gate on art brief, memory=this trail + project_creator_pipeline_h3_lane + vision_pmoves_sdk_and_node_lanes + vision_quantum_text_and_identity_mix. agent_signature: `ACK::5090-CLAUDE::VOICE-SAMPLER-FOUNDATIONS-2026-08-07`.

<!-- GRAPHITI_MARK: 5090-CLAUDE::VOICE-SAMPLER-FOUNDATIONS::2026-08-07 -->
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

## Claim-register verification sweep — 4090 (2026-08-07)

- `2026-08-07T00:30:00Z` NOTE `4090-claude (field)` scope: **Verification only — no lanes closed, no RELEASE written on another agent's behalf.** The register carries **121 CLAIM lines against 115 RELEASE lines**. Seven CLAIMs dated 2026-07-30 onward have no matching RELEASE. For each I checked whether the work it cites is actually merged, so the owning agent can close its own lanes from evidence rather than memory. **Village Rule: these are Mavis's and CRUSH's lanes to sign off, not mine.**
Expand Down
18 changes: 15 additions & 3 deletions pmoves/docs/creator/VOICE_SAMPLER_SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Every piece of the cloning stack exists and none of them connect:

## Flow

```
```text
[1] SOURCE operator picks a media item (PMOVES.YT video id / Jellyfin item id)
[2] ANALYZE media-audio /analyze {analysis_type: diarization} → speaker turns
[3] AUDITION segments grouped per speaker; operator listens, tags "this is me"
Expand All @@ -26,7 +26,7 @@ Every piece of the cloning stack exists and none of them connect:
a. JuiceFS rooms/<room>/creator/references/voice/<persona>/<clip>.wav
b. OmniVoice catalog services/creator-operator/voices/<catalog-id>.wav
c. flute /v1/voice/clone/register {persona_id, sample}
[6] ANNOUNCE NATS voice.reference.approved.v1 {persona_id, catalog_id, source, chit_sig}
[6] ANNOUNCE NATS voice.reference.published.v1 {persona_id, catalog_id, source, chit_sig}
```

## Components
Expand All @@ -41,7 +41,19 @@ Every piece of the cloning stack exists and none of them connect:
Renders in OpenRoom via the room adapter; approval posts the pub-gate event.
3. **NATS subjects** (register in catalog):
- `voice.sample.candidates.v1` — sampler → room (candidates ready)
- `voice.reference.approved.v1` — room → sampler (owner approved)
- `voice.reference.approved.v1` — room → sampler (owner approved; input only)
- `voice.reference.published.v1` — sampler → downstream (emitted only after
all writes succeed). Approval and publication are separate subjects so a
replayed approval can't masquerade as a completed publish; re-publishing
the same `catalog_id` overwrites the same `<catalog-id>.wav` (idempotent,
not duplicative).
4. **Identifier contract** (publication boundary): `room`, `persona_id`, and
`catalog_id` must be single path components — no `/`, `\`, or `..`; the
sampler *rejects* (fail-closed), it does not sanitize. The OmniVoice catalog
file becomes `voices/<catalog-id>.wav`, whose basename is the `voice_ref` id
OmniVoice resolves (and the future `pmoves_core.voice_profiles.name` /
`ref_audio_path` values). Same contract documented in
`pmoves/services/creator-operator/voices/README.md`.

## Gates

Expand Down
6 changes: 6 additions & 0 deletions pmoves/services/creator-operator/voices/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ Drop approved reference clips here (wav/m4a/mp3). The filename (without
extension) is the catalog id passed as `voice_ref` → `ref_audio` to
POST /synthesize — the server resolves ids under this dir only, never raw paths.

Naming contract: a catalog id is a single path component — no `/`, `\`, or
`..`. The voice-sampler worker enforces this at the publication boundary
(rejects, doesn't sanitize) and writes `<catalog-id>.wav`; the same id is what
`pmoves_core.voice_profiles.name` / `ref_audio_path` will reference once the
DB-backed profile registry lands. Manual drops should follow the same rule.

Population path (set, not preset): media source (PMOVES.YT / Jellyfin) →
media-audio diarization → operator approves segments in the room pub-gate →
clips land here + in JuiceFS `rooms/<room>/creator/references/voice/`.
Expand Down
2 changes: 1 addition & 1 deletion pmoves/services/media-audio/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ RUN --mount=type=cache,target=/root/.cache/pip \
# lock-vs-requirements.txt path above — the x86_64 lock does not pin pyannote, which
# would leave diarization broken on the GPU image (Codex #2181 P1). Full pip-compile
# regeneration of the lock with pyannote is the ideal follow-up.
RUN pip install --no-cache-dir "pyannote.audio>=3.3.0" "pydub>=0.25.1"
RUN pip install --no-cache-dir "pyannote.audio>=3.3.0,<4" "pydub>=0.25.1"
COPY pmoves/services/media-audio/ ./
# Shared envelope helper + contract registry. events.py resolves its contracts
# directory by probing /app/contracts first (see events._contracts_dir), so the
Expand Down
5 changes: 4 additions & 1 deletion pmoves/services/media-audio/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ librosa>=0.10.0
soundfile>=0.12.0
pydub>=0.25.1
# Speaker diarization — loaded only when HF_TOKEN is set (pyannote 3.1 is gated).
pyannote.audio>=3.3.0
# <4 is load-bearing: pyannote 4.x hard-requires torchcodec>=0.7, which aborts
# on import (std::length_error) in this image — reproduced as a 202-restart
# crash loop. The 3.4 stack is the documented, working path.
pyannote.audio>=3.3.0,<4

# API framework
fastapi==0.114.2
Expand Down
13 changes: 13 additions & 0 deletions pmoves/services/voice-sampler/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ def _juicefs():
return _s3(JUICEFS_ENDPOINT, JUICEFS_ACCESS_KEY, JUICEFS_SECRET_KEY)


def _safe_ident(val: str) -> bool:
"""True when val is usable as a single path component — no separators, no traversal."""
return bool(val) and not any(c in val for c in ("/", "\\", ".."))


# ── SOURCE → ANALYZE → AUDITION ─────────────────────────────────────────────
class SampleRequest(BaseModel):
bucket: str
Expand Down Expand Up @@ -224,6 +229,14 @@ async def _handle_approval(msg) -> None:
logger.warning("approval REFUSED: clip key traversal attempt")
published_total.labels(status="rejected").inc()
return
# room/persona/catalog_id become JuiceFS key components and the OmniVoice
# catalog filename (<catalog_id>.wav on a host bind) — same fail-closed rule
# as the /sample validator, but this path takes them from a NATS payload.
catalog_id = payload.get("catalog_id") or persona
if not all(_safe_ident(v) for v in (room, persona, catalog_id)):
logger.warning("approval REFUSED: identifier contains path separators")
published_total.labels(status="rejected").inc()
return
try:
refs = await asyncio.to_thread(_publish_references, room, persona, clips, payload)
published_total.labels(status="ok").inc()
Expand Down
Loading