Org Explorer read strategies (OWA / Delve)

Read-only harvest for Outlook Web Org Explorer (Delve iframe). Cockpit orchestrates; LCDL provides governed tasks.

Updated

Entry

  • OWA left nav → Org Explorer (not People for hierarchy harvest).
  • People (/people/) is a parallel LCDL surface for contact-list / directory-search ingest — see OUTLOOK-PEOPLE-READ-STRATEGIES.md.
  • Host URL: outlook.office.com/host/{app-id}/…
  • Iframe: loki.delve.office.com/orgexplorer/

CDP policy

  • Reuse the operator's existing Org Explorer tab (pick_org_explorer_cdp_page_target).
  • Attach with selective CDP (connect_owa_page_via_selective_cdp) — applies focus emulation on connect (tab thinks it is active; no OS focus steal).
  • Read iframe via Page.createIsolatedWorld + Runtime.evaluate.
  • Read-only clicks: person cards, View profile, Expand team (toggle on before snapshot), Show N more (repeat until exhausted), search picker, back/home.
  • Keep FORGE_LCDL_CDP_BRING_TO_FRONT=0 (default) for unattended crawl; see EDGE-CDP-ATTACH.md.

Tasks

Task Purpose
outlook_probe_org_explorer_health Iframe loaded, search box
outlook_org_explorer_home_snapshot Focused person + visible hierarchy wire JSON
outlook_org_explorer_prepare_focus Mail-first search / chart click → View profile (profile-enrich)
outlook_org_explorer_open_profile_card View profile panel (email, LinkedIn if present)
outlook_org_explorer_host_dr_panel Host flyout org structure when Delve iframe focus fails
outlook_org_explorer_focus_search Search picker → confirmed personInFocusDescription focus (display_name, optional graph_id, optional mail)
outlook_org_explorer_click_person Card click only when it changes focused person (not reports-list highlight)
outlook_org_explorer_dismiss_profile_panel Close host profile flyout

Input: { "session_ref": "…" } where the session page is a CdpOwaPageShim on an Org Explorer host tab.

Data sources (priority)

  1. Delve GraphQL (useOrgExplorerQueryRef_OrganizationQuery) — recursive JSON walk
  2. personaphoto?aadObjectId= URLs — stable graph_id
  3. personInFocusDescription-{aadObjectId} on focus cards — stable graph_id
  4. DOM innerText fallback — names/titles/locations only (no graph_id)

Not identity: blob: avatar photo URLs — session-ephemeral; never used as graph_id after harvest reconcile (reconcile_people_by_display_name).

Identity reconcile

After GraphQL + personaphoto collection, reconcile_people_by_display_name collapses duplicate keys sharing a normalized display_name to one canonical graph_id (GraphQL beats personaphoto beats focus card). Blob-only avatar rows are dropped when a real id exists for the same name.

Wire shape

{
  "ok": true,
  "focused_graph_id": "…",
  "people": [{ "graph_id": "…", "display_name": "…", "job_title": "…" }],
  "edges": [{ "src_graph_id": "…", "rel": "reports_to", "dst_graph_id": "…" }]
}

Edge rel values: reports_to, peer_of, works_with.

Consumer

forge-cockpit-webcockpit_server/org/explorer_harvest.py, ingest.py. Operator flow: org-explorer-harvest.md. Dual architecture: ORG-EXPLORER-DUAL-ARCHITECTURE.mdorg-explorer-dual-architecture.md.