215 lines
7.5 KiB
Python
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(),
|
|
}
|