A parallel effort is moving UmbraTrans into the main Umbra system on oldbox
(192.168.123.14). This commit makes this machine describable to that system and
records what the system still needs to decide. Everything is a proposal for the
controller to ratify; no architecture is decided here.
specs/21-umbra-system-nodes-tasks-and-changes.md
Four workstreams, each with what exists, what is missing, and a proposal:
- WS1 data model alignment / copy / verify / transition. Design is far along
already (manifest.md + spec 20); the missing piece is ratification.
- WS2 every machine is a node that gets assigned any task. Job types, leases
and pools exist (spec 19 §12); the node half does not. Proposes node
identity, capability advertisement, task/result envelopes, and refusal as a
valid audited outcome.
- WS3 cross-product change management. Nothing exists today. Proposes a
requirement catalog owned by the controller, four change classes, and a
distribute-then-confirm-with-evidence loop. Includes the worked example of a
controller-owned entity (designation, pronunciation, voice attribute) that
must apply across every product.
- WS4 UI topology. Recommends hybrid: controller gets the system portal,
the audio review UI stays on the worker because it is artifact-native and
has no authentication at all. Adds a git-tag deploy + verify + confirm flow.
Also: 25 questions grouped by who can answer, each naming what it blocks, and
a task list for this machine (NOW / NEXT / LATER) where every task has a
machine-checkable acceptance criterion.
infra/umbra-node.json
This machine's declared profile: node_id umbraaudio-m4, role worker, services,
capabilities, resource pools, constraints, project list with lifecycle, schema
fingerprints, and an honest facts_for_the_controller block (empty tables, no
stable utterance id, no auth, no shared-postgres client). No secrets - ids,
names, labels and counts only.
scripts/umbra_node_report.py
Read-only node report. Git state, listening ports, .env KEY NAMES only, per
project row counts, and a schema fingerprint hashed over sorted
table.column:type so ALTER TABLE reordering does not read as drift. --json for
machine consumption; --check compares declared profile to measured reality and
exits 2 on drift. Drift is deliberately limited to stable facts (contract
version, project set, schema fingerprints) - git HEAD and live ports are
reported but never drift.
manifest.md
Now explicitly a dated measurement with an update log, because other machines
read it as current. 2026-10-01 refresh: Last Dwarf speakers.json 70 -> 93
after the designation merge, DealHunter ref WAVs 94 -> 97. Every other §1.1
count re-verified unchanged.
Measured this machine: 4 projects, 2 distinct schema fingerprints (real
per-project drift), only 4 of 11 tables populated, 461 tests passing / 8 known
pre-existing failures. No UmbraAudio behaviour changed.
164 lines
5.9 KiB
JSON
164 lines
5.9 KiB
JSON
{
|
|
"_comment": [
|
|
"Umbra node profile - WHAT THIS MACHINE IS, declared by this machine.",
|
|
"Produced under spec 21 (specs/21-umbra-system-nodes-tasks-and-changes.md).",
|
|
"Read-only fact check: .venv/bin/python scripts/umbra_node_report.py --check",
|
|
"This file contains NO secrets. Only ids, names, labels and counts.",
|
|
"'status: draft' means the controller has not yet ratified this node."
|
|
],
|
|
|
|
"node_contract_version": 1,
|
|
"status": "draft",
|
|
|
|
"identity": {
|
|
"node_id": "umbraaudio-m4",
|
|
"display_name": "UmbraTools - Mac mini M4 (UmbraAudio)",
|
|
"organization": "umbra",
|
|
"role": "worker",
|
|
"products": ["umbraaudio"],
|
|
"primary_product": "umbraaudio",
|
|
"timezone": "America/Los_Angeles",
|
|
"operator": "single human operator (B.D. Murphy)"
|
|
},
|
|
|
|
"host": {
|
|
"hostname": "mini.bdmurphy.me",
|
|
"os": "macOS 26.6.2 (arm64)",
|
|
"lan_ipv4": ["192.168.123.12", "192.168.123.154"],
|
|
"storage_root": "/Volumes/1TB_APL_Storage",
|
|
"home_relocated": "/Volumes/Dock/worstauthor",
|
|
"python": "3.12.13"
|
|
},
|
|
|
|
"services": {
|
|
"web_ui": {
|
|
"bind": "0.0.0.0:8420",
|
|
"stack": "FastAPI + HTMX",
|
|
"public_name": "umbraaudio.bdmurphy.me",
|
|
"exposure": "Caddy reverse proxy on ANOTHER LAN machine -> this port",
|
|
"auth": "NONE (unsigned active_project cookie). Do not expose beyond the LAN.",
|
|
"autostart": "launchd LaunchAgent com.umbraaudio.web (login-scanned, KeepAlive)"
|
|
},
|
|
"tts_local": {
|
|
"url": "http://localhost:8881",
|
|
"engine": "Qwen3-TTS (transformers, MPS)",
|
|
"autostart": "NONE - manual nohup .venv/bin/python scripts/qwen_tts_server.py"
|
|
},
|
|
"tts_relay_listener": {
|
|
"url": "http://127.0.0.1:18881",
|
|
"note": "root LaunchDaemon on this host forwards to a LAN GPU box; has no upstream fallback"
|
|
},
|
|
"llm_local": {
|
|
"url": "http://localhost:1234/v1",
|
|
"engine": "LM Studio, google/gemma-4-e4b",
|
|
"note": "MUST NOT be co-resident with local Qwen3-TTS (16 GB unified memory)"
|
|
}
|
|
},
|
|
|
|
"capabilities": [
|
|
"audio.parse_manuscript",
|
|
"audio.segment_utterances",
|
|
"analysis.character_discovery",
|
|
"analysis.speaker_attribution",
|
|
"analysis.speaker_clarity",
|
|
"analysis.name_token_scan",
|
|
"voice.reference_generation",
|
|
"voice.attribute_measurement",
|
|
"audio.render_utterances",
|
|
"audio.stitch_chapters",
|
|
"audio.normalize_loudness",
|
|
"export.m4b",
|
|
"export.mp3_package",
|
|
"review.speaker_QA"
|
|
],
|
|
|
|
"resource_pools": {
|
|
"cpu": {"capacity": "bounded; shares the same 16 GB as TTS", "concurrency": 1},
|
|
"llm": {"capacity": "1 x gemma-4-e4b (MPS, ~21 tok/s)", "concurrency": 1},
|
|
"tts_mps": {"capacity": "1 x Qwen3-TTS-12Hz-1.7B", "concurrency": 1, "note": "one orphan generation can stall the model lock for ~20 min"},
|
|
"gpu_cuda": {"capacity": 0, "note": "no local NVIDIA GPU; GPU work is delegated to 192.168.123.14"},
|
|
"ffmpeg": {"capacity": 1, "note": "launchd PATH must be extended or export fails"}
|
|
},
|
|
|
|
"constraints": [
|
|
"macOS Local Network TCC blocks launchd-spawned processes from all LAN IPs (EHOSTUNREACH); loopback is exempt",
|
|
"external-volume TCC: only .venv/bin/python may read/write /Volumes/1TB_APL_Storage under launchd; a shell cannot even open a script there",
|
|
"Qwen3-TTS and LM Studio must never be co-resident",
|
|
"PROJECT_BASE is hard-coded in umbraaudio/config.py and is not configurable",
|
|
"four divergent DDL authorities in this repo (see manifest.md 1.3)"
|
|
],
|
|
|
|
"projects": [
|
|
"DealHunterShip_4.0e_whole-to-audio",
|
|
"Last Dwarf",
|
|
"Treasure Island",
|
|
"Vocals"
|
|
],
|
|
|
|
"project_lifecycle": {
|
|
"DealHunterShip_4.0e_whole-to-audio": "released",
|
|
"Last Dwarf": "active",
|
|
"Treasure Island": "experimental",
|
|
"Vocals": "showcase"
|
|
},
|
|
|
|
"schema_fingerprints": ["0078569a0fa6bc70", "367d5df9e546dbba"],
|
|
"_schema_fingerprints_note": [
|
|
"Two distinct fingerprints = real per-project schema drift.",
|
|
"0078569a0fa6bc70: current shape (has name_tokens + the 5 clarity columns) - Last Dwarf, DealHunter.",
|
|
"367d5df9e546dbba: older shape (no name_tokens, missing 9 utterance columns) - Treasure Island, Vocals.",
|
|
"Produced by scripts/umbra_node_report.py; re-run --check after any migration."
|
|
],
|
|
|
|
"authoritative_data_summary": {
|
|
"in_sql": ["project", "chapters", "utterances", "name_tokens"],
|
|
"in_json": [
|
|
"speakers.json",
|
|
"voice_profiles.json",
|
|
"voice_status.json",
|
|
"voice_engines.json",
|
|
"phonetic_names.json",
|
|
"m4b_meta.json"
|
|
],
|
|
"on_filesystem_only": ["voices/*_ref.wav", "voice_prompts/*.pt (vmatrix)", "chapter_audio/**/*.wav", "*.wav.txt markers"],
|
|
"never_import_as_authoritative": ["pipeline_status.json", "*_build_status.json", "manifests/*.json", "discovery_pass*.json"]
|
|
},
|
|
|
|
"facts_for_the_controller": {
|
|
"empty_sql_tables": [
|
|
"characters",
|
|
"generated_audio",
|
|
"pronunciation_guide",
|
|
"quality_reports",
|
|
"review_decisions",
|
|
"exception_reviews",
|
|
"voice_review_history"
|
|
],
|
|
"has_stable_utterance_id": false,
|
|
"has_stable_speaker_fk": false,
|
|
"has_user_accounts": false,
|
|
"has_shared_postgres_client": false,
|
|
"has_heartbeat_or_lease": false,
|
|
"test_suite": "pytest, 461 passing at 2026-10-01 (8 pre-existing/parallel failures)",
|
|
"open_defects_blocking_exposure": [
|
|
"no authentication on any endpoint (spec 20 J.6)",
|
|
"download path traversal via str.startswith (routes.py ~5049)",
|
|
"upload filename not sanitised with Path(...).name",
|
|
"no CSRF, no security headers, no TrustedHost"
|
|
]
|
|
},
|
|
|
|
"contracts": {
|
|
"informational_only": {
|
|
"min_node_contract_commit": "4775e3b",
|
|
"note": "git HEAD moves constantly; this field is documentation, never drift-checked"
|
|
},
|
|
"specs": [
|
|
"specs/21-umbra-system-nodes-tasks-and-changes.md",
|
|
"specs/20-enterprise-database-integration.md",
|
|
"specs/19-enterprise-control-plane.md",
|
|
"specs/18-UmbraTrans.md",
|
|
"manifest.md"
|
|
]
|
|
}
|
|
} |