6 Commits

Author SHA1 Message Date
B.D. Murphy
bf83bb35cc Merge minieq/main: Apache-2.0 LICENSE, NOTICE and SPDX headers
The remote had one commit this branch did not (03f8a85), which adds the
Apache-2.0 LICENSE, the NOTICE and SPDX headers to every .py and .sh
file. The merge was clean - the headers sit at the top of each file and
my changes are further down.

The new files this branch added (languages.py, translations.py, the
cluster package, the new tests and scripts) were written after the
merge base, so the licence commit never saw them. They now carry the
same header, so the convention holds across the whole tree.

Nothing else changed. 883 tests passed / 8 failed - the same 8
pre-existing failures.
2026-11-06 17:12:24 -08:00
umbra audio
03f8a858dd Add Apache-2.0 LICENSE, NOTICE, and SPDX headers 2026-10-18 19:01:04 -07:00
B.D. Murphy
80d8fb4d7e Untrack the 15.7 MB reference chain; keep the matrix and fix RESTORE.md
Same reasoning as the lab renders: the WAVs are copies of files that live in
~/Documents/Audacity4/ and are mirrored at
~/.umbra/library/author_voice_matrix/20261009T191636Z/. Git does not need a
third copy to be safe.

What stays is the part that is not duplicated anywhere else in a form git can
restore: author_v2.pt and author_v1.pt, 62 KB, the actual voice identity.
manifest.json keeps a SHA-256 for each WAV, so any copy can be verified against
what was captured rather than trusted.

RESTORE.md was wrong after this change and is fixed. It listed the WAVs as
files of the backup and told you to `cp BDMurphy_Voice_NOBG_v2.wav` from that
directory, which no longer works. It now states the WAVs are untracked and where
they actually are, and adds the verified matrix->WAV decode as a first-class
restore path, because that is the point: the WAV is derived from the matrix, not
a prerequisite for it.

Note the asymmetry this leaves in place, deliberately. Loss of the matrix means
losing the captured voice, and no WAV in existence could reconstruct the exact
one. Loss of the WAVs costs nothing, because they can be rebuilt from the
matrix. So the durable, unreproducible artifact is the thing that stays tracked.
2026-10-17 01:06:03 -07:00
B.D. Murphy
6c3f385b73 Untrack the 55 voice-lab renders; the .pt matrix stays
.gitignore alone does not untrack anything already committed, so the previous
commit stopped future tracking but left all 55 WAVs in the index and in history.
This removes them from the tree so a fresh clone no longer carries 25 MB of
audio.

The files themselves are untouched: moved to tmp_out/author_lab_wav/ and
tmp_out/author_lab_wav_cliff/, which is git-ignored, so the samples are still
listenable on this machine. They are regenerable at any time from the tracked
scripts:

  backup/author_voice_lab/scripts/voice_lab.py     # the 45-sample sweep
  backup/author_voice_lab/scripts/verify_cliff.py  # the 10 cliff samples

Nothing load-bearing was removed. Still tracked:
  backup/author_voice_matrix/**/*.pt        author_v1.pt + author_v2.pt (62 KB)
  backup/author_voice_matrix/manifest.json   SHA-256 + provenance per file
  backup/author_voice_matrix/RESTORE.md
  backup/author_voice_lab/FINDINGS.md, *.json, scripts/

The .pt files stay tracked on purpose: they are 62 KB, they are the actual
voice identity, and the matrix is the thing that must survive. The WAVs are
derived from it.

Caveat worth stating: the 25 MB still exists in git history from the earlier
commit, so the repository object store does not shrink until that commit is
garbage-collected. A future push carries them over the wire once; after that
no new clone pays for them. Rewriting published history to purge them was
deliberately not attempted.
2026-10-17 01:04:50 -07:00
B.D. Murphy
e810b49d2a Add a voice-regeneration queue with inline per-speaker state
Reviewing speaker exceptions meant clicking Regenerate, watching a modal that
blocked the page, then navigating back to find out what happened. Worse, the
route refused a second request with a 409 while one was running, and
/api/generation/status exposed only a single global `active` flag - so there
was nowhere to put a second speaker and nothing to render per row.

Now: POST returns as soon as the task is captured, the row shows a status tag
(queued / working / done / failed), and further speakers can be queued while one
generates. No navigation, no modal.

umbraaudio/voices/voice_queue.py
- Ordered, serial queue. TTS is strictly serial here, so items run one at a
  time in enqueue order; the second speaker waits rather than contending.
- Per-item state, persisted to voice_queue.json. An item left `running` by a
  crash is marked `error` on load - the honest outcome, since silently
  re-running could write audio nobody asked for twice.
- Enqueueing a speaker that is already pending is a no-op returning the
  existing state, so a double-click cannot queue the same work twice.
- `cancel` withdraws a queued item but refuses one already running.
- Status vocabulary (queued/running/done/error) is deliberately small and
  stable: it is what the controller will map onto node task states.

umbraaudio/web/voice_queue_routes.py - POST /api/voices/queue (capture the task),
GET /api/voices/queue (report per-item state), POST .../{speaker}/cancel. Kept
out of the hot routes.py, following name_token_routes.

/api/speakers/regenerate-voice is now a thin shim onto the queue so a
bookmarked call still works and the two paths cannot race. Net -24 lines in
routes.py; the blocking implementation is gone, not commented out.

The UI's modal, its 409 handler and the page-load resume handshake were removed
(67 lines) rather than left dormant - queue state is re-read from the snapshot
on load and after each HTMX row swap, so it survives navigation.

Unknown speaker names are rejected with a 404 at enqueue time. Previously they
reached the profile builder and surfaced as a bare KeyError('primary_name') in
the failed tag.

Verified live: two speakers enqueued back to back, first ran while the second
stayed queued, then the second started automatically on completion; both wrote
real ref WAVs. 16 new tests. Full suite 665 passed, 8 pre-existing failures.
2026-10-09 19:45:46 -07:00
B.D. Murphy
0c9bea72e0 Add enterprise/infra tooling and repoint Qwen3-TTS submodule to internal fork
Two groups of changes, committed together as the follow-up to the initial push:

1. .gitmodules — submodule Qwen3-TTS now points at the internal Gitea fork
   (ssh://git@mini-eq:2222/umbragit/Qwen3-TTS.git) instead of upstream GitHub,
   so a clone actually gets the UmbraAudio branding/integration commit
   012e452 rather than vanilla upstream.

2. New infra/ tooling for the cross-machine work in spec 21: agent_bridge,
   remote_credential_broker (Keychain-backed, secrets read at runtime and
   generated via read_secret/random_secret — no hardcoded credentials), the
   enterprise bootstrap scripts, and the oldbox SSH/auth prompts. Plus
   backup/ restore scripts, two specs (18 UmbraTrans, 19 enterprise control
   plane), umbraaudio/voices/lmstudio_gate.py, and in-flight edits to the
   discovery/attribution/profiling modules, config, chapter_builder, emotion,
   pronunciation, qwen_tts and routes.

Also adds *.db to .gitignore: SQLite project databases are runtime state and no
.db file was tracked before.

Verified before staging: .env's only change is UMBRA_TTS__QWEN_TTS_HELPER_URL and
the committed copy has zero secret-looking values; the broker ships only
config.example.json.
2026-10-06 06:57:41 -07:00