Playwright-assisted extension architecture

Step: 01_architecture_docs (PDCA Do phase) Repo: forge-lcdl (src/forge_lcdl/) Status: Architecture and boundaries only — implementation lands in later PDCA steps.

Updated

Purpose

Extend forge-lcdl with Playwright-assisted, read-only modules. An already-authorized user browser/session is attached through playwright. LCDL exposes deterministic Python APIs that read and normalize enterprise UI state into a source-neutral item model — without private Graph/Workday/SharePoint REST calls and without harvesting session secrets.

This extension is not a replacement for governed LLM tasks (run_task, TASK_REGISTRY_V1). It is a parallel capability for structured reads from attached sessions.

Design principles

Principle Meaning
Session-first All enterprise reads go through an attached PlaywrightSession facade; no alternate credential channel in v1.
Read-only v1 Navigate and observe only; no writes, submits, or DOM mutation (see READONLY_POLICY.md).
Deterministic core Same fixture + fake session → same UnifiedItem shapes; LLM optional later, not required for v1 reads.
Core stays light Disk-backed session bridges and heavy runtime wiring live in forge-lcdl-runtime or the host app (see runtime boundary).
Offline-first tests Default pytest uses FakePlaywrightSession and fixtures; live playwright tests are opt-in.
Separate from pw_* Playwright LLM tasks (tasks/pw_*_v1.py) ingest page mechanics for QA/education — not M365/Workday mailboxes.

Placement in forge-lcdl

Playwright extension placement

How an attached authorized browser session becomes governed, source-neutral UnifiedItem reads inside forge-lcdl.

  1. Consumer / host appHost obtains and attaches an already-authorized playwright session before LCDL reads.
  2. forge_lcdl.playwright ← new package (subsequent steps)New package exposes PlaywrightSession, read-only policy, and per-source normalizers.
  3. UnifiedItem[] ← source-neutral modelNormalized reads return a consistent, source-neutral item list for downstream consumers.
  4. forge_lcdl.tasks.* ← governed LLM wrappers if neededOptional governed LLM tasks may wrap reads when schema-aware task envelopes are needed.
  5. forge_lcdl.mcp_client ← optional MCP/Playwright backend hook onlyOptional MCP bridge can delegate browser mechanics to PlaywrightAdapter tools.

Unchanged in v1 architecture (do not refactor):

  • src/forge_lcdl/runner.pyrun_task / TaskRunner
  • src/forge_lcdl/tasks/catalog_v1.pyTASK_REGISTRY_V1
  • src/forge_lcdl/generic/ — OpenAI-compat transport
  • src/forge_lcdl/mcp_client/ — optional MCP client (related, not identical to playwright)

Module boundaries (planned)

Target tree under src/forge_lcdl/playwright/ (created in later steps):

Module Responsibility Depends on
types.py UnifiedItem, attachments, provenance, schema_version stdlib / typing only
errors.py PlaywrightError + kinds (not_attached, policy_denied, …) result.py patterns
session.py PlaywrightSession Protocol: attach, navigate, snapshot, readonly download nothing playwright-runtime-specific
policy.py Read-only guards, domain allowlists, tool-name blocks may mirror mcp_client/policy.py
normalize.py Shared mapping → UnifiedItem types.py
sources/outlook.py Message list/detail normalization session + normalize
sources/teams.py Chats, channel posts, activity session + normalize
sources/workday.py Inbox, notifications, announcements session + normalize
sources/sharepoint.py News, lists, library rows session + normalize
sources/onedrive.py Recent files, file activity session + normalize
sources/files.py PPTX, XLSX, DOCX, PDF from local bytes session + optional file extras
adapters/fake.py FakePlaywrightSession + fixture replay session Protocol
adapters/mcp_bridge.py Optional delegate to PlaywrightAdapter feature-flagged; mcp_client

Public API (later): export minimal types and attach_session from forge_lcdl.playwright. Do not add to top-level forge_lcdl.__init__ __all__ until the surface stabilizes.

Unified item model

Every source maps into one shape (implementation in playwright/types.py):

Field Role
schema_version Wire version (e.g. "1")
item_id Stable id within source + session
source_system outlook | teams | workday | sharepoint | onedrive | file
item_type, source_kind Source-specific subtype
title, body_preview, body_text Present only when policy allows
actor, participants People / identities visible in UI
created_at, updated_at ISO-8601 strings when parseable
url, thread_id, container, state Navigation / threading context
attachments File metadata list (not secret blobs)
provenance, raw_ref Non-secret locator (DOM ref, list row key) — never tokens

Optional governed tasks may add JSON Schema sidecars under contracts/playwright_*/v1/ in a later step if reads are wrapped by run_task.

Deterministic call flows

1. Attach (consumer or runtime)

Host obtains authorized playwright session (outside LCDL)
    → PlaywrightSession.attach(session_handle)
    → Result[None, PlaywrightError]

LCDL does not perform login, MFA, or credential prompts.

2. Read a source (core pattern)

Caller: read_outlook_inbox(session, options)
    → policy.check_navigation(url)           # domain allowlist
    → session.navigate(approved_url)
    → session.snapshot()                     # DOM / accessibility tree / text
    → sources.outlook.normalize(snapshot)    # pure function on snapshot bytes
    → Result[list[UnifiedItem], PlaywrightError]

Normalization is pure given snapshot input so unit tests do not need a browser.

3. Read-only file extraction

Caller: read_file_bytes(session, file_ref)
    → policy permits readonly download
    → session.download_readonly(file_ref)    # local bytes only; no upload path
    → sources.files.extract_text(bytes, mime)
    → Result[UnifiedItem, PlaywrightError]

Office/PDF parsers may be optional extras (playwright-files); core documents the hook even if parsers ship later.

4. Error envelope

All facade entrypoints return Result[T, PlaywrightError], consistent with McpError usage in the MCP client.

Source systems (v1 scope)

source_system Typical UI surfaces Normalizer module
outlook Mail list, reading pane sources/outlook.py
teams Chats, channels, activity feed sources/teams.py
workday Inbox tasks, notifications, announcements sources/workday.py
sharepoint News, lists, libraries; modern reads via canonical sharepoint_* tasks (list_library_files_viewport, scroll, columns, rest) connectors/sharepoint.py (+ sharepoint_urls, sharepoint_modern, sharepoint_scroll, sharepoint_rest, scripts/sharepoint_readonly) — see SHAREPOINT-LIBRARY-READ-STRATEGIES.md
onedrive Recent, activity sources/onedrive.py
file PPTX, XLSX, DOCX, PDF sources/files.py

Tenant-specific DOM variance is handled in normalizers with tolerant parsing and fixture-backed tests — not brittle single-selector assumptions in the session layer.

Relationship to MCP and runtime

Component Relationship
mcp_client Optional backend when playwright exposes Playwright-like tools; McpPolicy.readmostly_playwright() is the ceiling for tool posture (see READONLY_POLICY.md).
forge-lcdl-runtime Session persistence, bridge stubs, host wiring — out of core imports.
Governed LLM tasks Optional later via tasks/registry.py + packs; not required for v1 reads.

Testing architecture

Layer Approach
Unit FakePlaywrightSession records (method, args); fixtures under tests/playwright/fixtures/
Policy Assert denied tools/URLs and allowed read paths
Normalizers Static HTML/JSON snapshots → expected UnifiedItem fields
Live Marker playwright_live (planned), skipped unless FORGE_LCDL_PLAYWRIGHT_LIVE=1

Default pytest in pyproject.toml continues to exclude granite and must not require network or attached playwright.

SharePoint atoms vs consumer orchestration

LCDL exposes product-agnostic SharePoint read atoms (normalize, probe, REST list, scroll/viewport/columns, list-item fields, binary download). The host attaches an authorized browser session (PlaywrightPageSession.from_page(page)), registers routes, and invokes connectors or governed tasks. LCDL never imports consumer repos and must not know deck IDs, corpus paths, job phases, or M365 Copilot menus.

See SHAREPOINT-CONSUMER-BOUNDARY.md for the atomic catalog and consumer rules.

Outlook / Teams messaging atoms vs consumer orchestration

LCDL exposes product-agnostic Outlook and Teams read atoms (health probe, chunked list harvest, conversation/thread read). The host attaches Edge/CDP, registers session_ref, and composes tasks into ingest flows (caps, checkpoints, unified_message). On attach, LCDL applies CDP focus emulation by default so Microsoft virtualized lists render without stealing OS focus (FORGE_LCDL_CDP_BRING_TO_FRONT=0). LCDL must not import consumer repos or know Cockpit sync UI steps.

See MESSAGING-CONSUMER-BOUNDARY.md, OUTLOOK-EML-EXPORT-DUAL-ARCHITECTURE.md, OUTLOOK-READ-STRATEGIES.md, and TEAMS-READ-STRATEGIES.md.

Outlook People / CRM atoms vs consumer orchestration

LCDL exposes product-agnostic Outlook People and Dynamics CRM read atoms. (The former Org Explorer Delve-iframe crawl surface has been removed; the Outlook People Organization tab is now the sole org-structure source.) The host attaches Edge/CDP, registers session_ref, and maps wire output to SQLCipher / on-disk folders. Selective CDP attach enables the same focus emulation policy as messaging ingest so background tabs stay harvestable. LCDL must not import consumer repos or know crawl depth policies, enrich tier planners, or portfolio board swimlanes.

Security and compliance posture

  • No cookie, storage-state, header, or token export — enforced in policy and types (see READONLY_POLICY.md).
  • No Microsoft Graph, SharePoint REST, or Workday REST in v1.
  • Provenance and logs must remain redactable; tests will assert banned fields stay absent.

Enterprise data-handling expectations align with Data handling: LCDL core does not introduce durable mailbox storage; consumers own retention.

PDCA handoff

Topic Document
Discovery and file touch list PDCA_IMPLEMENTATION_MAP.md
Read-only rules READONLY_POLICY.md
Protocols, adapters, optional tasks EXTENSION_POINTS.md

Deferred to later steps: playwright/ package implementation, pytest markers, source modules, optional MCP bridge, handbook nav in docs/index.md, live testing guide.

References