Messaging consumer boundary

forge-lcdl exposes atomic, product-agnostic read operations for Outlook Web (OWA) and Microsoft Teams Web. Consumers (for example forge-cockpit-web) attach an authorized browser session, call governed tasks or connector…

Updated

LCDL must not import consumer repos or know about Cockpit workspaces, guided 10k harvest caps, UI step labels (Edge / Scan / Open), or checkpoint paths under var/data/.

Plaud voice memory is out of scope for this Playwright extension — consumers use filesystem/CLI adapters instead.

Atomic capability catalog — Outlook

Capability Governed task_id Input (summary) Output (summary)
Mailbox health outlook_probe_mail_health session_ref, optional url ready, list_row_count_estimate, sign_in_required, hints
Folder inventory outlook_list_folders session_ref folder items
Visible inbox sample outlook_list_messages_viewport session_ref, limit items[] (~viewport)
Large folder scroll outlook_list_messages_scroll session_ref, max_scroll_rounds items[]
Chunked inbox (resume) outlook_list_inbox_chunk session_ref, chunk_size, checkpoint, optional query.date_*, query.list_strategy items[], next_cursor, chunk.has_more
Inbox list (Graph REST) outlook_list_inbox_rest session_ref, top, skip / next link items[], folder_total_count, extra.is_unread
Thread / reading pane bodies outlook_read_conversation_thread session_ref, url, row_index, optional query.thread_id, preserve_unread items[] with body_text
Download message (.eml) outlook_download_message_eml session_ref, row_index, dest_path (must end .eml) ok, byte_length; rejects .msg
Downloads poll + move outlook_download_disk (wait_for_stable_eml_in_downloads, adopt_download_file, cleanup_owa_eml_left_in_downloads) Consumer staging path Moves OWA export from Edge ~/Downloads into dest_path; see OUTLOOK-EML-EXPORT-DUAL-ARCHITECTURE.md
URL normalize outlook_normalize_item_url url item_id, normalized URL
Layout detect outlook_apply_read_layout session_ref layout hints (v1 detect only)

Evaluate scripts live under src/forge_lcdl/playwright/scripts/outlook_readonly.py (for example harvest_list_rows, restore_row_unread). See Outlook read strategies and Outlook list strategies.

Read state and list strategy (decision tree)

  1. Unread / read flags on list rows → LCDL (extra.is_unread, optional state); consumers map to local is_read only.
  2. Restore OWA unread after conversation open → LCDL outlook_read_conversation_thread + preserve_unread; not consumer Playwright.
  3. How inbox rows are listed or folder totals → LCDL list strategy (OUTLOOK-LIST-STRATEGIES.md); consumers pass list_strategy / env only.
  4. SQLite badges and sync copy → consumer (wire_item_to_cockpit_fields, progress UI).
Task Observes read Mutates OWA read
outlook_list_inbox_rest is_unread No
outlook_list_inbox_chunk (DOM path) is_unread No
outlook_read_conversation_thread before click Yes (list_row); optional restore

list_row_count_estimate (not a harvest plan)

outlook_probe_mail_health may return list_row_count_estimate: a viewport DOM estimate of visible list rows (often ~30–40 on a typical inbox), not total mailbox size and not a consumer download quota. When Graph REST stats succeed, prefer folder_total_count and unread_count for mailbox size. Consumers should display viewport estimate as “rows visible now” and keep harvest caps in orchestration.

Atomic capability catalog — Teams

Capability Governed task_id Input (summary) Output (summary)
Surface health teams_probe_health session_ref, optional url ready, hints
Chat list teams_list_chats session_ref chat items
Channel list teams_list_channels session_ref channel items
Chunked chat messages teams_list_chat_messages_chunk session_ref, chunk_size, checkpoint, query.container items[], next_cursor, chunk.has_more
Chunked channel posts teams_list_channel_posts_chunk session_ref, chunk_size, checkpoint post items
Thread replies teams_read_thread_replies session_ref, url, thread context reply items

Evaluate scripts: src/forge_lcdl/playwright/scripts/teams_readonly.py. See Teams read strategies.

Wire shape

List tasks emit ReadResult-compatible JSON: items[] as UnifiedItem wire dicts (item_id, title, body_preview, actor, url, thread_id, participants, extra, …). Consumers map via their bridge (wire_item_to_cockpit_fields in Cockpit).

What LCDL must not know

Out of scope for LCDL Belongs in consumer
unified_message, message_channel, sync_run Cockpit SQLite + APIs
Guided 10k / channel cap 100 Cockpit sync_service budgets
harvest_phase, detail_status, thread file layout Cockpit message_storage
Checkpoint files under var/data/outlook_chunk_* Cockpit outlook_sync_state
Sync UI steps (browser, scan, detail) Cockpit SyncProgressPanel
Stable sync stat grid / heartbeat filtering Cockpit syncProgressDisplay.ts (LCDL only supplies raw progress_message)
Message-ID .eml paths, Downloads adopt index Cockpit eml_paths.py, eml_adopt_downloads.py
Plaud CLI, diarization, recordings Cockpit cockpit_server/plaud/

Consumer responsibilities

  1. Session — Resolve generic Edge CDP (forge_lcdl.playwright.edge_cdp_attach), attach with a new tab, register_playwright_page(session_ref, page). On attach, LCDL applies CDP focus emulation when bring-to-front is off so OWA/Teams lists render without stealing the operator's Edge window. See EDGE-CDP-ATTACH.md.
  2. Composition (LEGO) — Run atoms in order (probe → chunk loop → optional detail pass).
  3. Mapping — Bridge converts wire → upsert kwargs (source_message_key, folders, metadata JSON).
  4. Orchestration — Progress heartbeats, cancel, caps, pruning, audit events.

Example consumer: forge-cockpit-web — see that repo’s docs/messaging-lcdl-boundary.md (orchestration only; no new selectors in connectors/*.py).

Migration status (forge-cockpit-web)

Area LCDL atoms Cockpit status
Outlook routine + guided harvest probe + inbox chunk + conversation thread Production via lcdl_outlook_bridge
Outlook DOM PoC fallback Off by default (COCKPIT_OUTLOOK_DOM_FALLBACK=0); legacy in outlook_dom_legacy.py
Teams chat harvest probe + chat messages chunk Production via lcdl_teams_bridge
Teams DOM PoC fallback Off by default (COCKPIT_TEAMS_DOM_FALLBACK=0); legacy in teams_dom_legacy.py
Outlook People directory outlook_people_* tasks Production via people_harvest.py (sole org surface; Org Explorer removed)
Plaud N/A (CLI) Cockpit plaud.py only

Org directory boundary: ORG-DIRECTORY-CONSUMER-BOUNDARY.md.