Feature chore bug 005 #19

Merged
iamdoubz merged 106 commits from feature_chore_bug_005 into main 2026-07-07 22:02:41 -05:00
Showing only changes of commit e0668e48df - Show all commits
+79 -2
View File
@@ -5,8 +5,8 @@
//! summary) are regenerable; retention never touches an in-progress meeting.
use crate::models::{
ActionItem, CalendarEvent, ImportedEvent, MeetingId, MeetingListItem, MeetingStatus,
Participant, SearchHit, SpeakerInfo, TranscriptSegment,
ActionItem, CalendarEvent, ImportedEvent, McpAccessEntry, MeetingId, MeetingListItem,
MeetingStatus, Participant, SearchHit, SpeakerInfo, TranscriptSegment,
};
use crate::paths;
use async_trait::async_trait;
@@ -297,6 +297,24 @@ pub trait Store: Send + Sync {
&self,
meeting_id: Option<&MeetingId>,
) -> Result<Vec<SyncJobRow>, StoreError>;
// ---- MCP server (Phase 10b, ADR-0011) ----
/// Confirmed action items for a meeting (FR-MCP-2 `get_action_items`) —
/// distinct from `list_pending_reminders` (which is filtered to
/// unfired-reminder rows across *all* meetings for the startup reconcile).
async fn list_action_items(&self, meeting_id: &MeetingId) -> Result<Vec<ActionItem>, StoreError>;
/// Appends one row to the audit log (FR-MCP-5). Every MCP tool read calls
/// this, regardless of whether the read was actually allowed to see
/// anything — the audit trail is "what an agent asked for", not just
/// "what it received".
async fn record_mcp_access(
&self,
tool: &str,
meeting_id: Option<&MeetingId>,
client: Option<&str>,
) -> Result<(), StoreError>;
/// Most recent audit rows first, optionally capped (`mcp_access_log` command).
async fn list_mcp_access_log(&self, limit: Option<u32>) -> Result<Vec<McpAccessEntry>, StoreError>;
}
/// SQLite-backed store. Migrations live in `migrations/` (`sqlx::migrate!`).
@@ -1484,6 +1502,65 @@ impl Store for SqliteStore {
};
Ok(rows)
}
async fn list_action_items(&self, meeting_id: &MeetingId) -> Result<Vec<ActionItem>, StoreError> {
let rows = sqlx::query(
"SELECT id, text, owner, due_at, confirmed, reminder_set FROM action_items \
WHERE meeting_id = ? ORDER BY created_at ASC",
)
.bind(meeting_id)
.fetch_all(&self.pool)
.await?;
Ok(rows
.iter()
.map(|r| ActionItem {
id: Some(r.get("id")),
text: r.get("text"),
owner: r.get("owner"),
due_at: r.get("due_at"),
confirmed: r.get::<i64, _>("confirmed") != 0,
reminder_set: r.get::<i64, _>("reminder_set") != 0,
})
.collect())
}
async fn record_mcp_access(
&self,
tool: &str,
meeting_id: Option<&MeetingId>,
client: Option<&str>,
) -> Result<(), StoreError> {
sqlx::query(
"INSERT INTO mcp_access_log (id, at, tool, meeting_id, client) VALUES (?,?,?,?,?)",
)
.bind(uuid::Uuid::new_v4().to_string())
.bind(now_unix())
.bind(tool)
.bind(meeting_id)
.bind(client)
.execute(&self.pool)
.await?;
Ok(())
}
async fn list_mcp_access_log(&self, limit: Option<u32>) -> Result<Vec<McpAccessEntry>, StoreError> {
let cap: i64 = limit.map(i64::from).unwrap_or(200).max(1);
let rows = sqlx::query(
"SELECT at, tool, meeting_id, client FROM mcp_access_log ORDER BY at DESC LIMIT ?",
)
.bind(cap)
.fetch_all(&self.pool)
.await?;
Ok(rows
.iter()
.map(|r| McpAccessEntry {
at: r.get("at"),
tool: r.get("tool"),
meeting_id: r.get("meeting_id"),
client: r.get("client"),
})
.collect())
}
}
/// Read a derived artifact, transparently decrypting it if the vault sealed it