* docs(specs): restore bootstrap after dev rewrite
* docs(specs): remove runtime inventory snapshot
* docs(specs): reconcile current dev truth
* docs(specs): document scheduled task actions as an owner-attribution source
Owner Attribution covered cookie, bearer-token and internal-loopback
requests. Scheduled task actions are a fourth source and behave
differently: _execute_action passes owner=task.owner off the stored
ScheduledTask row, so no request and no resolved principal are in
flight, and route-level require_user() never runs.
Webhook triggers are the sharp case. They are unauthenticated by
design with the token as the only credential and execute under the
stored task.owner.
Paths cite routes/task/task_routes.py, the canonical location after
the task subpackage move (#6081); routes/task_routes.py on current dev
is the backward-compat shim.
* docs(specs): add chained tasks to the trigger list, refresh dev stamp
Review feedback from RaresKeY on the previous commit.
"Every trigger path" was too broad: success-chained tasks are another
path into _execute_action. Added them with their own citation, and
noted that chaining additionally requires the target task to share
task.owner and rejects cycles, which is stricter than the trigger-side
checks. Softened the lead-in to "these trigger paths".
Line 56 still pointed at routes/task_routes.py for webhook credential
validation. That path is the backward-compat shim on current dev after
the task subpackage move (#6081); repointed to the canonical
routes/task/task_routes.py.
Stamp moved to dev@2a6b09b. Inspection backing that bump was scoped:
every file path cited in this spec was mechanically checked to resolve
on 2a6b09b, and every file:line in the Owner Attribution additions was
read against it. Behavioral claims elsewhere in the file were not
re-audited.
* docs(specs): correct SECURE_COOKIES description to match current behavior
Third of the stale details RaresKeY enumerated. The cookie section
described SECURE_COOKIES as purely opt-in, which stopped being true.
_secure_cookie() (routes/auth_routes.py:89) treats an explicit true or
false as authoritative and derives the Secure attribute from the
request otherwise, including when the variable is unset and when
docker-compose injects it present-but-empty. Either the connection
scheme or the first X-Forwarded-Proto hop being https is enough.
* docs(specs): refresh current dev truth
---------
Co-authored-by: StressTestor <212606152+StressTestor@users.noreply.github.com>
8.1 KiB
Speech
Last updated: dev@e71f8ce | 2026-08-25
Scope
This spec covers speech behavior in:
- app service initialization and route registration in
app.py; services/stt/stt_service.py;services/tts/tts_service.py;routes/stt_routes.py;routes/tts_routes.py;src/upload_limits.py;- settings defaults/cache in
src/settings.py; - settings routes in
routes/auth_routes.py; - model endpoint cleanup in
routes/model_routes.py; - settings/tool aliases in
src/tool_implementations.py; - frontend modules
static/js/voiceRecorder.js,static/js/tts-ai.js,static/app.js,static/js/chat.js,static/js/slashCommands.js,static/js/keyboard-shortcuts.js,static/js/settings.js, andstatic/index.html; - optional dependency declarations in
requirements-optional.txt; - runtime cache path
data/tts_cache/; - tests covering speech service toggles, TTS speed/cache, STT temp cleanup, upload limits, settings scrubbing, and model endpoint cleanup.
Current Call Sites Include
- chat mic/send button behavior;
- browser and server STT recording paths;
- chat message read-aloud buttons and streaming TTS queueing;
/ttsslash command playback;- keyboard shortcut TTS activation;
- admin/settings API writes and
manage_settingsaliases; - model endpoint deletion cleanup for
endpoint:<id>speech providers.
STT
services.stt.STTService owns speech-to-text provider behavior. routes/stt_routes.py owns /api/stt/transcribe and /api/stt/stats. static/js/voiceRecorder.js owns microphone capture, browser STT, server upload, and audio-attachment fallback.
Provider runtime:
disabledreturns unavailable and avoids provider calls;browseris client-side only through Web Speech API and does not call/api/stt/transcribe;locallazily importsfaster-whisper, writes uploaded audio to a temporary WebM file, transcribes, and deletes the temp file infinally;endpoint:<id>resolves aModelEndpointand postsaudio.webmto/audio/transcriptionswith model and optional language.
Route behavior:
- audio uploads are capped by the shared STT upload limit from
src.upload_limits, including environment override validation; - empty uploads return a route error;
- uploaded content type, extension, and magic bytes are not strongly validated today;
- endpoint providers report optimistic availability and fail at request time if offline/misconfigured.
Frontend behavior:
- browser recording needs secure context and microphone permissions;
- server transcription success inserts text into the input;
- failed server transcription can attach the recorded audio file to chat instead; empty transcription shows a no-speech message.
TTS
services.tts.TTSService owns text-to-speech provider behavior, speed parsing, cache behavior, and local/provider-specific synthesis. routes/tts_routes.py owns /api/tts/stats, /api/tts/synthesize, and cache clearing. static/js/tts-ai.js owns frontend playback, client object-URL caching, browser TTS, queueing, and streaming button state.
Provider runtime:
disabledreturns unavailable and avoids provider calls;browseris client-side only throughspeechSynthesis;localcurrently means Kokoro and requirestorch,kokoro,soundfile, and CUDA/import availability;endpoint:<id>resolves aModelEndpointand posts to/audio/speech.- unknown or non-string
tts_providervalues are treated as unavailable rather than being parsed as endpoint strings.
Route behavior:
/api/tts/synthesizesupports binaryaudioresponses and JSONbase64responses;- binary responses choose WAV or MP3 MIME by audio magic bytes;
- synthesis input is passed to the service as submitted and capped there;
- malformed or nonpositive
tts_speedfalls back to1.0; - provider unavailable returns 503; failed synthesis/transcription generally returns route-level failure.
Settings, Endpoints, And Cache
Speech providers are global settings under data/settings.json, with defaults in src/settings.py. Settings reads are scrubbed for non-admin callers, writes are admin-only, and manage_settings can change non-secret speech settings through aliases.
Visible UI state is not complete: backend and JS speech settings exist, the TTS settings card is currently hidden, and the STT settings JS exits when its removed DOM nodes are absent.
routes.model_routes clears tts_provider and stt_provider references when a referenced model endpoint is deleted.
TTS cache behavior:
- server cache lives under
data/tts_cache/; - cache keys include provider, model, voice, safe speed, and text;
- cache files are stored as MP3 or WAV;
- route stats expose global cache state;
- cache clear is global;
- frontend TTS has a separate object-URL cache.
ODYSSEUS_TTS_CACHE_MAX_BYTES bounds server cache growth and is forwarded by all Compose variants. The default is 500 MiB; invalid integers fall back to that default and values at or below zero disable eviction. After a cache write, enforcement scans only .mp3/.wav, ignores files that disappear or cannot be stated, and when over limit removes oldest-by-mtime entries toward 80% of the ceiling. Sort/stat/unlink failures are logged and do not fail synthesis.
Security And Provenance
Speech routes rely on app-wide authentication and do not implement route-local admin or scope checks. Bearer-token callers that pass app auth can reach speech stats/synthesis/transcription/cache-clear surfaces using global speech settings.
Endpoint providers send user audio or assistant text to configured ModelEndpoint URLs with optional bearer keys. Endpoint lookup is by configured endpoint ID and currently does not enforce per-request owner filtering. ModelEndpoint.api_key is encrypted at rest and forwarded only process-side.
Microphone audio, uploaded audio, endpoint transcripts, and assistant text sent to TTS are untrusted/user/provider-visible data flows. Transcripts become user input; they are not trusted system instructions.
TTS cached audio can contain sensitive assistant text rendered as speech. The cache is global, has no owner partition or TTL, and is served inline/base64 by POST responses without a dedicated generated-file route.
Degraded Behavior
- Optional local speech packages may be absent.
- Local STT can run CPU-only and tolerates missing/broken torch by falling back to CPU/int8 behavior.
- Local TTS/Kokoro extras are declared as
kokoro==0.9.4plussoundfileonly for Python 3.11-3.12; Python 3.13+ intentionally skips them because Kokoro excludes those runtimes. Even where installed, local Kokoro remains unavailable without a CUDA-capable torch build/GPU. - External endpoint providers can be offline or misconfigured and may only fail at request time.
- Browser
speechSynthesis,SpeechRecognition,webkitSpeechRecognition, secure context, and microphone permissions can be absent. - Docker GPU overlays are passthrough-only and do not install speech engines by themselves.
- Optional dependency errors and route error wording are not fully consistent across STT and TTS.
Testing Coverage
Existing coverage includes speech service toggles, malformed/non-string TTS provider and speed handling, cache stats plus configured eviction/disable/file filtering/error handling, STT temp cleanup, direct upload limits, model routes, and settings scrubbing.
Missing coverage includes route-level STT/TTS success and failure shapes, auth/API-token behavior, endpoint owner isolation, STT type/magic rejection, TTS request-size/no-store/cache privacy behavior, degraded optional dependency paths, and frontend recorder/TTS fallback states.
Current Gaps
- Visible speech settings UI is incomplete relative to backend settings.
- Speech routes need a deliberate API-token/scope policy.
- Endpoint speech providers need owner-isolation or explicit global-settings documentation.
- TTS cache needs privacy policy: owner partition, TTL, no-store response headers, or accepted global cache semantics.
- STT upload validation needs content type/extension/magic-byte policy.
- Browser/compare STT mic behavior needs a product decision or regression test because compare can force send-button visuals while shared empty-input logic can start recording.