Files
UmbraAudio/umbraaudio/definitions.py
2026-10-18 19:01:04 -07:00

215 lines
7.5 KiB
Python

# Copyright 2026 Umbra Tools (umbratools.com)
# SPDX-License-Identifier: Apache-2.0
"""Published vocabulary sets for the Umbra system (controller §5.4).
The controller's rule, from ``manifest.md`` §14.2, is that anything a stored row depends on
must be a *versioned vocabulary in data*, not a literal in code. Otherwise every product
reimplements it and they drift silently. This module is the audio side of that.
Each set records, per value, where the value came from:
* ``declared`` - stated by a spec or by the controller. This node does not get to change it.
* ``extracted`` - read out of executable code in this repository, and therefore changeable
without a deployment.
That distinction is the point of publishing them: a diff against the controller shows not
only that two sides disagree, but whether a disagreement is one of wording or one of meaning.
Nothing here imports the database or the filesystem. It is pure data, so it is safe to read
from any product and cheap to diff.
"""
from __future__ import annotations
from typing import Any, Literal
Origin = Literal["declared", "extracted"]
DEFINITION_SET_VERSION = "defs-2026-10-03"
# Where each vocabulary's authority lives, for the reader of the diff.
AUTHORITY = {
"utterance_type": "specs/16 §5a, umbraaudio/database.py SCHEMA default 'narration'",
"character_persistence": "controller D11 (specs/21 §9)",
"character_kind": "specs/18 §5.4a",
"voice_capture_mode": "umbraaudio/generation/chapter_builder.py, controller §2.2",
"phonetic_provenance": "manifest.md §14.2",
"attribution_evidence_kind": "umbraaudio/analysis/clarity.py",
"artifact_kind": "manifest.md §11, specs/20 §F.19",
"artifact_status": "controller §2.1 (artifacts.status)",
"project_lifecycle": "manifest.md §1.4, controller §2.1 (projects.status)",
"migration_state": "manifest.md §14.1",
"job_capability": "specs/21 §4.7 + controller §2.3",
"attribute_absence_reason": "controller D13",
"voice_variant_reason": "controller D6",
"name_token_status": "specs/17 §3.4",
"name_token_kind": "specs/17 §3.4",
"post_tts_effect_trait": "umbraaudio/generation/audio_effects.py TRAIT_EFFECTS",
"emotion": "umbraaudio/generation/chapter.py EMOTION_OPTIONS",
"pace": "umbraaudio/generation/chapter.py PACE_OPTIONS",
"qa_issue_type": "umbraaudio/analysis/attribution_qa.py",
}
def _d(*values: str) -> dict[str, Origin]:
return dict.fromkeys(values, "declared")
def _e(*values: str) -> dict[str, Origin]:
return dict.fromkeys(values, "extracted")
VOCABULARIES: dict[str, dict[str, Origin]] = {
# --- unit and unit-adjacent ------------------------------------------------
"utterance_type": _d("narration", "dialogue", "scene_break"),
# controller D11: persistence is DECLARED, never inferred from name overlap
"character_persistence": _d("book_local", "series_persistent"),
# specs/18 §5.4a. Note "speaker" is deliberately absent: it is a derived fact
# (a character with lines in a work), never a kind. Controller §1.2.
"character_kind": _d(
"named_character",
"recurring_role",
"scene_role_instance",
"group",
"ai",
"narrator",
),
# How a voice's identity was captured, per manifest.md §5.4
"voice_capture_mode": _e("embedding", "icl"),
# manifest.md §14.2 - IPA derivation is Latin-only, so provenance is load-bearing
"phonetic_provenance": _d("explicit", "ipa_derived"),
"attribution_evidence_kind": _e("name", "role", "pronoun", "address_only", "none"),
# --- artifacts -------------------------------------------------------------
"artifact_kind": _d(
"source_document",
"character_profile",
"voice_reference",
"voice_prompt_vmatrix",
"utterance_wav",
"chapter_stitch",
"m4b",
"mp3_track",
"ar_package",
),
"artifact_status": _d("intermediate", "final", "superseded"),
# --- projects and migration ------------------------------------------------
"project_lifecycle": _d(
"active", "on_hold", "released", "archived", "experimental", "parsed", "analyzed"
),
"migration_state": _d("legacy", "mirroring", "dual_write", "authoritative", "retired"),
# --- work routing ----------------------------------------------------------
"job_capability": _d(
"audio.parse_manuscript",
"audio.segment_utterances",
"analysis.character_discovery",
"analysis.speaker_attribution",
"analysis.speaker_clarity",
"analysis.name_token_scan",
"character.personality_analysis",
"character.consistency_check",
"voice.capture_character",
"voice.reference_generation",
"voice.attribute_measurement",
"audio.render_utterances",
"audio.stitch_chapters",
"audio.normalize_loudness",
"export.m4b",
"export.mp3_package",
"review.speaker_QA",
),
# controller D13: absence is typed, never NULL
"attribute_absence_reason": _d(
"not_attempted", "not_applicable", "in_progress", "superseded"
),
# controller D6: a variant with no reason is indistinguishable from a bug
"voice_variant_reason": _d(
"sarcasm_mimic", "disguise_roleplay", "technical_degradation"
),
# --- review ----------------------------------------------------------------
"name_token_status": _e("unresolved", "assigned", "created", "dismissed"),
"name_token_kind": _e("name_tag", "speaker_name"),
"qa_issue_type": _e(
"speech_tag_mismatch",
"narrator_dialogue",
"role_tag_guessed",
"address_mismatch",
"narration_speaker_mismatch",
"long_monologue",
),
# --- delivery --------------------------------------------------------------
"emotion": _e(
"neutral",
"calm",
"serious",
"sad",
"happy",
"angry",
"fearful",
"excited",
"gentle",
"firm",
"hesitant",
"sarcastic",
"tender",
"urgent",
"dramatic",
"whisper",
"romantic",
"commanding",
"melancholy",
"hopeful",
"desperate",
"surprised",
"confused",
"disgusted",
"determined",
"tired",
"confident",
),
"pace": _e("slow", "moderate", "fast"),
"post_tts_effect_trait": _e(
"raspy",
"gravelly",
"hoarse",
"breathy",
"whispery",
"nasal",
"deep",
"boom",
"reverb",
"echo",
"radio_filter",
"distortion",
"pitch_shift",
"noise_inject",
"tremolo",
"chorusing",
),
}
def vocabularies() -> dict[str, list[str]]:
"""The plain ``{set_name: [values]}`` shape the controller diffs (phase 1)."""
return {name: sorted(values) for name, values in VOCABULARIES.items()}
def provenance() -> dict[str, dict[str, str]]:
"""Per-value origin plus the authority each vocabulary is accountable to."""
out: dict[str, dict[str, str]] = {}
for name, values in VOCABULARIES.items():
entry = {v: o for v, o in values.items()}
entry["_authority"] = AUTHORITY.get(name, "undeclared")
out[name] = entry
return out
def declaration() -> dict[str, Any]:
"""The full published definition set, versioned."""
return {
"definition_set_version": DEFINITION_SET_VERSION,
"product": "umbraaudio",
"vocabularies": vocabularies(),
"provenance": provenance(),
}