101 lines
4.1 KiB
Rust
101 lines
4.1 KiB
Rust
//! Pure scope-control logic (FR-MCP-3), split out from `mcp/mod.rs` so it's
|
|
//! unit-testable without a DB, a running server, or the `mcp` cargo feature.
|
|
//!
|
|
//! Design note (documented here because the schema doesn't (yet) carry a
|
|
//! per-meeting "expose this meeting" flag -- only `feature_briefs.exposed`
|
|
//! does, per `docs/03-data-model.md`): with `ExposeScope::Selected`, meetings/
|
|
//! transcripts/action-items have no selection mechanism to key off in this
|
|
//! milestone, so they are treated the same as `None` (deny) rather than the
|
|
//! same as `All` (allow) -- a privacy-conservative default consistent with
|
|
//! every other WA default (recording/sync/hosted-AI/MCP itself all default
|
|
//! OFF). Only `get_feature_brief` has real per-item selection today, via the
|
|
//! brief's own `exposed` flag (M1). A future "select meetings" UI/schema
|
|
//! addition should upgrade `Selected` for the other three tools without
|
|
//! changing this function's callers.
|
|
|
|
use crate::mcp::ExposeScope;
|
|
|
|
/// Whether `list_recent_meetings`/`get_transcript`/`get_action_items` may see
|
|
/// meetings at all under the current scope. `Selected` has no per-meeting
|
|
/// selection mechanism yet (see module docs) so it is conservatively treated
|
|
/// like `None`.
|
|
pub fn meetings_visible(scope: ExposeScope) -> bool {
|
|
matches!(scope, ExposeScope::All)
|
|
}
|
|
|
|
/// Whether a specific feature brief may be served. `exposed` is the brief's
|
|
/// own per-item flag (`feature_briefs.exposed`, set via `set_brief_exposed`).
|
|
pub fn brief_visible(scope: ExposeScope, exposed: bool) -> bool {
|
|
match scope {
|
|
ExposeScope::None => false,
|
|
ExposeScope::Selected => exposed,
|
|
ExposeScope::All => true,
|
|
}
|
|
}
|
|
|
|
/// Recordings (`.wav`) are never exposed unless explicitly allowed (FR-MCP-3),
|
|
/// independent of `ExposeScope`. None of the four MCP tools serve raw audio
|
|
/// bytes today, but a meeting that retained its recording (ADR-0009) is
|
|
/// treated as more sensitive-by-association: its transcript/action items are
|
|
/// also withheld unless the user opted into `expose_recordings`. Every tool
|
|
/// handler must call this for each candidate meeting -- there is no central
|
|
/// choke point (FR-MCP-3 "enforce in every tool handler").
|
|
pub fn recording_gate_ok(expose_recordings: bool, meeting_recorded: bool) -> bool {
|
|
expose_recordings || !meeting_recorded
|
|
}
|
|
|
|
/// Combined check a tool handler runs before including one meeting's data.
|
|
pub fn meeting_allowed(
|
|
scope: ExposeScope,
|
|
expose_recordings: bool,
|
|
meeting_recorded: bool,
|
|
) -> bool {
|
|
meetings_visible(scope) && recording_gate_ok(expose_recordings, meeting_recorded)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn none_hides_all_meetings() {
|
|
assert!(!meetings_visible(ExposeScope::None));
|
|
}
|
|
|
|
#[test]
|
|
fn selected_hides_meetings_pending_a_selection_mechanism() {
|
|
// Documented conservative choice -- see module docs.
|
|
assert!(!meetings_visible(ExposeScope::Selected));
|
|
}
|
|
|
|
#[test]
|
|
fn all_shows_meetings() {
|
|
assert!(meetings_visible(ExposeScope::All));
|
|
}
|
|
|
|
#[test]
|
|
fn brief_visibility_follows_the_exposed_flag_only_under_selected() {
|
|
assert!(!brief_visible(ExposeScope::None, true));
|
|
assert!(!brief_visible(ExposeScope::Selected, false));
|
|
assert!(brief_visible(ExposeScope::Selected, true));
|
|
assert!(brief_visible(ExposeScope::All, false));
|
|
assert!(brief_visible(ExposeScope::All, true));
|
|
}
|
|
|
|
#[test]
|
|
fn recordings_never_served_unless_explicitly_allowed() {
|
|
assert!(!recording_gate_ok(false, true));
|
|
assert!(recording_gate_ok(false, false));
|
|
assert!(recording_gate_ok(true, true));
|
|
assert!(recording_gate_ok(true, false));
|
|
}
|
|
|
|
#[test]
|
|
fn meeting_allowed_requires_both_scope_and_recording_gate() {
|
|
assert!(!meeting_allowed(ExposeScope::All, false, true)); // recorded, not opted-in
|
|
assert!(meeting_allowed(ExposeScope::All, false, false)); // not recorded
|
|
assert!(meeting_allowed(ExposeScope::All, true, true)); // opted-in
|
|
assert!(!meeting_allowed(ExposeScope::None, true, false)); // scope still wins
|
|
}
|
|
}
|