Skip to content

feat: Each recording knows its meeting, and an assistant lists the recordings of a meeting EXO-91998 - #429

Merged
bmestrallet merged 9 commits into
feature/ai-contributionfrom
feat/EXO-91998-meeting-recordings
Oct 27, 2026
Merged

bmestrallet merged 9 commits into
feature/ai-contributionfrom
feat/EXO-91998-meeting-recordings

Conversation

@bmestrallet

@bmestrallet bmestrallet commented Oct 23, 2026 •

Copy link
Copy Markdown

Why
After a meeting held in eXo's video meeting, its recordings must be found from the meeting itself, by the agenda and by the assistant, with the viewer's own rights (EXO-91998; Tech Spec note 55129 §2.2, §2.3, §3.2, §3.5).

How

  • uploadFile: the recording upload carries its own limit, exo.webconferencing.recording.uploadLimitMB (0 = unlimited by default), registered under a random upload id and removed in finally; the failure path now passes the upload info, so a refused recording is notified.
  • saveFile: new optional mixin exo:callRecording (conf/webconferencing-nodetypes.xml) stamps the call id, provider, owner, title, start and save dates; exo:title becomes « call title – date », the node name is unchanged.
  • CallProvider.findCallId(url): new optional SPI method, null by default.
  • WebConferencingService.getCallRecordings / getCallRecordingsByConference, in storage/CallRecordingStorage: call-level check (space membership or super manager; owner or participant), then a JCR-SQL query run as the viewer (made the current conversation state around the session), merged with the unstamped files named after the call; exo.webconferencing.recording.occurrenceGraceHours (12) is added to to. Each recording carries startedDate, read from the recorder's file name in the recorder's time zone, exo.webconferencing.recording.recorderTimeZone (default UTC; an invalid value falls back to UTC with a warning): Jibri writes its JVM's local time with no zone, and a local time repeated when clocks go back reads as its earlier instant.
  • REST: GET /webconferencing/rest/calls/{callId}/recordings and GET /webconferencing/rest/recordings/byConference?url=, optional from/to; fixed message codes on 400/403/404/500, Cache-Control: no-store.
  • MCP tool get_meeting_recordings(conference_url, recorded_from?, recorded_to?), read-only: MeetingRecordingMcpTool, its ai-tool-definitions.json entry, mcp-server-tools as a provided dependency and the -parameters flag. A date it cannot read (RFC3339 or yyyy-MM-dd only) is refused with a message naming the argument and the accepted formats.

The jitsi PR implementing findCallId compiles once this one is merged and published.

Upgrade / Ops: where the Jibri JVM does not run in UTC, set exo.webconferencing.recording.recorderTimeZone to its zone (e.g. Europe/Paris); otherwise every recording's start is shifted by that offset. Recording uploads are no longer capped by exo.uploadLimit (200 MB by default): set exo.webconferencing.recording.uploadLimitMB to bound them.

Knowledge: Meeds-io/eng-standards#695, Meeds-io/eng-standards#696

This change is classified N1 at 842b1090 — its approver must be an Archi/Dev who knows it is N1, not an approval on AI review alone; author ≠ approver.

🤖 Generated with Claude Code

bmestrallet and others added 8 commits October 23, 2026 17:20
…cordings of a meeting - EXO-91998

The recording upload gets its own size limit (exo.webconferencing.recording.uploadLimitMB,
0 = unlimited by default) instead of the platform's generic one, which refuses the
recording of a long meeting.

Every recording saved from now on carries the new exo:callRecording mixin (call id,
provider, owner, title, scheduled start, recording date) and is titled after its
meeting and date; its file name is unchanged.

CallProvider gains an optional findCallId(url). WebConferencingService.getCallRecordings
lists a call's recordings with a JCR-SQL query in the viewer's own session, so the
repository's permissions decide what comes back, after checking that the viewer is a
member of the call's space (space and space-event calls) or a participant (other calls).
A recording saved before the mixin is found by its Jibri file name, and merged with the stamped ones. The end of a range
is extended by exo.webconferencing.recording.occurrenceGraceHours (12).

Spring MVC REST: GET /webconferencing/rest/calls/{callId}/recordings and
GET /webconferencing/rest/recordings/byConference?url=, both with optional from/to.
MCP tool get_meeting_recordings (read-only), the add-on's first, with its own
ai-tool-definitions.json and the -parameters compiler flag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s failure - EXO-91998

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…der gives its file - EXO-91998

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…XO-91998

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e calling thread runs as - EXO-91998

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ng that defaults to UTC - EXO-91998

Jibri names a recording after its JVM's local time, with no zone.
exo.webconferencing.recording.recorderTimeZone names that zone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…me zone - EXO-91998

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed - EXO-91998

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@bmestrallet

Copy link
Copy Markdown
Author

Self-review close-out (AI, rounds 2–4 of the delivery #429 · #430 · #431 · jitsi#173 · agenda#1241 · agenda#1242 · ppd#648)

Rounds after the first fixes: delivery checks ×3, a fresh reviewer (round 2), a fresh closing reviewer (rounds 3–4, clean).

# Sev Finding Status
D1 🟡 Jibri writes the time in a recording's file name in its JVM's local zone (FileSink.kt:38, LocalDateTime.now()), not UTC ✅ ccefae9a: exo.webconferencing.recording.recorderTimeZone (default UTC, invalid → UTC + warning); pinned, mutation-verified
D4 🟡 configuration.xml line endings converted in the fix commit ✅ restored, 3-line diff
D5 🟢 DST overlap unstated ✅ Javadoc + aRepeatedLocalTimeReadsAsItsEarlierInstant
R1 🟡 startedDate unasserted past recordingStart; zone wiring untested ✅ 335242c5; mutants at the call site (UTC, null) both fail the suite
R2/F1 🟡 Body missed recorderTimeZone, and that uploads are no longer capped by exo.uploadLimit ✅ body: How + Upgrade / Ops
R5 🟢 Orphaned test Javadoc, fully-qualified names ✅ 335242c5
F3 🟢 MeetingRecordingModel missing @param startedDate ✅ 388ac29d
R4 🟡 Tech Spec note still says UTC ➖ spec resync at close-out

Verified conform (closing reviewer, in source): the read as the viewer, traced through the JCR engine (SessionProvider.getSession → RepositoryImpl.login → BaseAuthenticator.authenticate() = current state) and pinned by a test that sees the login identity; the random upload id and its limit removed on every path; the JCR-SQL statement parsed by the repository's own parser; call-level access before the repository ACL; the MCP tool's identity from the request.

Tests at 335242c5: mvn -o verify -Pcoverage -pl services: 66 run, 0 failures, 0 errors; all coverage checks met. 388ac29d adds Javadoc only.

This change is classified N1 at 388ac29d — its approver must be an Archi/Dev who knows it is N1, not an approval on AI review alone; author ≠ approver.

…recordings of a relative date - EXO-91998

get_meeting_recordings answered a date it could not read, such as
"yesterday", with the REST layer's bare message code, so the model had
nothing to correct its call with. The tool now refuses it with a message
naming the argument, the accepted formats with an example, and asking the
model to resolve the relative expression itself; the date arguments'
descriptions state the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 9118d3e2e69bc598b8bd7167ccab65c2f7c04b34)
@bmestrallet
bmestrallet merged commit ea439f8 into feature/ai-contribution Oct 27, 2026
1 check passed
@bmestrallet
bmestrallet deleted the feat/EXO-91998-meeting-recordings branch October 27, 2026 11:38
bmestrallet added a commit that referenced this pull request Oct 27, 2026
#432)

## Why
EXO-92126: the people who attended the call a recording was made in are
the first candidates for who its speakers are.

## How
- `WebConferencingService.getRecordingAttendees(documentId)`: the
attendees the recording's call stored; null (no attendee list) when the
call lists none or more than `MAX_RECORDING_ATTENDEES` (20), is a space
call or a meeting that invited a space, or is no longer known.
- `CallRecordingStorage.findCallId(documentId)`: the call a stamped
recording was made in, read as the system.
- Read by the enterprise glue (platform-private-distributions,
EXO-92126), so documents does not depend on web-conferencing.

Stacked on #429 (EXO-91998).

Knowledge: Meeds-io/eng-standards#756

This change is classified N1 at head 6d4c838 (a system read of call
data, part of the EXO-92126 delivery) — its approver must be an
Archi/Dev who knows it is N1, not an approval on AI review alone; author
≠ approver.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
bmestrallet added a commit that referenced this pull request Oct 27, 2026
…nda EXO-92121 (#434)

## Why
A meeting recording opened in Documents shows its length and transcript,
but not the meeting it was recorded in (EXO-92121). The details panel
now shows « Meeting: <event title> · <date> », opening the event in the
agenda, or the occurrence of a recurring one.

Stacked on #429 (the recording stamp `exo:callRecording` and
`CallRecordingStorage`).

## How
- `GET /webconferencing/rest/recordings/documents/{documentId}/call`:
reads the file in the user's own JCR session (`readAs`, shared with
`findRecordings`) and answers its stamp: `callId`, `providerType`,
`ownerType`, `startedDate`, `recordedDate`, plus `graceHours`. A link to
a recording answers its target's call. A file the user cannot read, in
the trash, or without the stamp (any other file, a recording saved
before stamping) answers 204. Every answer is `no-store`.
- `occurrence-grace-hours` is capped at 168 h (with a WARN), the longest
the agenda takes, so both read the same occurrence.
- The line is a Vue component in a new
`WebConferencingDocumentsExtension` module (`DocumentsGRP`), contributed
to the details rows (`DocumentsInfoDetails` / `documents-info-details`,
exoplatform/documents#2130). It shows the event's title on a first line
and its date on a second one, its label aligned with the title
(`align-top`). Only for an agenda-event call (`ownerType`
`space_event`), it asks exoplatform/agenda#1244 for the event at the
recording's save date with that grace; it shows nothing otherwise. No
provider script is needed on the page.
- Labels in `Visio_en.properties` only.
- Scope: the line shows in the Documents app only (decided on the task);
the details panel opened elsewhere (stream, notes, favorites) does not
load the module. A call not started from an agenda event shows no line.

Tests: `CallRecordingStorageTest`,
`WebConferencingServiceRecordingsTest`, `CallRecordingRestTest` (new
cases mutation-verified); full `web-conferencing-services` suite with
`-Pcoverage`: 82/82; webapp build and lint clean.

Knowledge: Meeds-io/eng-standards#761
Classification: N1 (a client-supplied document id read in a swapped
ConversationState behind a new REST) — computed at a520552.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
bmestrallet added a commit that referenced this pull request Oct 27, 2026
…-92130 (#433)

## Why
EXO-92130: when an assistant lists a meeting's recordings, each
recording shows its chapters and its speakers, so that the assistant
picks the right recording and chapter before reading any transcript.

## How
- `get_meeting_recordings` gives, for the 10 newest recordings it lists,
the chapters of the transcript (start in seconds, time, title) and the
speakers' names in the order they first speak: the person's full name,
the guest's name, or `Speaker N` (`RecordingOutlineReader`,
`RecordingChapterModel`, `RecordingOutlineModel`). A recording without
them is listed as before (`NON_EMPTY`).
- The outline is read from Documents
(`DocumentFileService#getTranscriptionChapters`,
`#getTranscriptionSegments`, `#getTranscriptionSpeakers`) with the
listing user's identity, so Documents' ACL applies. Chapters and
speakers are read separately: a failure on one never takes the other
away, and a failure on both lists the recording without an outline.
- `documents-api` is a `provided` dependency, and the reader is a bean
only when Documents is installed (`@ConditionalOnClass` by name,
`ObjectProvider` in the tool): without Documents, recordings are listed
as today.
- Chapters are numbered as Documents numbers them (title and start
present, stored order), and the tool description sends the assistant to
`get_transcript_excerpt` (documents) with the recording's `documentId`
and the chapter's number.

## Tests
- `RecordingOutlineReaderTest`, `MeetingRecordingMcpToolTest`: 22 tests,
0 failures, compiled against a `documents-api` carrying #2121 and #2122
built locally; this PR's CI needs a published `documents-api` with both,
so it waits for those PRs.
- On the shared test server, through an MCP client (Claude Code)
connected to the server's MCP endpoint, not the EVA chat:
`get_meeting_recordings` on the « Demo ACME recordings » meeting listed
its 13 recordings, the ones with chapters showing them. Asked in the EVA
chat by a recording's title, EVA hands the question to its Search or
Content specialist and never reaches this tool: routing meeting
questions is EXO-92131's.

The tool description changed: an instance that already imported it shows
the new text after one start with `meeds.mcp.tools.forceReimport=true`.

Stacked on #429 (EXO-91998, `get_meeting_recordings`).

Knowledge: pending — EXO-92130

This change is classified N1 (the user's identity resolution drives the
ACL'd Documents reads, recall-first) at 4093eb0 — its approver must be
an Archi/Dev who knows it is N1, not an approval on AI review alone;
author ≠ approver.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
exo-swf pushed a commit that referenced this pull request Oct 28, 2026
#432)

## Why
EXO-92126: the people who attended the call a recording was made in are
the first candidates for who its speakers are.

## How
- `WebConferencingService.getRecordingAttendees(documentId)`: the
attendees the recording's call stored; null (no attendee list) when the
call lists none or more than `MAX_RECORDING_ATTENDEES` (20), is a space
call or a meeting that invited a space, or is no longer known.
- `CallRecordingStorage.findCallId(documentId)`: the call a stamped
recording was made in, read as the system.
- Read by the enterprise glue (platform-private-distributions,
EXO-92126), so documents does not depend on web-conferencing.

Stacked on #429 (EXO-91998).

Knowledge: Meeds-io/eng-standards#756

This change is classified N1 at head 6d4c838 (a system read of call
data, part of the EXO-92126 delivery) — its approver must be an
Archi/Dev who knows it is N1, not an approval on AI review alone; author
≠ approver.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
exo-swf pushed a commit that referenced this pull request Oct 28, 2026
…nda EXO-92121 (#434)

## Why
A meeting recording opened in Documents shows its length and transcript,
but not the meeting it was recorded in (EXO-92121). The details panel
now shows « Meeting: <event title> · <date> », opening the event in the
agenda, or the occurrence of a recurring one.

Stacked on #429 (the recording stamp `exo:callRecording` and
`CallRecordingStorage`).

## How
- `GET /webconferencing/rest/recordings/documents/{documentId}/call`:
reads the file in the user's own JCR session (`readAs`, shared with
`findRecordings`) and answers its stamp: `callId`, `providerType`,
`ownerType`, `startedDate`, `recordedDate`, plus `graceHours`. A link to
a recording answers its target's call. A file the user cannot read, in
the trash, or without the stamp (any other file, a recording saved
before stamping) answers 204. Every answer is `no-store`.
- `occurrence-grace-hours` is capped at 168 h (with a WARN), the longest
the agenda takes, so both read the same occurrence.
- The line is a Vue component in a new
`WebConferencingDocumentsExtension` module (`DocumentsGRP`), contributed
to the details rows (`DocumentsInfoDetails` / `documents-info-details`,
exoplatform/documents#2130). It shows the event's title on a first line
and its date on a second one, its label aligned with the title
(`align-top`). Only for an agenda-event call (`ownerType`
`space_event`), it asks exoplatform/agenda#1244 for the event at the
recording's save date with that grace; it shows nothing otherwise. No
provider script is needed on the page.
- Labels in `Visio_en.properties` only.
- Scope: the line shows in the Documents app only (decided on the task);
the details panel opened elsewhere (stream, notes, favorites) does not
load the module. A call not started from an agenda event shows no line.

Tests: `CallRecordingStorageTest`,
`WebConferencingServiceRecordingsTest`, `CallRecordingRestTest` (new
cases mutation-verified); full `web-conferencing-services` suite with
`-Pcoverage`: 82/82; webapp build and lint clean.

Knowledge: Meeds-io/eng-standards#761
Classification: N1 (a client-supplied document id read in a swapped
ConversationState behind a new REST) — computed at a520552.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
exo-swf pushed a commit that referenced this pull request Oct 28, 2026
…-92130 (#433)

## Why
EXO-92130: when an assistant lists a meeting's recordings, each
recording shows its chapters and its speakers, so that the assistant
picks the right recording and chapter before reading any transcript.

## How
- `get_meeting_recordings` gives, for the 10 newest recordings it lists,
the chapters of the transcript (start in seconds, time, title) and the
speakers' names in the order they first speak: the person's full name,
the guest's name, or `Speaker N` (`RecordingOutlineReader`,
`RecordingChapterModel`, `RecordingOutlineModel`). A recording without
them is listed as before (`NON_EMPTY`).
- The outline is read from Documents
(`DocumentFileService#getTranscriptionChapters`,
`#getTranscriptionSegments`, `#getTranscriptionSpeakers`) with the
listing user's identity, so Documents' ACL applies. Chapters and
speakers are read separately: a failure on one never takes the other
away, and a failure on both lists the recording without an outline.
- `documents-api` is a `provided` dependency, and the reader is a bean
only when Documents is installed (`@ConditionalOnClass` by name,
`ObjectProvider` in the tool): without Documents, recordings are listed
as today.
- Chapters are numbered as Documents numbers them (title and start
present, stored order), and the tool description sends the assistant to
`get_transcript_excerpt` (documents) with the recording's `documentId`
and the chapter's number.

## Tests
- `RecordingOutlineReaderTest`, `MeetingRecordingMcpToolTest`: 22 tests,
0 failures, compiled against a `documents-api` carrying #2121 and #2122
built locally; this PR's CI needs a published `documents-api` with both,
so it waits for those PRs.
- On the shared test server, through an MCP client (Claude Code)
connected to the server's MCP endpoint, not the EVA chat:
`get_meeting_recordings` on the « Demo ACME recordings » meeting listed
its 13 recordings, the ones with chapters showing them. Asked in the EVA
chat by a recording's title, EVA hands the question to its Search or
Content specialist and never reaches this tool: routing meeting
questions is EXO-92131's.

The tool description changed: an instance that already imported it shows
the new text after one start with `meeds.mcp.tools.forceReimport=true`.

Stacked on #429 (EXO-91998, `get_meeting_recordings`).

Knowledge: pending — EXO-92130

This change is classified N1 (the user's identity resolution drives the
ACL'd Documents reads, recall-first) at 4093eb0 — its approver must be
an Archi/Dev who knows it is N1, not an approval on AI review alone;
author ≠ approver.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant