Skip to content

environments and packages: provisioning layout, locks, registry, and audio packages #11

Description

@fyang0507

Context

TRANSCRIBE_DESIGN_HANDOFF.md opens the design work as seven
items. This issue takes the provisioning half of them — handoff open item 3 (the
environment layout, concretely), the transport half of item 2 (how a stage runs in an
environment that is not the tool's own), and everything audio packages needs to exist.
#12 takes the backend half.
The seam between the two is stated below and is worth reading before either starts.

This one comes first because it can invalidate the other's layout, not because it is
foundational in the abstract. The handoff's suggested sequence puts one uv resolution
attempt at step 1 for exactly that reason.

The naming contract is VOCABULARY.md §"Packages and environments"; the
command surface is TRANSCRIBE_CONTRACT.md §0 and §6; the output
shapes to diff against are TRANSCRIBE_HAPPY_PATH.md §1.3 and §5.
None of it is built.

The experiment that comes first

Question: can vibevoice-asr-7b, qwen3-forcedaligner, and firered-asr2s share one
locked Python environment? VOCABULARY declares three provisioned environments as the floor
and marks this joint resolution unverified. If it fails, torch splits, three
environments becomes four, and the plan's environment-spanning claims change.

The evidence already on this machine says the answer is probably no, and says why. The three
source checkouts each carry their own .venv, and those venvs — which are what actually
produced every recorded figure — disagree on a major version:

Checkout torch transformers huggingface_hub numpy
model_tests/firered/FireRedASR2S/.venv 2.10.0 5.1.0 1.27.0 2.4.2
model_tests/forced_aligner/.venv 2.13.0 4.57.6 0.36.2 2.4.6
model_tests/vibevoice/VibeVoice/.venv 2.13.0 4.57.6 0.36.2 2.4.6

(Read off *.dist-info under each venv's site-packages. The aligner venv also carries
qwen_asr==0.0.6 and qwen_omni_utils==0.0.9; VibeVoice carries diffusers==0.39.0.)

model_tests/vibevoice/VibeVoice/pyproject.toml declares transformers>=4.51.3,<5.0.0, so
FireRed's as-built transformers is outside VibeVoice's declared range. A single lock
satisfying both requires one of them to move, and moving either means re-running its recorded
evidence before any figure in model_tests/ still describes it.

Also worth knowing before trusting any tracked requirement file:
model_tests/firered/FireRedASR2S/requirements.txt pins torch==2.1.0+cu118 and
transformers==4.51.3 against a CUDA index. Neither can be what ran on Apple Silicon, and
neither matches the venv above. The as-built venvs are the artifact; the upstream
requirement files are not.
Do not lock from them.

So the experiment is not "attempt one resolution" but three questions in order:

  1. Does a joint uv resolution of the three as-built dependency sets exist at all? Resolve
    only; do not install.
  2. If it does not, is the conflict genuinely FireRed's transformers 5.x requirement, or did
    the venv drift upward from a floor that 4.57.6 also satisfies? This decides whether the
    split is forced or incidental, and a forced split is a different registry than an
    incidental one.
  3. If FireRed genuinely needs transformers 5.x, what is the smallest split — torch plus
    torch-firered, or something that groups differently — and what does that do to
    environments_spanned for a firered + word_timestamps request?

Record the outcome as an evidence artifact, whichever way it goes, and cite the resolver
output. "They conflict" without the resolver's own message is not a result. A negative
result here is the cheap one; discovering it during implementation is not.

What is already decided

Do not redesign these; they have stated reasons in VOCABULARY and the contract.

  • One root, AUDIO_PROCESSING_MODEL_CACHE when set, else the per-platform cache dir
    src/audio_cli/vad.py already resolves. Nothing provisioned beside a source checkout.
  • registry.json, models/, envs/{mlx,torch,swift}/ is the declared tree.
  • Locks live in this repository. pull materializes a lock; it never resolves "latest".
    This is what keeps the mlx-audio private batched API at the one version the source-hash
    guard expects.
  • Provisioning is explicit and fails closed. pull is the only thing that downloads
    weights, builds the Swift product, or applies the VibeVoice patch. A transcription request
    never triggers any of them — it exits 3 with the pull line that would fix it.
  • Only small hash-pinned artifacts auto-fetch, the way vad.py already does, digest
    check and atomic rename included.
  • remove is reference-counted; purge reads the registry rather than shell history and
    reports reclaimable bytes first; both state that the HF cache may be shared.
  • Hub weights stay in the HF cache. The registry records which revisions this tool
    materialized, rather than duplicating a snapshot.
  • fix is a runnable command wherever a configuration exists that would work.

Design questions this issue answers

  1. registry.json's schema. Unspecified today. It has to serve five readers with
    different needs: list (bytes, license, used_by_stacks), verify (digests, the
    mlx-audio source hash, applied patches, whether the Swift product runs), remove
    (reference counts, materialized Hub revisions), purge (everything, from a session with
    no history), and run's exit-3 check (is this package present, at this revision). Decide
    whether it is one document or a document plus per-package sidecars, and how it survives a
    crashed pull — a half-written registry that reads as provisioned is the failure mode
    that turns exit 3 into exit 1.
  2. How an environment is created and located. uv venv from a tracked lock is the
    obvious mechanism (and this repo's convention), but the questions are: one lock file per
    environment or one project with extras; how the tool finds each environment's interpreter
    afterwards; what happens when the tool is installed via uv tool install and therefore
    has no project checkout to read locks from — the locks must ship in the wheel or be
    fetched, and pyproject.toml's [tool.hatch.build] include list currently ships neither.
  3. Non-Python environments. swift has no interpreter: it is a build product plus one
    Core ML package, and swift may be absent (doctor reports that, does not fail).
    speaker-diarization-coreml is a separate package from fluidaudio in the declared
    tables and must stay one.
  4. The VibeVoice patch. logits_to_keep is tracked under
    model_tests/benchmark/patches/. Decide where a patch lives in the shipped tool, how
    verify knows it is applied, and what --repair does when it is not.
  5. Cross-environment invocation. Not the stage ordering — that is
    #12 — but the mechanism a
    torch stage is invoked through from a core-environment process, and what crosses that
    boundary. The recorded evidence used fresh subprocesses per stage
    (run_interview_pipeline.py), so a persistent worker is outside what was measured and
    needs its own justification. Whatever it is, it has to make "no two model stages resident
    at once" enforceable rather than aspirational.
  6. audio doctor. Tool version and path, ffmpeg/ffprobe/swift/uv presence, platform,
    memory, disk, resolved root, per-environment and per-package state.
  7. Testing this without downloading 32 GB. The registry, reference counting, purge --dry-run, exit 3, and the integrity-failure path are all testable against a fabricated
    root. Decide what the fake root looks like and which parts genuinely require a live pull
    (live marker already exists in pyproject.toml).

The seam with #12

One table, two owners, and it must not become two tables.

Acceptance

  • The torch resolution question is answered by a recorded resolver run, and VOCABULARY's
    environment table plus its "unverified" note are updated to match — including the split and
    its reason if it splits.
  • registry.json has a written schema, and a crashed pull cannot leave a root that reads
    as provisioned.
  • audio packages list | pull | verify | remove | purge | path and audio doctor produce the
    key sets in TRANSCRIBE_HAPPY_PATH §1.3 and §5 — including environments_kept_reason,
    hub_cache_note, unsized_packages, and the non-blocking license_unreviewed warning.
  • remove on vibevoice-asr-7b keeps torch while the aligner and firered-asr2s need it,
    and says so.
  • run before pull exits 3 with packages_not_provisioned and a fix line that, run
    verbatim, provisions exactly the missing set. A digest/size/revision mismatch exits 3 with
    package_integrity_failed and --repair.
  • uv run --extra dev pytest stays green; the enhance render-from-original path is not
    regressed.

Traps

  • A claim about a package must cite the artifact, not a summary. The handoff's process
    rule exists because three documents agreed on something no artifact supported. The
    requirements-file-versus-venv divergence above is the same failure shape, one layer down.
  • Do not add registry structure nobody dispatches on. The capabilities report shed four
    nested objects for exactly this reason. If no command branches on a field, it is a note.
  • Do not describe machinery that does not exist. If verify declares it checks applied
    patches, write the test that fails when a patch is reverted, and check that it can fail.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions