217 lines
9.9 KiB
Gherkin
217 lines
9.9 KiB
Gherkin
Feature: Upload an audio recording, get a private Swedish transcript, discuss it
|
|
As someone with a recorded meeting on my computer
|
|
I want to upload it and then ask questions about what was said
|
|
So that I can find out what was decided, what is open and who owes what
|
|
|
|
# ADR-033, tapir#33. The load-bearing constraint: an uploaded transcript is
|
|
# PRIVATE content, so it lives in its own RLS-scoped store and never in the
|
|
# shared public-caption `transcripts` table (ADR-021). Rejection is synchronous,
|
|
# the audio is never retained, and a failed job ends in a terminal state with a
|
|
# message — never a spinner (ADR-025). A file with a video track (a Teams meeting
|
|
# with the camera off is one) is accepted: only its audio is used and the user is
|
|
# told. Video playback, keyframes and analysis of what is shown remain #28.
|
|
|
|
Background:
|
|
Given I am a registered user with audio upload enabled
|
|
|
|
Scenario: The upload form states the limits up front
|
|
When I open the upload page
|
|
Then I see the size limit and the length limit before I choose a file
|
|
|
|
Scenario: An accepted recording is queued and I am taken to its page
|
|
When I upload an audio file within the limits
|
|
Then it is recorded as queued for me
|
|
And I am redirected to its page
|
|
|
|
Scenario: A recording with no title is named after its file
|
|
When I upload an audio file and give no title
|
|
Then its title is the filename without its extension
|
|
|
|
Scenario: A file with a video track is accepted and I am told the video was ignored
|
|
When I upload a recording that also has a video track
|
|
Then it is accepted and queued like any other
|
|
And its page says only its audio was used and the video was discarded
|
|
And it says so while queued, while transcribing, when done and when failed
|
|
|
|
Scenario: A video file with no audio is rejected for having no audio
|
|
When I upload a video that has no audio track
|
|
Then it is rejected with a message that it has no audio
|
|
And nothing is recorded
|
|
|
|
Scenario: A recording over the length limit is rejected with both lengths
|
|
When I upload a recording longer than the limit
|
|
Then it is rejected stating the recording's length and the limit
|
|
And nothing is recorded
|
|
|
|
Scenario: A file over the size limit is rejected as too large
|
|
When I upload a file larger than the size limit
|
|
Then it is rejected with 413 and a message about the limit
|
|
And nothing is recorded
|
|
|
|
Scenario: Cover art on an audio file is not a video track
|
|
When I upload an mp3 whose only video stream is attached cover art
|
|
Then it is treated as audio and no video notice is shown
|
|
|
|
Scenario: A file that is not media is rejected as unreadable
|
|
When I upload a file ffprobe cannot read
|
|
Then it is rejected as the file's fault
|
|
|
|
Scenario: A broken ffprobe is reported as our fault, not as a bad file
|
|
When the prober itself fails
|
|
Then the upload is an error rather than a rejection of my file
|
|
|
|
Scenario: A recording becomes a transcript and the audio is deleted
|
|
Given I have uploaded a recording
|
|
When it has been transcribed
|
|
Then the transcript is stored privately for me
|
|
And the audio is deleted
|
|
|
|
Scenario: The endpoint that served a transcript is recorded and shown
|
|
Given a recording was transcribed by the fallback endpoint
|
|
When I view its page
|
|
Then it names the fallback endpoint, because that means the audio left the homelab
|
|
|
|
Scenario: The local endpoint is tried first and the fallback only on failure
|
|
When the primary transcription endpoint fails for any reason
|
|
Then the fallback endpoint transcribes it
|
|
But when the primary succeeds the fallback is never contacted
|
|
|
|
Scenario: A failed transcription ends in a message, never a spinner
|
|
When every transcription endpoint fails
|
|
Then the upload is failed with a typed reason
|
|
And its page shows that reason and stops polling
|
|
|
|
Scenario: A restart fails the jobs it interrupted
|
|
Given the service restarted while a recording was queued or transcribing
|
|
When it starts again
|
|
Then those uploads are failed as interrupted
|
|
And any audio left in the spool is deleted
|
|
|
|
Scenario: Only one recording is transcribed at a time
|
|
When several recordings are queued
|
|
Then they are transcribed one after another
|
|
|
|
Scenario: My uploads are invisible to other users
|
|
Given another user has uploaded a recording
|
|
When I ask for it by its id
|
|
Then I get 404 and no part of its title or text
|
|
And the chat for it calls no model
|
|
|
|
Scenario: I can ask about my recording and get a grounded answer
|
|
Given my recording has been transcribed
|
|
When I ask a question about it
|
|
Then the answer comes from my transcript through the same chat as a video's
|
|
|
|
Scenario: A long recording is not cut to its first third
|
|
Given my recording's transcript is longer than the caption default
|
|
When I ask a question about it
|
|
Then the transcript is sent with the upload budget, not the 18,000-character caption default
|
|
|
|
# ADR-034, two engines and a local reconciler (off unless TAPIR_RECONCILE_BASE_MODEL is set)
|
|
|
|
Scenario: Two engines transcribe the audio and their disagreements are reconciled
|
|
Given two-engine transcription is enabled
|
|
When a recording is transcribed
|
|
Then both engines run on the same audio, the fast one first
|
|
And a local model chooses between their disagreements
|
|
And the endpoint names every engine and the model that took part
|
|
|
|
Scenario: Trivial disagreements never reach the model
|
|
When the engines differ only by a compound written as one word or two, or by a filler word
|
|
Then the base reading is kept without a model call
|
|
|
|
Scenario: Differences close together are one decision
|
|
When two differences are separated by a single shared word
|
|
Then they are asked about as one dispute
|
|
|
|
Scenario: An unsure or changed reading is flagged for me
|
|
When the model changes a reading or cannot decide
|
|
Then the page lists the spot with a minute mark, both readings and which was used
|
|
And an unsure spot keeps the base reading and is marked uncertain
|
|
|
|
Scenario: The model cannot be a cloud model
|
|
When the reconciliation model or base engine is not a koala/ or iguana/ alias
|
|
Then the service refuses to start
|
|
And a cloud alias that merely contains a local host name is refused too
|
|
|
|
Scenario: One engine failing does not fail the upload
|
|
When one of the two engines fails
|
|
Then the other engine's transcript is used
|
|
And the page says only one engine produced it and why
|
|
And the cloud fallback is not used
|
|
|
|
Scenario: Only when both local engines fail does the cloud fallback run
|
|
When both local engines fail and a fallback is configured
|
|
Then the fallback transcribes it and the row records that it did
|
|
|
|
Scenario: A model that is down gives an unreconciled transcript, not a failed job
|
|
When the reconciliation model fails three times in a row
|
|
Then the base transcript is kept unreconciled with the reason
|
|
And the page says it could not be reconciled
|
|
|
|
Scenario: A cancelled job never reaches the cloud
|
|
When the job is cancelled during either engine
|
|
Then neither the second engine nor the cloud fallback is called
|
|
|
|
Scenario: A single-engine transcript claims no second opinion
|
|
When only one engine was configured
|
|
Then the page does not mention reconciliation
|
|
|
|
# ADR-035: progress, the log, and the judged spots
|
|
|
|
Scenario: I can see which phase my upload is in
|
|
Given a recording is being transcribed
|
|
When I open its page
|
|
Then it lists every phase in order, the finished ones with how long each took
|
|
And the running phase is marked, with how long it has run
|
|
And the page updates in place every two seconds until the job ends
|
|
|
|
Scenario: A bar appears only where the count is real
|
|
When the deciding phase has decided 142 of 282 differences
|
|
Then the page shows a bar with "142 of 282"
|
|
And an engine call, which is one request with nothing to count, never gets a bar or a percentage
|
|
|
|
Scenario: A slow engine call is not mistaken for a dead service
|
|
When an engine call has run for 25 minutes and the service is still reporting
|
|
Then the page shows the elapsed time and says nothing is wrong
|
|
|
|
Scenario: A service that stopped reporting is called lost
|
|
When nothing has been heard from the service for longer than the heartbeat allows
|
|
Then the page says the service stopped reporting and may have restarted or crashed
|
|
And the running phase carries a warning mark and no spinner
|
|
And the page keeps checking
|
|
|
|
Scenario: A model that stops advancing is called quiet
|
|
When the deciding phase has not advanced for five minutes and the service is still reporting
|
|
Then the page says nothing new has happened for that long and that the service is still running
|
|
And it does not say the service is lost
|
|
|
|
Scenario: A failed job shows where it stopped
|
|
When a job fails
|
|
Then the failed phase is marked and what never started stays pending
|
|
And the page stops checking
|
|
|
|
Scenario: A finished job keeps its record of what was done
|
|
When a job is done
|
|
Then the page keeps a collapsed list of the phases with how long each took
|
|
|
|
Scenario: The heartbeat runs on its own while an engine call is stuck
|
|
When an engine call does not return
|
|
Then the row is still stamped alive every few seconds
|
|
And no heartbeat outlives its job
|
|
|
|
Scenario: The log names every phase and never the transcript
|
|
When a job runs
|
|
Then the log has a line per phase in the order it ran, each naming the upload
|
|
And no line contains transcript text or the spool path
|
|
|
|
Scenario: Every judged spot is stored but only the ones to check are listed
|
|
When the model has changed, kept and been unsure about spots
|
|
Then every one is stored with its verdict and the words before it
|
|
And the page lists only the changed and uncertain ones, with the words before each
|
|
|
|
Scenario: The saved segments carry the merged words
|
|
When the model changes a word
|
|
Then the segment containing it carries the reconciled word
|
|
And the segment's timing is unchanged
|