Repository navigation
Releases
Releases · codemyriad/gocassini
v0.2.0
Added
- Administrators can select Transcription only (.json) in Settings → Publish pipeline to publish new meetings as transcription-only files in Nextcloud Files, retaining speaker timing, summaries, metadata, tags and annotations without audio. Existing meetings keep their format on reruns. Audio and transcription-only meetings can coexist, are labelled in the meeting list, and follow the same retention policies.
- Administrators can publish transcription-only meetings and choose whether to retain source media for reruns or delete it after processing succeeds or fails. Each recording keeps the policy selected when it starts.
- Operator jobs show media deletion progress and errors, offer deletion retries, and explain why processing cannot be rerun when deletion was selected.
- Proof of concept: separate the voices of people who shared one participant's device. In the app, a meeting's People panel offers Separate voices for a participant, then lets readers listen to each voice, name it, say two voices are the same person, or undo the split; the operator republishes the recording with the new speakers and rewrites its summary. Diarization runs locally on that participant's own track and stores only turn timestamps; nothing is re-transcribed and the audio is not re-encoded, so marks stay attached. After a split the original transcript stays in the recording as
raw-asr("Original"); renaming a speaker without a split only changes the name in the one transcript. Each person can save a limited number of speaker changes in a while, across all meetings; past it the panel says when to try again and keeps what was typed. Seedocs/speaker-separation.md. - Voice separation (optional) in Settings: an administrator downloads the Nemotron 3 Diarization int8 model (about 62 MiB, CPU only) like any optional model;
cassini models install|pack|importhandle it too. cassini speakers diarize|apply|show, and the operator routeannotations/meetings/<id>/speakers(GET/POST, for anyone who can read the meeting).CASSINI_DIARIZATION_THREADSsets the diarizer's CPU threads (default 2).- Insights have names. Create an insight fills one in from the template and the meetings it reads (the question is quoted inside the insight instead), and you can change it before generating or later from the pencil beside the insight's title, with Save and Cancel as when renaming a tag. The list, the insight and the meeting's Insights card show the name, search matches it, and the document saved to your Nextcloud files is named after it.
- An answered insight can now leave Cassini. Export in the insight drawer copies or downloads a markdown copy with a readable header (the insight's name, the question when that differs, when it was written, the template and model, and the meetings it drew from that you can see, each linking back to its meeting), and Open in Files opens the document in Nextcloud Files with its details open, where it can be shared.
- Administrators can schedule whole-meeting expiry from Nextcloud Files independently of container storage. Expiry removes published audio, transcripts and notes together, with conditional deletion, durable recovery and protection against reruns or indexes recreating expired meetings. Nextcloud versions, trash and backups remain separate.
- ADMIN-only APIs provide on-demand sweeps and eviction previews with the meeting name, creation and recording timestamps, age in calendar days and an evict/keep decision.
- ADMIN-only
POST /operator/storage/retention/sweepto run the scheduled expiry logic on demand, with overlap protection and synchronous results. - Configurable daily retention sweep time and timezone in Operator → Storage, defaulting to 02:00 UTC. Saved schedule changes take effect without restarting.
- Operator → Storage retention configuration for container recordings, attempt history, current output archives and stage logs. Policies default to keep forever, with 7, 30, 60, 90 or custom days evaluated on UTC dates.
- Startup/daily cleanup with restart recovery, whole-recording expiry (audio, video and supporting files together), and unavailable-source rerun protection. Only successful publication replaces the current local archive.
- Storage insights now includes published Nextcloud files in the category comparison and temporal charts, with the same time range and exact-value controls as local assets.
- Files without a matching job use recordedAtLocal or createdAtUtc metadata for their date, including inspection of older unindexed files. Undated files and partial measurement failures remain visible.
- Operator → Storage shows local usage by retention category alongside retention settings, plus published Nextcloud usage including legacy archives.
- Each category has a date bar chart with independent time range and precision controls, exact values and a data table. Attempt history can be combined or split into its fine-grained policy categories. Unknown dates and unclassified local files remain visible.
- Storage usage is served from an in-memory index with its last-calculated time. Administrators can rebuild it on demand, and Cassini refreshes it on fixed five-minute boundaries.
- After a Talk recording is first published, Cassini makes a best-effort attempt to post its meeting link as the moderator who started recording. Notification failures are logged without affecting publication or recording access; messages are not retried.
Changed
- The fp32 Parakeet v3 model now downloads from dist.gocassini.com, like every other model.
- The native speech runtime now includes Nemotron 3 Diarization support (sherpa-onnx
v1.13.7-cassini.6). The runtime accepts the version-2 Nemotron models on dist.gocassini.com and fixes final-chunk padding. Parakeet and Silero model files and transcription behavior are unchanged. - The privacy guide describes on-demand voice separation, the summary rewritten after a speaker edit (which sends the transcript and the typed names to the configured summary endpoint), and where names and editors are kept.
- A participant whose voices were separated stays in the recording's speaker list, marked with the voices that replace it, so every transcript's speakers are listed. Readers older than this release show it as one more speaker beside its voices.
- Refreshed the App Store listing: a new description with a live demo link, an early-release note inviting feedback, rough hardware guide and Ethical AI rating; six new captioned screenshots; the Social & communication category; gocassini.com as the homepage; and the Codemyriad author link.
- The documentation and landing page describe recording access as it now works: each recording is shared with the people, groups and Teams in its Talk room, with no audience to choose and no Team folders or Everyone Group app needed.
- New recordings capture audio only by default, including upgrades without a saved video choice. Administrators can opt into retaining source camera video using Capture video in Operator → Publish pipeline. Retention review or reminder dismissal does not change video consent. Saving affects subsequent recordings; published meeting files remain audio only, and existing source video follows its retention policy.
- Retention descriptions explain storage growth and the consequences of deleting each category. Existing keep-forever defaults and published Nextcloud recordings are unchanged.
- Nextcloud Files retention uses the meeting’s recorded local date when present, otherwise its creation timestamp. Existing managed meetings adopt these dates without relying on job completion history; unknown provenance or invalid timestamps keep files unchanged.
- Preview, operation-status inspection and on-demand sweeps are API-only tools; the retention UI provides policy settings. The durable operation journal and backend logging are preserved.
- The old artifact-retention flag/environment variable is deprecated and ignored. Successful capture/intermediate duplicate cleanup remains unconditional; historical payloads now default to keep forever.
- Search backfill against the local work root requires stopping its operator process to avoid racing archive promotion or expiry. Existing AppAPI installations must receive the new ADMIN retention route through a versioned manifest update.
- Wide storage charts can be scrolled with the scrollbar, keyboard, or Earlier/Later buttons. Days per bar adapt automatically to the selected range.
- A meeting opens on Summary and Transcript tabs, with insights at the end of the summary. The playback bar shows only under Transcript, and find-in-transcript sits beside the tabs, pinned while you scroll.
- The meeting header is compact: the date, room, duration and participants sit in a details pill that opens them in full, technical details are folded into "About this recording", and the Raw JSON view is gone. Copy and download actions are one Export menu.
- Tagging is a deliberate step. Dragging the timeline rail scrolls the transcript instead of selecting text, and a text selection offers Tag selection, Copy text and Cancel. An existing tagged section opens read-only, with Edit to adjust or remove it. A tag on the whole meeting is removed from its menu rather than an always-visible ✕.
- Selections and hovered tagged sections are highlighted continuously across words, like a normal text selection.
- On phones, the meeting and Prepare sheets fill the screen, and tapping the covered tab bar closes them.
- Doctor opens like the other Operator pages: a fixed title and description, with Run all checks beside the title, and the overall verdict in a callout beneath, with when the checks last ran. The verdict also says when a test recording you started is unfinished, and links to it.
- Each check leads with the next thing to do, under the explanation it acts on: Nextcloud's setup guide and Talk's settings when there is no High Performance Backend, **Open...
v0.2.0-beta.8
Added
- The recording checks now show their state in colour: green for a check that passed, red for one that is blocking recording, and grey for one nothing has verified yet. The heading takes the colour of the worst news, so the panel can be read at a glance.
- Configure optional transcription in Settings with visible model download progress, cancellation, retry, and explicit activation.
- Model downloads resume on their own after a dropped connection or a stalled transfer.
- Prepare transferable models with
cassini models packand install manually copied packages or files on air-gapped systems withcassini models import. - A Doctor section in the operator answers "is recording going to work?" in one place. It reports the recording volume and temporary space, Nextcloud storage, Talk's High Performance Backend and its credential, the Talk connection, a test recording, and how much of your archive search can read. Checks run once when Cassini starts and then only when you ask — with Run all checks, or a single row's own button, which re-runs only the probe behind that row.
- Cassini says when Talk has no High Performance Backend, which stops recording entirely. It reads Talk's own signaling mode, so this appears even before a recording credential exists; it used to be discoverable only through a connection check that never got that far. The row is on the checklist whatever the outcome, including before anything has been established, so the one check that decides whether recording can work is never simply absent.
- Archive search coverage reports how many meetings search can actually read, so a partial answer is visible instead of looking like no matches. Where meetings were never indexed, a Re-index now button does it for you. A shortfall nobody can act on — a recording where nobody spoke, or an older meeting search cannot index at all — is reported without being counted against coverage.
cassini doctor --jsonemits its checks as structured data, each with a stable id. Runningcassini doctorby hand prints exactly what it printed before.- Copy or download a meeting transcript as Markdown (
.md) and download its audio from the meeting view or Prepare. Multiple selected meeting files download together in one ZIP. - The
<cassini-meeting>embed takeslayout="inline"for pages that already show the recording's name and details, and--cassini-color-*/--cassini-font-sanscustom properties to match the page's colours and type. - The
<cassini-meeting>embed dispatches aplaybackerrorevent when the browser cannot play the recording, and follows itsthemeattribute when the page changes it. - Scheduled upstream compatibility checks and real-browser coverage of the embedded Cassini transcript and recording playback.
Changed
- Share each recording with its Talk room audience through Nextcloud Files, without requiring Team Folders or Everyone Group apps.
- Show only recordings in the caller's current Nextcloud shares and check each file read as that caller.
- Replace the two recording access choices with one participant-based model; public meeting participants may reshare when Nextcloud permits it.
- Rewrote the App Store summary and description to match the README and docs site, and pointed the user documentation link at gocassini.com/docs.
- Brought the App Store description up to date: Nextcloud 33.0.9 to 35, transcription off until a model is installed (and the model download from
dist.gocassini.com), the Doctor checklist, and transcript and audio downloads. - Rewrote the landing page copy: a new hero, a feature list that leads with the App Store install and Talk's Record button, an AI providers section in place of the summaries one, and reworked CPU and GPU cards.
- Corrected the supported Nextcloud versions to 32 to 35, said that insights as well as summaries send transcript text to a configured endpoint, and linked the meeting file specification.
- Renamed GoCassini to Cassini in page titles, link previews and support links.
- Transcription defaults to off. Recordings remain playable with participant metadata when transcription is disabled, unavailable, or fails.
- A recording waits, as it does for memory, when its model cannot be checked yet (GPU not visible, not enough free RAM or VRAM). It is published as audio only when the model is missing or the wait runs out.
- CPU and CUDA images no longer bundle Parakeet or VAD. Models download from
dist.gocassini.cominto persistent revision directories and survive application upgrades without downloading unchanged weights. - A check that depends on another one says what it is waiting for rather than asking for attention itself, and offers no buttons of its own. With no High Performance Backend, the credential and the connection checks no longer each report a problem: the backend is the one row to act on, and fixing it is what makes the others checkable.
- Testing a recording no longer asks you to supply a Talk room. Cassini makes the conversation for you, in your name so that you can moderate it — Talk only lets a conversation's moderators start a recording — and you join the call and press Talk's own record button, while the row reports what happened until you confirm you could play the audio back. The check appears only once everything it depends on has been checked and passed, so it is never offered as a test that cannot succeed.
- Checks explain in words, with no shell commands to copy and no questions about how Nextcloud was installed. Where Cassini can fix something it offers a button; where it cannot, it points at Nextcloud's own settings and documentation. An instruction assembled from a guess about your deployment is worse than a reference.
- Checks no longer re-run themselves every five seconds. A reading is taken when Cassini starts and whenever you ask for one, so the panel stops disabling its own buttons twelve times a minute, and a check costs a probe only when somebody wants it. A test recording still being worked on is followed until it publishes, because that is the one thing that moves on its own.
- The signaling credential is reported by the High Performance Backend check rather than by a row of its own: it is that server's credential, and that check is what accepts or rejects it. One row now says whether a backend exists, whether Cassini has its secret, and whether the secret works. It says plainly that the value is not a Nextcloud setting and where on the signaling server to read it, it does not ask for the secret where no backend exists, and the form stays reachable once the check passes so the secret can be rotated.
- The Nextcloud storage check says what it established: Cassini's account exists, its recordings folder is writable, and the sharing API answers. It used to report that a "preflight passed", which named an internal routine and no fact.
- The operator's readiness endpoint is now
GET /operator/health, because it reports on more than recording readiness. An installed app picks the new route up when it updates to a release carrying it. - Require Nextcloud 33.0.9–35 for new installations; retire Nextcloud 32 support in this release.
- Require successful installed-app compatibility evidence for every advertised Nextcloud major before publishing a release, with exact tested versions and artifacts attached to the release.
- Cache compatibility Go tools, report scenario phase timings, share GPU smoke transcription across assertions, and separate registry maintenance from release qualification.
Removed
- The "Cassini is ready to record" first-run dialog and its per-install acknowledgement, left over from the two permission models. A fresh install records as soon as its checks pass; creating the
cassiniaccount, when Nextcloud refuses it to the app, stays in Operator › Publish pipeline.
Fixed
- A failed refresh no longer erases the checks. It used to rewrite every row to "could not refresh", so one failed request hid whatever was actually wrong; the last findings stay on screen and the failure is reported once, on its own.
- A check that was passing no longer turns into "not verified" five minutes later. It keeps what it found and says how long ago, so an idle panel stops looking like a problem — and a check that has never run says so, rather than being reported like one whose result merely aged.
- A connection check that ran and could not reach Nextcloud or the signaling server now reports a problem instead of "not verified". It tried and failed, which is a finding about the deployment.
- When Cassini's Talk backend URL override points at Cassini's own app proxy, the check says so and names the setting to clear, instead of blaming connectivity and TLS.
- Buttons that read "Configure" and opened a panel about an unrelated fault are gone. Every action a check offers is named for what it does, and one the app cannot name offers no button at all.
- The operator panels can be scrolled to the bottom again. Whenever a check needed attention, the banner saying so pushed the panels down without shortening them, so their last few centimetres sat below the window with no way to reach them.
- Meeting tags now stay in click order while changes are confirmed or retried: selection and meeting counts update immediately without bouncing, and tag choices remain alphabetically ordered.
- An embedded recording no longer takes the Space key on the whole page. Space plays and pauses only when pressed inside the viewer.
- Following playback in an embedded recording scrolls only the transcript, never the page around it.
- A narrow embedded recording no longer shows a back arrow that led nowhere.
- An embedded recording no longer reads or rewrites the host page's URL fragment, so switching transcripts no longer replaces the page's
#anchor. - Meeting tags and transcript annotations now appear immediately while saves run in the background. Editing stays available, pending changes survive closing and reo...
v0.2.0-beta.7
Added
- Access control is now a choice rather than a requirement. Cassini used to have exactly one way to store recordings — a Nextcloud Team folder with per-recording permissions — which needs two Nextcloud apps an external app cannot install for itself, so an instance without them recorded meetings happily and then could not publish any of them. Recordings can now be visible to anyone with a Nextcloud account, in the
cassiniservice account's ownCassiniNoACL/Recordings, needing no extra apps at all; or to meeting participants only, which is exactly today's model in itsCassiniTeam folder. Each has its own folder, so neither can shadow the other's path. - Cassini can set itself up. It offers to make the changes an administrator used to make by hand: the
cassiniservice account and its group, the Team folder, its group mappings, advanced permissions, and the permission-manager delegation. It lists every change before it makes any of them, and performs them as you — Nextcloud asks you to confirm your password with its own dialog, and Cassini never sees, stores or sends it. Theoccrecipe is still there for an administrator who would rather run it, and is generated from the same plan Cassini executes, so the two cannot describe different setups. - Every missing prerequisite is named, together with the
occcommand that fixes it. Cassini andGET /operator/statuslist them all — the service account, each Nextcloud app, the Team folder, its group mappings, its ACL setting and its ACL manager. - A development or CI deployment can still declare the mode, with the
CASSINI_STORAGE_MODEdeploy option (defaultoraccess_controlled). It is not for production installs, and it is believed only where it fits: a declared mode whose prerequisites are missing, or that meets recordings in both folders, or the same recording in both, is refused at enable time and not recorded. It seeds the initial value only, and changing the variable never moves an archive that already exists. - Switching says what it would do before it does it, and can be re-run. The confirmation reports how many recordings would move and between which folders, whether the destination already holds any, and whether an earlier switch left a copy behind. Working that out is read-only, and nothing happens until you confirm. The switch itself copies the archive, checks every recording arrived, records the new audience, and only then empties the old folder — so whichever audience is in force, that folder holds a complete archive, and a switch interrupted by a restart, a timeout or a closed browser tab strands nothing and can simply be asked for again. Cassini names the folder that still holds a leftover copy, offers one action to clear it, and says when recordings are sitting in the audience that is not in force.
cassini meetings summarize <meeting.opus> ...adds a meeting summary to already-published portable files without re-running transcription: the transcript sealed inside each file feeds one LLM call — configured by the sameLLM_BASE_URL/OPENROUTER_API_KEY/SUMMARY_*variables ascassini build— and the file is rewritten in place with its audio bytes untouched.- Files that already carry a summary are skipped;
--forceregenerates and replaces one, and--outwrites a single file's result to a new path instead of in place. A backfilled summary is markedprovenance.meetingSummary.source = "backfill"in the manifest. - You can find words inside the meeting you are reading. A search box above the transcript narrows it to the turns that match, shows how many of them there are, and clearing it brings the whole transcript back. It searches what was actually said, so it still finds a turn whose wording was tidied for display.
- Administrators can tell Cassini the spellings transcription produces for names it gets wrong, in the app's settings beside the existing vocabulary: one name per line, its spellings separated by commas. A search for the name then also finds the recordings where it was written down differently, and each result says whether it matched what you typed or a known mis-hearing. Cassini ships a starting list; anything configured here is added to it, and replaces it for a name that appears in both. It changes no recording — the vocabulary setting is the one that biases new transcriptions.
- You can now search across your meetings. Cassini indexes each transcript as it is published and answers
published/searchwith the moments that match — the meeting, the speaker, and where in the recording they said it. You only ever see meetings you can already open. - Search knows the names transcription gets wrong. Looking for "cassini" also finds the recordings where it was written down as "casino", and each result says whether it matched your words or a known mis-hearing of them.
- Every answer says how much it covered: how many of the meetings you can read were actually searched. A meeting Cassini could not index is reported as outside coverage rather than passing for one with no matches, and an unreachable archive is an error rather than an empty result.
- Cassini's search index lives in its own
search.sqlite3file beside the job database and holds nothing that cannot be rebuilt from the published recordings. Deleting it is safe: it costs a rebuild and never touches recordings or job history. cassini-operator backfill-searchindexes meetings published before the index existed. It is safe to re-run and safe to interrupt, and it indexes a meeting only when the stored transcript matches the recording that was delivered.- Searches are rate limited per account. Each one asks Nextcloud what that person may read, so a client looping on search would put that load on Nextcloud rather than on Cassini; a burst is fine and a loop is asked to slow down, with a reply that says when to retry. The limit is per account, so one runaway client cannot lock anyone else out.
- Pick several meetings in the Cassini app — across rooms, and the picks survive changing the room or the search — and Prepare hands you all of them as one document to copy or download. It is the same document
cassini meetings contextprints for the same meetings in the same order, byte for byte, because the app asks the same producer for it rather than assembling its own. - Before it hands anything over, Prepare says what the bundle will not contain: how many of the picked meetings have no summary, how many were published before the index recorded whether they have one — a different statement, and never counted as the first — how many predate the single-file format, and whether the word count it shows is a total or a floor. All of it is read from the published meeting index, so nothing is downloaded to answer it.
- A meeting that leaves the archive while it is picked is removed from the selection and said so, rather than quietly shrinking the document you were about to hand over.
- A portable export offers no Prepare at all. There is no operator behind an exported site to assemble a bundle, and producing a lookalike in the browser would be a second implementation of a published format.
GET /jobsreturnsroom_name, the conversation's display name as it was when the job ran. The operator now stores the room as real columns on the job rather than only inside its Talk binding, and recovers them for every recording it has already made, so a room-scoped question is an ordinary query rather than a JSON parse. The conversation's token gets a column but is deliberately left out of the response, for the same reason the Talk binding has always been withheld from it.- The meeting list is organised by room. A rooms nav down the left lists every room the archive knows about with its meeting count, alongside "All meetings" and a "No room" group that keeps recordings without a room — an imported file, a simulated run, an old recording — visible rather than hidden.
- Meetings are grouped under month headings, and each row now shows its room, its date, its speaker count and how long it ran.
cassini insight runasks one question of several meetings and writes the answer. It reads the context bundlescassini meetings context --json --outproduces — one--contextper bundle, in the order you name them — runs the summarise workflow over all of them at once, and writes the result to--outor to standard output.--modeloverrides the model for that run alone, and--timestampsshows the model where in each meeting something was said so it can cite it.- The document it writes carries its own provenance as frontmatter: which meetings it was produced from, with their rooms; the workflow's id, version and the content hash of the exact prompt bytes that produced it; the endpoint and model that answered; and when. A summary has always been able to say only that some model wrote it at some point, which is not enough to tell two documents from different prompt versions apart.
--recordwrites the same record as JSON beside the document, so a grading harness reads one pair rather than parsing prose. cassini insight runexits with a different code per outcome — no endpoint configured, the endpoint refused, the model failed or timed out, a bad request — instead of warning and carrying on. Skipping quietly is reasonable for a summary nobody asked for and never for a document somebody did. The codes are listed incassini insight run --help. A failed run still writes its--recordfile, saying what was attempted and why it did not happen.- Transcripts now carry cross-track speaker attribution evidence. Every word records how far the loudest other participant's microphone sat above its own noise floor compared with the speaker's own, so crosstalk the decoder transcribed as speech becomes visible instead of shipping unmarked. Where a meeting's own level distribution shows an unambiguous crosstalk population, affected words are flagged...
v0.2.0-beta.6
Fixed
- Fixed transcript reruns failing validation when overlapping decoder-window words end out of start-time order.
v0.2.0-beta.5
Fixed
- Long speech turns admitted by the interjection-sensitive VAD are decoded in bounded overlapping windows, reducing Parakeet's silent omissions from 20–25-second utterances without reverting the short-interjection VAD thresholds. Dense merged-fallback windows also retain deterministic seam ownership when adjacent decodes disagree completely.
- Production ExApp deploys now use collision-safe temporary manifest paths and clean them after settled runs, so a stale file from another operator cannot block an upgrade.
- ExApp startup logs now report the effective publication sink instead of contradicting a resolved Nextcloud Files destination with a later
localline.
v0.2.0-beta.4
Changed
- GPU transcription upgrade: existing portable/plain-image ExApp installations continue to capture and preserve Talk audio, but operator-managed transcription now enters
build/blockedinstead of using CPU ASR. Install the matching-cudaimage on a GPU deploy daemon, then choose Rerun for each preserved blocked job in Cassini Admin.
Fixed
- Short interjections now retain their real timeline position and speaker attribution. Transcription drains final VAD audio, recovers more subsecond turns, and rejects near-silent decoder hallucinations without discarding speech whose decoded timestamp leads its waveform.
- Transcript construction no longer applies duplicate timeline offsets or retains both sides of a mixed-track fallback, and genuine final words emitted in decoder or VAD padding are preserved at the real audio boundary.
- Meeting duration now comes from playable audio without discarding source-container provenance or preallocating PCM from corrupt duration metadata.
- GPU readiness failures are reported explicitly, and generated artifacts record the effective ASR device.
v0.2.0-beta.2
Added
- Data-processing & privacy note. A new
docs/privacy.mddocuments what
Cassini stores, where it lives, the single optional step (LLM cleanup and
summaries) that sends data off your infrastructure, and what deletion and
uninstall remove. - The App Store listing now shows what Cassini is: screenshots of the meeting viewer and the admin control panel, an
aicategory alongsideintegration, and user/admin documentation links. A screenshot-less entry reads as abandoned; the listing now presents the product. - Meetings now know which conversation they came from, and you can list and filter by it. Until now a recording's room name was flattened into its title and the room's identity was discarded, so there was no way to ask for "the meetings from this room". Every recording made from here on carries a room id and display name in its portable
.opusand in the published catalog, and the CLI gains two ways to use that:cassini meetings roomslists the conversations your account has readable recordings from, andcassini meetings listaccepts--room,--fromand--to(all optional, and they combine —--from 2026-08-01 --to 2026-08-31means all of August). The agent skill for reading meetings now narrows by room and date before listing, instead of fetching the whole archive and guessing. - A new setting,
CASSINI_ROOM_ID_PEPPER. The room id published on a meeting is a one-way derivation of the Talk conversation's identity, never the conversation's own token — for a public conversation that token is also the link that joins it, and every signed-in account can read those recordings. Setting this to a stable secret is what makes the derivation resistant to being reversed: a Talk token is short enough that an unpeppered one can be recovered by working through the possibilities offline. Choose it once — changing it changes every room id, and meetings already published keep the ids they were written with, so a room splits in two until you merge it again.
Changed
- The Nextcloud App Store description now discloses that recording and
transcription stay on your infrastructure and that only the optional
external-LLM step sends data to a third party. - Recordings published before this update can be given their room back, from the files themselves. Run
./scripts/backfill-catalog-rooms.shonce, by hand, on the host where the app container runs; it reports what it would change and writes nothing until you add--apply. It recovers each meeting's room name from its published recording and derives a room id from that name, because those files never carried the conversation's token and it cannot be recovered from them. - Because of that, one conversation can appear as two rooms — one identified from its token, for recordings made since the update, and one identified from its name, for everything older. Cassini cannot prove they are the same conversation: two conversations can share a display name, and a room can be renamed between recordings. When you know they are the same,
./scripts/reattribute-catalog-room.sh --from <id> --to <id>merges them, and like the backfill it shows you every meeting it would move before it writes anything.
Fixed
- Published recordings can be deleted again. Cassini created its
CassiniTeam folder with a Group Folders flag that, on recent versions, made every file in it permanently undeletable and un-moveable — by everyone, including the Cassini account and Nextcloud administrators. New installations no longer set it, and nothing else changes: who can read a recording is decided by the same per-recording permissions as before. An existing folder cannot be repaired automatically, because Group Folders offers no way to clear the flag after creation; the app's status page now reports the condition and what it costs instead of leaving it invisible. - Setting up recordings no longer fails on an installation that is already correct. On a Nextcloud that requires password confirmation for administration, an administrator has to create the recordings folder by hand — after which Cassini re-applied the same group and permission settings anyway, was refused, and reported the setup as broken even though nothing was wrong. It now checks what is already in place and only writes what is missing.
- The app's status page no longer reports a completed setup as healthy when a step failed along the way.
- Setting up the recordings folder now reports why it failed. The Group Folders API answers a refused request with
HTTP 200and the failure recorded only inside the response body, so Cassini read a refusal as a success and then failed while decoding it — reporting a JSON error that named neither the problem nor the cause. On Nextcloud installations that require password confirmation for administration, every one of these requests is refused, and the app's status page said only that setup was incomplete. Cassini now reports the actual reason and, when it cannot proceed, names theocccommands that create the Team folder by hand — which it then adopts on the next enable. - Setting up recordings no longer fails on Nextcloud installations that require password confirmation for user administration. Cassini creates a
cassiniservice account and its group when the app is enabled; on Nextcloud 34 (and any instance enforcing password confirmation) those calls are refused, because an app has no browser session in which to confirm a password. An administrator can create the account and group by hand — but Cassini did not check whether they already existed before trying to create them, so a correctly prepared installation looked identical to an empty one and setup failed permanently. Cassini now looks first, adopts what is already there, and when it genuinely cannot proceed it names theocccommands that fix it.
v0.2.0-beta.1
Added
- Cassini now makes the Talk signaling internal secret — the one input required for invisible HPB-internal recording that cannot be auto-provisioned — easy to know and set. When
CASSINI_TALK_SIGNALING_INTERNAL_SECRETis missing, the operator logs an actionableWARNINGat startup andGET /operator/statusreturns asignaling_internal_secret_hint, instead of only failing when a recording is attempted. The External Apps deploy-option description anddocs/exapp-install.mdnow explain where to find it (e.g.docker exec nextcloud-aio-talk printenv INTERNAL_SECRETon Nextcloud All-in-One, or the signaling server's[clients] internalsecreton a standalone HPB) and how to set it. - Zero-config Talk recording secret:
CASSINI_TALK_RECORDING_SECRETis now optional. When it is not supplied, the operator generates a secret on first start and persists it on the AppAPI volume (surviving restart and app update), so installing Cassini from the App Store no longer requires inventing or copying a shared secret. An explicitly supplied secret still wins and is treated as externally managed. - New ADMIN-only provisioning endpoint
operator/talk/provisioningreturns the ready-to-applyspreedrecording_serversvalue (backend URL + secret), so an admin can register Cassini as Talk's recording backend in one step without ever handling the secret by hand. - The operator now delivers published recordings into Nextcloud Files. Each published meeting's portable
.opusis uploaded before its entry is added to thecatalog.jsonindex under the canonicalCassini/Recordings/folder. The in-Nextcloud viewer then serves its catalog and per-meeting audio authoritatively from Files (with its CSP and routes unchanged), while standalone development keeps its local source. Mutable catalog responses bypass browser caches, and the embedded viewer refreshes its meeting list on focus and every 15 seconds. Delivery failures are visible rather than silently masked by local fallback. Recordings are delivered as.opusonly; Talk's recording/storeendpoint (which rejects.opus) is not used. This first pass is public and writes as the Nextcloud admin; per-user/per-group access and a dedicated service account follow. - Recordings now belong to a dedicated
cassiniaccount, created automatically when the app is enabled. Previously they were owned by the Nextcloud administrator, which tied every recording to a human account that may be renamed, handed over or removed, and gave the identity holding all the recordings full administrative rights over the instance. Setup work — creating the recordings folder, its groups and the service account itself — is still performed as an administrator; storing and reading recordings is not. The account owns and serves the recordings; there is no other mode. - Per-participant access control for recordings. The operator freezes each recording's audience to the Talk room's participants at publish time — it grants read-only Nextcloud advanced-ACL (
nc:acl-list) to each local participant (users, groups, circles) on the meeting's.opus; guests, email and federated participants are skipped. The in-Nextcloud viewer is then served per caller: the meeting list is built directly fromcatalog.json, filtered to the.opusfiles visible in the caller's own Nextcloud scan, and each recording is fetched as the caller so Nextcloud enforces access — a recording a user is not granted never appears and cannot be played. The read path fails closed (an empty list, never the full archive) on any error. Access is editable afterwards through Nextcloud's own sharing/ACL tools. Recordings are owned by a dedicatedcassiniservice account, which also manages their ACLs; privileged setup is performed separately as a Nextcloud administrator. Two native prerequisites must be enabled first — the Team folders (groupfolders) and Everyone Group (group_everyone) apps — after which Cassini provisions the recordings folder and ACL topology itself. Seedocs/exapp-nextcloud-recordings-permissions.md. - Automatic setup of the recordings directory and its permissions. Enabling the Cassini ExApp now provisions everything the model needs by itself, over Nextcloud's HTTP APIs — no manual
occTeam-folder setup. On the enabled edge the operator creates theCassiniTeam folder with its default-deny advanced-ACL floor, gives Nextcloud's virtualeveryonegroup read and the narrowcassiniowner group full permissions, delegatescassinias ACL manager, migrates any per-recording permissions left by earlier versions, applies the root container ACL (so the owner can write and every account can traverse), and creates the canonicalCassini/Recordings/meetingscollections. Because the audience group is virtual, every account has the mount from the moment it is created — there is no membership sweep and no convergence delay. Two native prerequisites remain, neither of which an external app can install for itself: the Group folders ("Team folders") app and the Everyone Group (group_everyone) app. The local harness enables both automatically. - The recordings setup now reports its own health. Setting up the recordings folder used to fail into a log line, leaving an app that looked healthy while showing nobody their recordings.
GET /operator/statusnow reports whether the Team folder, its permissions and the universaleveryonemount are actually in place, names the step that failed and the likely cause, and returns 503 when the app cannot serve recordings — so a missing Group folders or Everyone Group app is visible instead of silent. - The production deploy is now a committed, parameterised script
(ops/deploy/deploy-exapp.sh) driven by an inventory file, instead of a
one-off on a maintainer's laptop. It is dry-run by default, reads the live
rollback target before touching anything, and refuses to deploy a moving tag
(latest,latest-cuda, …) or an image whose child manifests are missing
from the registry. CPU and GPU deployments differ by one inventory value, the
daemon's compute device — there is no separate GPU path. docs/exapp-update-constraints.mdstates what the Nextcloud admin UI's
Install and Update buttons can and cannot deliver — most importantly that
deploy environment is applied at container creation only, so a release adding
a required environment variable is a breaking change the Update button
cannot deliver.- A retention policy for per-run working files,
--artifact-retention/CASSINI_ARTIFACT_RETENTION. Disk use grew with every recording and every rerun and nothing ever reclaimed it. The default (sealed) removes a completed run's working copies — all of which are duplicated in the operator's canonical library or were temporary staging — and keeps its sealed meeting file and every log.supersededonly removes runs that a rerun has replaced;allkeeps everything, as before — except a delivered run's staging copy, which is always removed once the recording reaches its destination so that no full copy of it is left outside the access model. Nothing removes the last copy of anything, and published recordings are never touched. - Installing Cassini now tells you whether recordings will actually work. Setting up the recordings folder used to fail into a log line, so an app whose setup had not completed still enabled, answered every request, and showed nobody their recordings.
GET /operator/statusnow reports that setup underrecordings_accessas provisioned, unavailable (a named thing is missing — it says which app to install, or which setting to fill in), degraded (a setup call failed), or unknown (the app was restarted without being re-enabled), and returns 503 for anything that is not working. The two Nextcloud apps Cassini cannot install for itself — Team folders and Everyone Group — are now checked by name before setup starts, and reported individually. GET /operator/setup— a USER-level route reporting whether recordings can be served (okandstate) and nothing else. It is what lets a non-admin be told that an install was never finished; the diagnosis (which step, which administrator, which paths) stays on the admin-only/operator/status. It answers 200 in every state, including the broken ones, so a browser can tell "Cassini is not set up" apart from "the app is down" —/operator/statusremains the endpoint that answers 503 for monitoring. Existing installs pick the route up when the app is next registered or updated.- A one-shot migration for recordings published by an older version. Recordings made before Cassini stored them in Nextcloud Files live only on the app's own storage, and nothing publishes them again.
./scripts/backfill-nc-files.shmoves them across, run once by hand after updating;--dry-runreports what it would move without changing anything. Migrated recordings are readable only by the Cassini service account, because the audience a recording had when it was published cannot be recovered afterwards — grant access from the Files app, or use--publicto make them readable by every signed-in account as they were before Cassini had access control. The migration changes nothing when Nextcloud Files already holds recordings, or when there is no older archive to move, so it is safe to run when you are unsure whether it is needed — and skipping it entirely is a supported choice: the old recordings simply stay where they are and everything published from now on works normally. - Read your meetings from outside Nextcloud:
cassini meetings. An agent or script on a laptop or container can now list the recordings a Nextcloud account is allowed to read (cassini meetings list), download one as its single portable.opus(cassini meetings fetch <id>), or print one as readable context — transcript plus generated summary, as markdown or JSON (`cassini meetings context ...
v0.2.0-alpha.4
Added
- Added a unified
cassini dev stack up|down|status|planworkflow with explicit--servicestopology modes, including support for remote harness runs and a visible stack plan. - Restored the operator (recording-control) UI inside the single "Cassini" Nextcloud app as an admin-gated surface (D-420 V3): admins now get a Browse | Operator top nav, while non-admins keep the browse-only experience unchanged. The active surface is deep-linkable and back/forward-navigable via the URL fragment (
#surface=operator), with no pathname router. Whether the operator surface is shown is decided by probing the operator API (403 → hidden); the operator JSON API stays admin-only ininfo.xml(the real security boundary), so a non-admin can never drive recordings even if they force the surface. - Added negative controls (
harness/bin/test-session-artifact-packet-guard.sh,harness/bin/test-av-drift-pair-requirement.sh) that feed the real verifiers synthetic artifacts and assert they go red on captures with no media and green on healthy ones. They run on every pull request in the fast lint job — no Docker, no stack, no media tooling — so the guards' behavior is demonstrated rather than assumed. cassini dev stack plan,up, and destructivedowncommands now surface non-fatal warnings for ignored settings, surprising recording paths, unreachable private media addresses, and resource-removing lifecycle options.
Changed
- Standardized dev and CI harness execution around the same stack commands so e2e entrypoints and manual flows now use explicit, deterministic startup/topology flags.
- Reworked the viewer to load meeting data through a single
DataProviderseam instead of hardcoded static fetches, decoupling the viewer UI from its data source (groundwork for unifying the viewer and operator into one in-Nextcloud app). No user-visible behaviour change. - Collapsed the two Cassini Nextcloud top-menu entries ("Cassini" viewer + "Cassini Admin" control-panel) into a single "Cassini" entry backed by one in-Nextcloud app (
cassini-app, which consumes the viewer as a layer). Every logged-in user gets the meeting-browsing app; the operator/recording-control surface returns as an admin-gated surface inside the one app in a follow-up (D-420 V3), and the operator JSON API stays admin-only throughout (unchanged security boundary). No change for viewer users. - Split the in-Nextcloud viewer into a shell (
App) plus reusableMeetingListandMeetingViewcomponents — the app-shell layer for unifying the viewer and operator into one in-Nextcloud app (MeetingViewis also the future standalone single-meeting share surface). No user-visible behaviour change. - Release CI now verifies that the exact CPU ExApp image can be installed through AppAPI/HaRP, record a Talk call, and publish a non-empty viewer transcript before image or harness changes merge.
- Reworked the Cassini operator panel for the in-Nextcloud audience it is now shown to: the recordings list and run detail work as a master/detail pair, each run's attempts collapse into accordions so a long retry history opens at a readable summary, starting a recording is an on-demand composer rather than a permanently parked URL field, and the API-implementation captions are replaced with plain descriptions.
- Rebuilt the connection indicator to state the connection first: a one-word status (Live / Connecting / Reconnecting / Offline) with the refresh cadence beside it, replacing a label that never said whether the panel was connected.
- Restyled the transcription settings as three matching sections — detected hardware, quality, and device and model overrides — shown side by side on wide screens, with the overrides no longer hidden behind a collapse.
- You can now link directly to a recording: the selected run is kept in the URL, and the back button returns to the list.
Fixed
- Fixed harness teardown and bring-up edge cases so repeated runs no longer leave stale containers/networks or duplicate
signaling-public-proxyaliases. - Cassini shell polish (D-420 review follow-ups): switching between the Browse and Operator surfaces now keeps the open meeting in the URL (deep-link no longer dropped); the operator surface uses the same light/dark theme as browse (stored preference, then OS) instead of only the OS setting; and the in-Nextcloud mount targets the innermost
#contentmore robustly. - Added the missing
operator/settingsproxy route so the operator surface's STT-quality Settings panel works through the Nextcloud AppAPI proxy (it previously had no route and would have 403'd/404'd), and dropped the now-deadcontrol-panelproxy routes left over from the collapsed second entry. - Talk recorder: stabilized subscriber negotiation behind intermittent empty/blank recordings ("no remuxable streams found in session artifact", silent MKVs, and 0-word transcripts). A fresh in-call participant previously caused two empty-SID
requestoffermessages; the second could make the MCU tear down and recreate its Janus subscriber, rotating the SID while the first offer was still being answered. Initial and exhausted-peer recovery now issue exactly one request, repeated in-call snapshots keep the valid response throttle, and inbound offers cannot trigger recovery in front of themselves. The recorder also sends its answer without waiting for full ICE gathering, queues trickled candidates behind the matching answer, and emits end-of-candidates only after gathering completes (D-454). - Stopping a recording no longer fails with "job is not stoppable" when the operator has just reported the job as running. The stop registration now brackets the whole time the job is advertised as recording, so a stop is accepted from the moment the job goes running until it leaves the record stage, and one arriving before the recorder has finished starting up is delivered as soon as it does.
- A stop requested after the recording has already ended on its own (while the operator is still uploading to Talk and queueing the build) is now accepted and recorded as a stop request instead of being rejected; the job's stop reason continues to report why the recording actually ended.
- The release pipeline's required "Faithful installed ExApp" gate now decides whether to run from a deny-list (run unless a change is purely docs/notes/assets) instead of an allow-list of enumerated product paths, so a pull request touching a build input nobody thought to enumerate can no longer report the required check green without actually building and recording.
- Talk recorder: closed two gaps that could still lose media on a call the recorder appeared to be recording normally. A participant whose handshake completed but whose media never arrived was left untouched for up to ~45 seconds, so short calls (a ~30s 1:1) could still finalize as empty ("no remuxable streams"); such a peer is now rebuilt about 12 seconds after its answer, but only while its connection is not actually established, so a slow-but-healthy participant is never torn down. A participant who turned their camera on mid-call could also be wedged permanently by a dropped signaling answer — their audio kept recording while the newly added video was silently lost for the rest of the call; the recorder now retransmits that answer as the signaling connection recovers, within a bounded budget, instead of skipping the participant forever (D-509).
- Fixed the CI session-artifact guard passing recordings that captured no media. It verified headers and index framing but never counted packets, so a capture with a valid header and a 0-byte index reported "session artifact verification passed" — the exact empty-capture class (D-454) the per-PR e2e leg is relied on to catch. The guard now walks each stream log and requires real RTP media packets per captured stream, counted kind-aware so a stream carrying only RTCP no longer passes for having non-empty bookkeeping.
- Fixed the A/V drift check reporting success without measuring anything. It exited 0 when no audio/video pair was found, and also when pairs were found but every one was too short to compare — printing "av drift check passed" after zero comparisons. Drift runs now require at least one pair to be actually compared, and the rejoin leg, which legitimately records video-less, opts down explicitly.
- Fixed the e2e leg never producing a drift pair the check could measure. The publisher bots play the sample fixture once and stop at its end, and the default fixture is 15s — capping the measurable pair at ~11s against the 15s minimum regardless of the configured publish window. The leg now prepares a fixture sized to the publish window. The fixture is also rendered with a keyframe at least every 0.5s (libvpx's default 128-frame interval delayed decodable video up to ~4.3s behind audio, which the elapsed-difference metric misread as drift), and the leg's tolerance carries a documented budget for the remaining structural skew.
- Fixed panels, cards and form fields losing their borders and shadows inside Nextcloud, which left content areas with nothing to separate them.
- Fixed high-contrast themes leaving those same areas undefined, and the selected recording unmarked.
- Fixed Nextcloud's app chrome applying twice, which added a second header-height gap, doubled the side margins, and clipped the bottom of the app.
- Fixed the shell tab bar, the operator scrollbar gutter, and the detected-hardware values rendering with the wrong colours under some themes.
- Fixed the recordings list not appearing on narrow screens.
- Fixed the settings Save button offering to save when nothing had changed.
- Fixed the meeting filter drawing a second focus ring inside its own.
- Fixed the meeting load-in fade stalling and then snapping into place.
v0.2.0-alpha.3
Added
- Cassini marketing and docs microsite (
cassini-microsite/): homepage, changelog page, and docs section with sidebar navigation. - The Cassini microsite is now published at https://gocassini.codemyriad.io.
- Release tooling for the Nextcloud App Store: a version-ladder CLI
(scripts/release-version.sh), changelog folder (scripts/fold-changelog.sh),
local release-prep orchestrator (scripts/prepare-release.sh), App Store
package builder/validator, and a manual Release workflow that signs and
publishesgocassini.tar.gz. Seedocs/release.md.
Changed
- Nextcloud toolbar icon updated to the Cassini brand mark with white fills and a tighter viewBox for correct rendering in NC ExApp containers.
- Viewer restyled with the Saturn theme (ice blue and Saturn gold) and now shows a favicon. A previously saved light/dark choice resets once and follows your system setting until you set it again.
Fixed
- Documentation pages on the microsite now show the site footer.
- The microsite changelog timeline no longer draws a trailing line below the last entry.
- Viewer meeting dates no longer append a timezone (e.g. "GMT+1") they cannot actually justify. Meeting date labels carry no timezone, so a UTC-derived time could be shown an hour off with a wrong-but-confident zone suffix. The viewer now renders the label's own wall-clock time and makes no timezone claim.
- The operator control panel no longer flashes to loading spinners every couple of seconds while watching an active recording. Background refreshes (the 2s polling fallback and event-stream reconnects) now update the jobs list and run detail in place instead of re-showing the first-load placeholder each tick.