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.
The review UI's language dropdown did nothing: every selection kept
rendering the English text. Two independent causes, both fixed.
1. The translations were never being read. The pipeline's
`utterance_translations` table holds one row in Deal Hunter, and that
row stores the *English* text under a `zh-Hans` code. The real
translations live in the on-disk sample files under
`samples/translations/`. `umbraaudio/translations.py` now resolves both
sources and merges them by (chapter_id, utterance_index): the table wins
where it holds a real translation, the sample files fill the gaps. A
candidate whose text is identical to the source is a placeholder, not a
translation, so it is ignored - otherwise the page would show English
under a Chinese heading and claim it was translated.
2. The query that read them was broken. `language_routes.py` selected
`u.index` and `u.speaker`; the columns are `utterance_index` and
`speaker_name`, so every read raised `sqlite3.OperationalError: near
"index": syntax error` and `/language-mockup` returned 500. It also
opened `<project>/project.db` - a 0-byte decoy - instead of
`<project>/.umbra/project.db`, the file that actually holds the data.
Also in this commit:
* The chapter review page honours `?lang=`, remembers the choice in a
cookie (set by a middleware, so it survives on every page), and shows the
selected language's *name* in the "has not been translated yet" message.
* The page now shows the translation, not the source, and the edit box is
bound to the source text so saving cannot overwrite the English with the
Chinese.
* The stray language selector that sat after `</html>` in base.html is
gone; the chapters-list page's dropdown had zero options because the
route never passed the language list.
* The language tag is wired into generation: `utterance.language_code`
(NULL = project source language) reaches `engine.generate()` and the
staleness marker, so a retagged line is re-rendered instead of keeping
English phonology behind an unchanged text. The marker records the
language; a marker written before the tag existed means English, so the
~4,300 existing clips are not invalidated.
* `project.source_language` (spec 31 section 13.1) records the book's own
language, with a migration for existing project DBs.
* A scene-break marker deliberately records no language: it is silence,
identical in every language, and tagging it would claim a phonology the
audio does not have.
Tests: 883 passed / 8 failed - the same 8 pre-existing failures
(2 test_pronunciation, 6 voice_profile_manager accent). 26 new tests
cover the resolver and the generation wiring. ruff introduces no new
findings.
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.
The controller (oldbox) sent a data-model alignment proposal and asked for six
additive changes on this node, the validation protocol, and an honest report.
Six of the seven changes are done; the seventh is declared missing rather than faked.
Stage 0 - reachability verified exactly as documented: health 200, heartbeat 401
(the correct answer - auth is refusing), missing Host 400. Nothing else was sent.
source_unit_key (controller 5.1, the blocking item)
umbraaudio/source_units.py. sha256("uk1" | chapter_source_text_sha256 |
occurrence | text_sha256). Nothing positional in the key: "occurrence"
disambiguates repeated text in document order.
- 18,181/18,181 units keyed across all four projects, 0 collisions
- chapter 1 renumbered +100 -> 0 keys changed (the load-bearing proof)
- backfill 10,015 rows in 0.11s, second run stamps 0 (idempotent)
- alignment phase 3: 200/200 probe units resolved with used_index: false
- tolerates the older projects that have no chapters.chapter_index
Recorded at generation time in chapter_builder, BEFORE the rows are read and
BEFORE the marker is written, so every path that renders audio also produces
identity and the clip can name its source unit.
5.7 recorded: utterance_index is KEPT as a display ordinal and made
non-load-bearing. spec 21 §10.
Blob hashes and effect chain (5.2)
Markers now carry the full sha256 of the delivered bytes and the effects that
produced them. Verified the existing order is already correct - write_marker
runs AFTER apply_effects - and closed the remaining hole: a changed trait list
changed the hash with no recorded cause. sha256 verification is opt-in, since
hashing every file on every validation pass would re-read gigabytes.
Vocabularies published (5.4) - umbraaudio/definitions.py, 19 sets, 126 values,
each value carrying whether it is 'declared' (spec/controller) or 'extracted'
(read out of code), so a diff shows a difference of meaning, not of wording.
Alignment report tool (6.6) - scripts/alignment_report.py --phase all. Emits the
controller's envelope with the per-phase evidence shapes, and obeys the two
rules they set: no evidence means blocked, never pass; a field we cannot produce
is null PLUS a finding, never omitted. Phase 2 is deliberately 'fail' - the
three-way store disagreement is real, and a pass there would be a check that
passed by doing nothing.
Fingerprint discipline (5.5) - the drift detector reported its OWN fingerprint
change when source_unit_key was added rather than hiding it. Node profile
updated with the new fingerprints and an honest note that they moved.
Node report extended - per-project source-unit-key coverage, per-voice-asset
sha256 + object_key, definition_set_version, controller stage reached.
Six gaps found in the proposal (spec 21 §11), including the one they invited plus
five more. The sharpest: "hash at generation" ignores that audio is
post-processed afterwards, and our key is anchored to chapters.source_text - a
second copy of the manuscript - so any change to chapter storage silently
invalidates every key in that chapter.
5.6 deliberately NOT faked: personality has no versioned artifact here, only
unversioned LLM prose in speakers.json. Reported as phase 6b finding B-03
instead, because wrapping unversioned inputs in a version field would produce
artifacts claiming a lineage they do not have.
Answered the controller's two open questions (infra/controller-handoff-2026-10-03.md):
forward-only link for a revealed recurring character, and yes - a new edition may
contradict a frozen state, since this node's correction path IS a regeneration.
Full suite 484 passed / 8 failed - the same 8 pre-existing and unrelated failures.
ruff clean on every file touched. The parallel session's chapter_builder hunk
(helper_url) was filtered out of the commit and left intact in the working tree.
- export_full_book_m4b: concat chN_full.wav chapters -> AAC m4b with full
m4b_meta.json metadata, chapter markers, and embedded cover art
- metadata: extend BookMetadata, write rich MP4 tags, add apply_ffmpeg_cover
- fix ChapterQualityChecker.check_all_chapters to use row_factory (was crashing)
- chapter_builder/staleness: validate against accepted voice modes (embedding/icl)
instead of a hardcoded single mode, ending over-regeneration of mixed chapters
- pipeline_manager: surface warnings/errors lists; add POST /api/audiobook/export-m4b
- step-6 webui: Export Full Book (.m4b) button with live status/warnings/errors