Outlook People read strategies (OWA)

Read-only harvest for Outlook Web People (/people/). Cockpit orchestrates; LCDL provides governed tasks. Outlook People is the sole OWA org-directory surface — the legacy Org Explorer (Delve iframe) crawl has been…

Updated

Entry

  • OWA left nav → People.
  • Host URL: https://outlook.office.com/people/
  • No Delve iframe — all reads are host-page evaluate.

CDP policy

  • Reuse the operator's existing People tab (pick_outlook_people_cdp_page_target).
  • Attach with selective CDP (connect_owa_page_via_selective_cdp) — focus emulation on connect.
  • Read-only clicks: contact rows, tree items, search suggest options, profile panel dismiss (Escape).
  • Keep FORGE_LCDL_CDP_BRING_TO_FRONT=0 (default) for unattended harvest.
  • Tab lifecycle — external LinkedIn profile tabs opened during LPC harvest must be closed after URL capture; see TAB-LIFECYCLE-POLICY.md.

Tasks

Task Purpose
outlook_probe_people_health /people/ URL, Search contacts and directory combobox, left [role=tree]
outlook_people_home_snapshot Visible [role=row] contacts (+ scroll rounds)
outlook_people_focus_search Type query → CDP Enter → capture suggest API → open LPC via quick-action
outlook_people_focus_tree Click row or treeitem by display_name / mail
outlook_people_snapshot_person Focus (tree, else search) + read_profile_panel
outlook_people_read_profile_panel Parse flyout text (email, phone, org fields)
outlook_people_dismiss_profile_panel Escape dismiss

Input: { "session_ref": "…", "operator_graph_id": "…", "operator_mail": "…" } on a People host tab.

Task inputs for focus/search also accept query, display_name, mail, graph_id.

Data sources (priority)

  1. Directory suggest APIPOST /search/api/v1/suggestions?domain=People (network capture in OutlookPeopleReader)
  2. Contact list[role=row] innerText (parse_people_row_text)
  3. Left tree — operator email from treeitem text
  4. Profile panel — shared parsers from owa_lpc_reader (parse_profile_card_text, parse_contact_panel_fields)

Identity: List rows often lack Azure AD GUID. Wire uses pending_mail:{email} unless operator_graph_id / env supplies operator GUID. Cockpit resolves mail against person table before ingest.

Not identity: Suggest PersonId when absent — prefer mail match in consumer.

Wire shape

{
  "ok": true,
  "focused_graph_id": "5af741ae-…",
  "upn": "operator@contoso.com",
  "people": [{ "graph_id": "pending_mail:user@example.com", "display_name": "…", "mail": "…" }],
  "edges": []
}

v1 home scan does not emit reports_to / peer_of edges — hierarchy comes from the per-person Organization tab (parse_organization_panel_fields), resolved to graph_id consumer-side.

Consumer

forge-cockpit-webcockpit_server/org/people_harvest.py, ingest.py (harvest_source="outlook_people"). Operator flow: org-people-harvest.md. Dual architecture: OUTLOOK-PEOPLE-DUAL-ARCHITECTURE.mdorg-people-dual-architecture.md.