Handbook
Playwright PDCA implementation map
Step: 00_repo_discovery_constraints (PDCA Do phase) Repo: /home/lzvyahin/Code/forge-lcdl Date: 2026-05-31
Updated
This document records repository discovery for the Playwright-assisted, read-only extension. It is the handoff artifact for subsequent PDCA steps; no runtime code ships in step 00.
Repo confirmation
| Check | Result |
|---|---|
| Workspace root | /home/lzvyahin/Code/forge-lcdl |
| Package root | src/forge_lcdl/ present |
| Python | requires-python = ">=3.11" in pyproject.toml |
| Default test cwd | Repo root; pythonpath = ["src"] |
| Sibling runtime | ../forge-lcdl-runtime exists (Playwright/MCP bridge stubs only; no playwright code yet) |
| Prior playwright code | None in core or runtime; only .cursor/rules/lcdl-playwright-pdca.mdc |
Product scope (v1, preserved)
In scope: attach to an already-authorized playwright browser/session; navigate approved domains; read visible metadata/text and DOM snapshots through a facade; read-only file extraction when session policy permits; normalize into a source-neutral item model.
Out of scope (v1): writes/submits/approvals; cookie/storage/token/header export; Microsoft Graph, SharePoint REST, Workday REST, or other private app APIs; bypassing MFA or harvesting credentials.
Alignment with existing MCP policy: McpPolicy.readmostly_playwright() already denies cookie/storage mutation tools and treats click/type/fill as approval_required — playwright v1 should stay at or below that posture.
Existing package layout (relevant areas)
src/forge_lcdl/
├── __init__.py # Public exports (TaskRunner, run_task, operators, generic, …)
├── runner.py # run_task / TaskRunner dispatch
├── result.py # Ok / Err Result alias
├── errors.py # LlmFailure hierarchy
├── contracts/ # 39 task_ids × v1 (contract.md + contract.json)
├── contracts_api.py # ContractSpec v2, UnknownContractError
├── tasks/
│ ├── catalog_v1.py # TASK_REGISTRY_V1 (generic + RAG + games LLM tasks)
│ ├── registry.py # register_task / resolve_task / lazy packs
│ └── packs/install.py # generic | rag | games | playwright packs
├── mcp_client/ # Optional MCP client (pip install forge-lcdl[mcp])
│ ├── hub.py # Sync McpHub
│ ├── policy.py # Deny-by-default; readmostly_playwright preset
│ └── adapters/playwright.py # PlaywrightAdapter, fetch_snapshot, …
├── generic/ # OpenAI-compat transport helpers
├── execution/ # LcdlClient, ExecutionEngine
├── context/ # Context packs (byte-budget repo scan)
├── verification/ # Schema/shape checks, pytest runner
└── testing/phased_gates.py # Subprocess gate helpers
tests/ # ~84 Python test modules (`tests/**/test_*.py`)
├── test_runner.py, test_catalog_tasks.py
├── mcp_client/ # Fake hub adapter tests (offline)
├── integration/ # granite + mcp_live (skipped by default)
└── tasks/, games/, game_engine/, docs/
docs/
├── reference/runtime-boundary.md # Core vs forge-lcdl-runtime vs consumers
├── agents/MCP-CLIENT.md # Playwright MCP client patterns
└── playwright/ # This map (new)
Task / contract inventory
| Pack | Count | Registration |
|---|---|---|
| Generic LLM | 15 | TASK_REGISTRY_V1 minus RAG/GAME ids → generic_task_pack() |
| RAG | 3 | rag_task_pack() |
| Games | 6 | games_task_pack() |
| Playwright LLM | 11 | playwright_task_pack() (separate modules under tasks/pw_*_v1.py) |
| Total registered | 35 | Via ensure_builtin_tasks_registered() / install_default_packs() |
Contracts live at src/forge_lcdl/contracts/<task_id>/v1/{contract.md,contract.json}. 39 contract folders exist; four have schema/input artifacts only (no register_task runner): page_probe, interaction_probe, page_mechanics, validation_failure — consumed as JSON shapes by Playwright LLM tasks (e.g. pw_page_kind_route, pw_quiz_mechanics_discover).
Sidecar generation and audits: scripts/generate_contract_sidecars.py, scripts/audit_contract_docs.py.
Playwright note: Enterprise read modules are not required to be LLM run_task entries on day one. Prefer a dedicated forge_lcdl.playwright package with optional governed tasks added later if normalization needs LLM assistance. Do not overload existing pw_* Playwright LLM tasks — those ingest page mechanics for QA/education, not M365/Workday mailboxes.
Patterns to reuse (do not refactor)
| Pattern | Location | Playwright use |
|---|---|---|
Result[T, E] |
forge_lcdl.result |
All facade methods return Result[…, PlaywrightError] (new error type, mirror McpError shape) |
| Injectable fakes | tests/mcp_client/test_playwright_adapter.py (_FakeHub) |
FakePlaywrightSession with recorded navigate/snapshot calls |
| Policy + URL allowlists | mcp_client/policy.py |
Extend or wrap for enterprise domain presets (outlook.office.com, teams.microsoft.com, *.sharepoint.com, workday host patterns) |
| Browser read facade | PlaywrightAdapter.fetch_snapshot |
Optional adapter backend when playwright exposes MCP/Playwright-like tools; protocol hook only in core |
| Task registration | tasks/registry.py + packs |
Enterprise read tasks ship in playwright_task_pack() alongside pw_* LLM tasks |
| Runtime boundary | docs/reference/runtime-boundary.md |
Disk session / heavy bridges stay in forge-lcdl-runtime; core stays import-light |
Proposed module layout (subsequent steps)
Target new tree under src/forge_lcdl/playwright/ (names tentative):
| Module | Responsibility |
|---|---|
types.py |
UnifiedItem, AttachmentMeta, Provenance, schema_version constants |
errors.py |
PlaywrightError, kinds: not_attached, policy_denied, parse_failed, source_unavailable |
session.py |
PlaywrightSession Protocol: attach, is_attached, navigate, snapshot, download_readonly (no-op/write guard) |
policy.py |
Read-only guardrails; block disallowed tool names; domain allowlists per source |
normalize.py |
Shared field mapping helpers → UnifiedItem |
sources/outlook.py |
Message list/detail normalization |
sources/teams.py |
Chats, channel posts, activity |
sources/workday.py |
Inbox tasks, notifications, announcements |
sources/sharepoint.py |
News, lists, library rows |
sources/onedrive.py |
Recent files, file activity |
sources/files.py |
PPTX, XLSX, DOCX, PDF text/metadata extraction (local bytes only) |
adapters/fake.py |
Deterministic fake session + fixture JSON |
adapters/mcp_bridge.py |
Thin wrapper delegating to PlaywrightAdapter when config matches (optional hook) |
Public surface (later): export minimal types + attach_session from forge_lcdl.playwright; do not add to top-level forge_lcdl.__all__ until API stabilizes.
Unified item model (common shape)
Single dataclass/TypedDict in playwright/types.py:
schema_version(e.g."1")item_id,source_system(outlook|teams|workday|sharepoint|onedrive|file)item_type,source_kindtitle,body_preview,body_text(optional per policy)actor,participantscreated_at,updated_at(ISO-8601 strings)url,thread_id,container,stateattachments(metadata list)provenance,raw_ref(non-secret locator, e.g. DOM path or stable page ref — never tokens)
JSON Schema sidecar optional in a later step under contracts/playwright_* if governed tasks wrap reads.
Likely files to touch (by PDCA theme)
| Step theme | Files (estimate 3–6 per Composer run) |
|---|---|
| Core types + errors | playwright/types.py, playwright/errors.py, tests/playwright/test_types.py |
| Session protocol + fake | playwright/session.py, playwright/adapters/fake.py, tests/playwright/test_session_fake.py |
| Policy | playwright/policy.py, tests/playwright/test_policy.py; may reference mcp_client/policy.py patterns |
| Source module (each) | playwright/sources/<source>.py, fixture under tests/playwright/fixtures/, tests/playwright/test_<source>_normalize.py |
| File formats | playwright/sources/files.py, tests with tiny binary fixtures (or generated minimal OOXML/PDF) |
| Docs | docs/playwright/*.md, handbook cross-link from docs/index.md (when stable) |
| Optional tasks | contracts/playwright_*/v1/*, tasks/playwright_*_v1.py, tasks/packs/install.py new pack |
| Pytest config | pyproject.toml markers playwright, playwright_live |
Avoid in early steps: broad edits to catalog_v1.py, runner.py, transport.py, or mcp_client/hub.py.
Test strategy
Default (CI / offline)
- All unit tests use
FakePlaywrightSessionor static HTML/JSON fixtures undertests/playwright/fixtures/. - Mirror
tests/mcp_client/test_playwright_adapter.py: capture(method, args)call log; no network. - Default pytest invocation (existing):
pytest→addopts = ["-m", "not granite"]inpyproject.toml.
Markers (existing + planned)
| Marker | Meaning | Default run |
|---|---|---|
granite |
Live Granite gateway | Excluded by default |
mcp |
Requires optional MCP SDK | Included if deps installed |
mcp_live |
Live Playwright MCP (FORGE_LCDL_MCP_PLAYWRIGHT=1) |
Skipped in test body |
stress_context |
Large prompt stress | Included unless selected |
playwright (planned) |
Needs playwright protocol stubs beyond fake | Include when only stdlib + fake |
playwright_live (planned) |
Attached real playwright session | Skipped unless env set (e.g. FORGE_LCDL_PLAYWRIGHT_LIVE=1) |
Suggested env gates (later): FORGE_LCDL_PLAYWRIGHT_LIVE, FORGE_LCDL_PLAYWRIGHT_SESSION_ID — document in docs/playwright/TESTING.md.
Discovery command (step 00)
cd /home/lzvyahin/Code/forge-lcdl
python -m pytest --collect-only -q
Expected: hundreds of tests collected; granite and mcp_live cases remain skip-conditioned. Re-run after adding tests/playwright/.
Operator verify (2026-05-31, Act): shell execution was unavailable in the Act agent session; run the command locally before step 02 and record the trailing N tests collected line here when known.
Risks and mitigations
| Risk | Mitigation |
|---|---|
| Unknown playwright runtime API | PlaywrightSession Protocol + FakePlaywrightSession first; mcp_bridge optional |
| DOM variance across tenants | Normalizers tolerate missing fields; snapshot fixtures per source; no brittle selectors in v1 core |
| Accidental write actions | Policy layer rejects click/type/submit tools; code review checklist in PDCA guardrails |
Secret leakage in provenance / logs |
Ban cookie/token fields in types; tests assert redaction |
| Scope creep into Graph/REST | v1 docs + policy explicitly deny; code review |
| File parser deps (PDF/Office) | Optional extras in pyproject.toml (e.g. playwright-files); pure-python or stdlib-first where possible |
Confusion with pw_* LLM tasks |
Separate package namespace playwright/; distinct docs |
| Large files / footprint | Keep modules ≤ ~700 LoC; one source per file |
Gaps deferred to later PDCA steps
- Playwright runtime contract — exact attach API, session handle, and tool surface (stub Protocol only next).
pyproject.tomlmarkers — addplaywright/playwright_livewhen first integration tests land.- Optional dependencies — PDF/Office parsing libraries not yet declared.
- Governed LLM tasks — whether reads get
run_taskwrappers or stay pure Python APIs. - Handbook publishing —
docs/playwright/TESTING.mdand deeper nav when content stabilizes; home pointer added in step 01 Act. - forge-lcdl-runtime bridge — consumer-side attachment wiring; core documents hook only.
- Live pytest verification — operator should run
pytest --collect-only -qlocally before step 02 (Act session could not execute shell).
Architecture docs (step 01)
| Document | Purpose |
|---|---|
| ARCHITECTURE.md | Module boundaries, call flows, unified item model |
| READONLY_POLICY.md | v1 allowed/disallowed operations and MCP alignment |
| EXTENSION_POINTS.md | Protocols, adapters, optional tasks and runtime hooks |
Suggested PDCA step sequence (high level)
- 01 — Architecture docs (complete)
- 02 —
UnifiedItem+ errors + fake session protocol + unit tests - 03 — Read-only policy module + domain presets
- 04–09 — One source module per step (outlook → teams → workday → sharepoint → onedrive → files)
- 10 — Optional MCP bridge adapter (feature-flagged)
- 11 — Integration tests (skipped live), docs (
README, testing guide) - 12+ — Optional governed tasks / catalog entries if product requires
References
.cursor/rules/lcdl-playwright-pdca.mdc— implementation guardrailsdocs/reference/runtime-boundary.md— core vs runtimedocs/agents/MCP-CLIENT.md— Playwright MCP client (related, not identical to playwright)src/forge_lcdl/mcp_client/policy.py—readmostly_playwright()tool allow/deny listsCONTRIBUTING.md— responsible use; no credential theft
Step 00 complete (Do + Act): discovery recorded; Check nits applied to inventory counts and contract-vs-runner note; implementation code intentionally not added.
Step 01 complete (Do + Act): architecture/policy/extension docs; Check PASS; Act renumbered PDCA sequence, updated operator handoff notes, added docs/index.md discoverability pointer.
Step 04 — connector protocols and fakes (Act 2026-05-31)
Status: Act applied Check fixes; operator must run pytest to close verification.
| Fix | Detail |
|---|---|
session_method_names |
Unwraps GuardedPlaywrightSession.backend so audit records fake navigate / snapshot when tests use the guarded facade |
normalize_read_result |
Optional source_kind for empty batches (audit no longer stuck on "unknown" when caller supplies kind) |
| Tests | Parametrize write-filter denial over full _DENIED_QUERY_FILTER_KEYS; empty-batch audit test |
Verify:
cd /home/lzvyahin/Code/forge-lcdl && python3 -m pytest tests/playwright/ -q --tb=short
Next PDCA step: per-source readers under playwright/sources/ (outlook inbox reader beyond stub, teams, workday, …).
Step 08 — SharePoint reader (Act 2026-05-31)
Status: Do + Check PASS; Act applied no code changes (no pytest failures or blocking Check findings). Operator must run pytest to close verification.
| Deliverable | Location |
|---|---|
SharePointReader + DOM parsers |
src/forge_lcdl/playwright/connectors/sharepoint.py |
| Offline tests (21 cases) | tests/playwright/test_sharepoint_reader.py |
| Fixtures | tests/playwright/fixtures/dom_pages.py (SHAREPOINT_*_HTML) |
| Package exports | src/forge_lcdl/playwright/connectors/__init__.py |
Source kinds (v1): news, list, library — session navigate → snapshot → HTML parse → UnifiedItem.
Verify:
cd /home/lzvyahin/Code/forge-lcdl
python -m pytest tests/playwright/test_sharepoint_reader.py -v --tb=short
python -m pytest tests/playwright/ -q
python -m compileall -q src tests
Known limitations (deferred):
- Fixture-oriented
data-*selectors; real modern SharePoint UI mapping is a runtime adapter concern. SharePointReader()defaults topermissive_https()for offline tests; production callers should passsharepoint_source_policy()without permissive override.body_textnot populated by v1 parser (preview only).- No task-registry / contract wiring in this step.
Next PDCA step: 09 — OneDrive reader.
Step 21 — SharePoint modern bridge + offline tests (Act 2026-05-31)
Status: Implemented in core; offline pytest is the default verification path.
| Deliverable | Location |
|---|---|
| URL helpers | connectors/sharepoint_urls.py |
| Modern parse / health | connectors/sharepoint_modern.py |
| Vendored evaluate JS | scripts/sharepoint_readonly.py (EvaluateScriptId) |
| Scroll harvest | connectors/sharepoint_scroll.py |
| Reader kinds | library_viewport, library_columns, page_health, library_scroll on SharePointReader |
| Session bridge | evaluate_readonly / scroll_readonly; EvaluatingFakePlaywrightSession, PlaywrightPageSession |
| Governed tasks | sharepoint_normalize_library_url, sharepoint_probe_library_health, sharepoint_list_library_files_* (+ deprecated playwright_* aliases) |
| Contracts | contracts/sharepoint_normalize_library_url/v1/, sharepoint_probe_library_health/v1/, sharepoint_list_library_files_*/v1/ |
| Strategy guide | SHAREPOINT-LIBRARY-READ-STRATEGIES.md |
| Fixtures | SHAREPOINT_MODERN_* in tests/playwright/fixtures/dom_pages.py |
| Tests | test_sharepoint_urls.py, test_sharepoint_modern.py, test_evaluating_fake_session.py, test_sharepoint_scroll.py, test_sharepoint_reader_modern.py, test_playwright_sharepoint_tasks.py, optional test_playwright_page_session.py |
Verify:
cd /home/lzvyahin/Code/forge-lcdl
.venv/bin/python -m pytest tests/playwright/test_sharepoint_urls.py \
tests/playwright/test_sharepoint_modern.py \
tests/playwright/test_evaluating_fake_session.py \
tests/playwright/test_sharepoint_scroll.py \
tests/playwright/test_sharepoint_reader_modern.py \
tests/playwright/test_playwright_sharepoint_tasks.py -q --tb=short
.venv/bin/python -m pytest tests/test_contract_docs_completeness.py -q
Out of scope: forge-knowledge-assistant register_playwright_page wiring; live tenant CI.
Step 09 — OneDrive reader (Act 2026-05-31)
Status: Do + Check PASS; Act applied no code changes (no pytest failures or blocking Check findings). Operator must run pytest to close verification.
| Deliverable | Location |
|---|---|
OneDriveReader + DOM parsers |
src/forge_lcdl/playwright/connectors/onedrive.py |
| Offline tests (~20 cases) | tests/playwright/test_onedrive_reader.py |
| Fixtures | tests/playwright/fixtures/dom_pages.py (ONEDRIVE_*_HTML) |
| Package exports | src/forge_lcdl/playwright/connectors/__init__.py |
Source kinds (v1): recent, shared, activity — session navigate → snapshot → HTML parse → UnifiedItem (SharePoint-hosted OneDrive layout URLs).
Verify:
cd /home/lzvyahin/Code/forge-lcdl
python -m pytest tests/playwright/test_onedrive_reader.py -q --tb=short
python -m pytest tests/playwright/ -q
python -m compileall -q src tests
Known limitations (deferred):
- Fixture-oriented
data-*/ row class selectors; real OneDrive DOM may need runtime adapter tuning. - Navigation targets SharePoint
_layouts/15/onedrive.aspxviews;onedrive.live.comconsumer UI variants not mapped. - No governed task contract /
TASK_REGISTRY_V1wiring in this step. - File content extraction (
10_files_reader) is separate from list/activity reads. - Live playwright / MCP bridge tests remain out of scope (skipped unless env-gated later).
Optional Check hardening (not applied in Act): site_url filter navigation test, invalid source_kind → invalid_query test, parse_onedrive_snapshot_html body data-kind override test — add only if a future failure demands them.
Next PDCA step: 10 — files reader (PPTX, XLSX, DOCX, PDF) and/or MCP bridge per PDCA pack sequence.
Step 11 — DOCX reader (Act 2026-05-31)
Status: Do + Check PASS; Act added warning-path tests and this handoff note.
| Deliverable | Location |
|---|---|
extract_docx_bytes / extract_docx_file |
src/forge_lcdl/playwright/files/docx_reader.py |
Offline tests (10 cases with [files]) |
tests/playwright/test_docx_reader.py |
| OOXML fixtures (stdlib zip) | tests/playwright/fixtures/docx_bytes.py |
| Public exports | src/forge_lcdl/playwright/files/__init__.py |
Optional dependency: pip install 'forge-lcdl[files]' → python-docx>=1.1.0 (extra name files, not playwright-files).
Verify:
cd /home/lzvyahin/Code/forge-lcdl
pip install -e '.[files,dev]'
python -m pytest tests/playwright/test_docx_reader.py -q --tb=short
python -m compileall -q src tests
Known limitations (deferred): PPTX/PDF readers; sources/files.py facade; playwright session download wiring; TASK_REGISTRY_V1 contracts; footnotes/headers/footers/images. (XLSX reader: step 13.)
Next PDCA step: 12 — PPTX reader (completed; see below).
Step 12 — PPTX reader (Act 2026-05-31)
Status: Do + Check PASS; Act narrowed notes exception handling, added pptx_notes_unavailable warning test, and this handoff note.
| Deliverable | Location |
|---|---|
extract_pptx_bytes / extract_pptx_file |
src/forge_lcdl/playwright/files/pptx_reader.py |
Offline tests (12 cases with [files]) |
tests/playwright/test_pptx_reader.py |
Programmatic deck fixtures (lazy python-pptx) |
tests/playwright/fixtures/pptx_bytes.py |
| Public exports | src/forge_lcdl/playwright/files/__init__.py |
| Optional dep | pyproject.toml → [project.optional-dependencies].files includes python-pptx>=1.0.0 |
Verify:
cd /home/lzvyahin/Code/forge-lcdl
pip install -e '.[files,dev]'
python -m pytest tests/playwright/test_pptx_reader.py -v --tb=short
python -m pytest tests/playwright/ -q
python -m compileall -q src/forge_lcdl/playwright/files/pptx_reader.py
Without [files]: 3 passed, 9 skipped (import probe, invalid metadata, missing-dep monkeypatch; extraction tests skip).
Known limitations (deferred): sources/files.py unified facade; playwright session download wiring; stdlib-only PPTX fixture bytes; charts/images/SmartArt/masters; TASK_REGISTRY_V1 file contracts.
Next PDCA step: 13 — XLSX reader (completed; see below).
Step 13 — XLSX reader (Act 2026-05-31)
Status: Do + Check PASS after Act (row-limit assertion aligned; hidden-sheet test; map note).
| Deliverable | Location |
|---|---|
extract_xlsx_bytes / extract_xlsx_file |
src/forge_lcdl/playwright/files/xlsx_reader.py |
Offline tests (11 cases with [files]) |
tests/playwright/test_xlsx_reader.py |
| OOXML fixtures (stdlib zip) | tests/playwright/fixtures/xlsx_bytes.py |
| Public exports | src/forge_lcdl/playwright/files/__init__.py |
| Optional dep | pyproject.toml → [project.optional-dependencies].files includes openpyxl>=3.1.0 |
Verify:
cd /home/lzvyahin/Code/forge-lcdl
pip install -e '.[files,dev]'
python -m pytest tests/playwright/test_xlsx_reader.py -v --tb=short
python -m pytest tests/playwright/ -q
python -m compileall -q src tests
Without [files]: 3 passed, 8 skipped (import probe, invalid metadata, missing-dep monkeypatch; extraction tests skip).
API notes: max_table_rows / table_chunks[].row_count count body rows only; xlsx_sheets[].row_count is the bounded grid row count (includes header row).
Known limitations (deferred): sources/files.py unified facade; playwright session download wiring; Excel <table>/charts/macros; TASK_REGISTRY_V1 file contracts; live playwright tests.
Next PDCA step: 14 — PDF reader (completed; see below).
Step 14 — PDF reader (Act 2026-05-31)
Status: Do + Check PASS; Act aligned encrypted pdf_pages with successful extractions (empty list), hardened stdlib PDF fixture streams, and this handoff note.
| Deliverable | Location |
|---|---|
extract_pdf_bytes / extract_pdf_file |
src/forge_lcdl/playwright/files/pdf_reader.py |
Offline tests (10 cases with [files]) |
tests/playwright/test_pdf_reader.py |
| Stdlib minimal PDF builder | tests/playwright/fixtures/pdf_bytes.py |
| Public exports | src/forge_lcdl/playwright/files/__init__.py |
| Optional dep | pyproject.toml → [project.optional-dependencies].files includes pypdf>=4.0.0 |
Verify:
cd /home/lzvyahin/Code/forge-lcdl
pip install -e '.[files,dev]'
python -m pytest tests/playwright/test_pdf_reader.py -v --tb=short
python -m compileall -q src tests
Without [files]: 3 passed, 7 skipped (import probe, invalid metadata, missing-dep monkeypatch; extraction tests skip).
Known limitations (deferred): sources/files.py unified facade; playwright session download wiring; OCR / scanned PDFs; password-protected PDFs (blank-password attempt only); TASK_REGISTRY_V1 file contracts; live playwright tests.
Next PDCA step: 15 — unified feed processor (completed; see below).
Step 15 — Unified feed processor (Act 2026-05-31)
Status: Do + Check PASS; Act added source_kinds filter test, public id/dedupe exports, and dedupe_items ordering note.
| Deliverable | Location |
|---|---|
| Merge pipeline (filter → dedupe → sort → cap → optional group) | src/forge_lcdl/playwright/processor.py |
| Offline tests (21 cases) | tests/playwright/test_processor.py |
| Public exports | src/forge_lcdl/playwright/__init__.py |
Verify:
cd /home/lzvyahin/Code/forge-lcdl
python -m pytest tests/playwright/test_processor.py -q --tb=short
python -m pytest tests/playwright/ -q
python -m compileall -q src tests
API notes: Standalone dedupe_items returns key-sorted survivors (not time-sorted); use sort_items or process_unified_feed for newest-first feeds. ProcessedUnifiedFeed has no wire/JSON helper yet.
Known limitations (deferred): merging multiple ReadResult batches with cursor pagination; date-bucket grouping; TASK_REGISTRY_V1 contracts for processed feeds; ProcessedUnifiedFeed wire export; playwright_live integration tests.
Next PDCA step: per PDCA pack (e.g. sources/files.py facade, session download wiring, or governed task wrapping).
Step 20 — Final hardening and handoff (2026-05-31)
Status: Act complete (2026-05-31). Public API test uses only forge_lcdl.playwright exports (no direct policy import). Operator must run verification commands below to confirm green pytest locally.
Implemented surface (v1)
| Area | Paths |
|---|---|
| Core models + wire helpers | src/forge_lcdl/playwright/models.py, errors.py |
| Session protocol + guarded facade | session.py, adapters/fake.py |
| Session ref registry | session_provider.py (exported from forge_lcdl.playwright) |
| Read-only policy | policy.py |
Connectors (DOM → UnifiedItem) |
connectors/{base,outlook,teams,workday,sharepoint,onedrive}.py |
| File extraction | files/{common,docx,pptx,xlsx,pdf}_reader.py |
| Feed merge / dedupe | processor.py |
| Viewer (JSON / JSONL / Markdown) | viewer.py |
| Governed tasks (no LLM) | tasks/playwright_read_v1.py + pack playwright in tasks/packs/install.py |
| Contracts | contracts/playwright_{read_items,extract_file,render_feed}/v1/ |
| Docs | docs/playwright/{ARCHITECTURE,READONLY_POLICY,EXTENSION_POINTS,TESTING}.md |
| Offline tests | tests/playwright/** (~220+ test functions across modules) |
| Live smoke templates (skipped) | tests/integration/test_playwright_live_smoke.py, tests/playwright/live_fixtures.py |
Public import: import forge_lcdl.playwright — stable __all__; not re-exported from top-level forge_lcdl until product stabilizes.
Optional deps: pip install -e '.[files,dev]' for Office/PDF parsers (pyproject.toml extra files).
Pytest markers (configured)
| Marker | Default CI |
|---|---|
playwright |
Runs (auto on tests/playwright/ via conftest.py) |
playwright_live |
Skipped unless FORGE_LCDL_PLAYWRIGHT_LIVE=1 |
granite |
Excluded via addopts = ["-m", "not granite"] |
Verification commands
cd /home/lzvyahin/Code/forge-lcdl
pip install -e '.[dev,files]' # optional; file tests skip without [files]
python -m compileall -q src tests
python -m pytest tests/playwright/ -q --tb=short
python -m pytest -q --tb=line # full suite minus granite
python -m pytest tests/test_contract_docs_completeness.py -q
With live session (operator only):
FORGE_LCDL_PLAYWRIGHT_LIVE=1 FORGE_LCDL_PLAYWRIGHT_SESSION_ID=… \
python -m pytest -m playwright_live -q
Tests added in step 20
| File | Purpose |
|---|---|
tests/playwright/test_public_api.py |
__all__ import smoke; session provider exports; credential tool denial |
Read-only guarantees (enforced in code)
PlaywrightPolicy/filter_tool_namedeny write and credential-export operations.GuardedPlaywrightSessionwraps backends with navigation URL checks.sanitize_for_wire/redact_raw_refstrip secret-like keys and patterns.- Connectors use
navigate→snapshotonly; no Graph/REST clients in v1. - Governed tasks call
get_playwright_session_provider(); no LLM transport.
Known limitations (deferred)
| Gap | Notes |
|---|---|
| Runtime attach bridge | forge-lcdl-runtime / MCP adapter not wired in core |
Unified extract_file_by_kind facade |
Per-format readers + playwright_extract_file task cover v1 |
| Real tenant DOM selectors | Fixture data-* HTML; production mapping is adapter work |
playwright_live in CI |
Gated runner only; never default PR |
ProcessedUnifiedFeed wire helper |
Use viewer / manual dict assembly |
| MCP bridge module | Optional future step; not shipped |
Next extension points
See EXTENSION_POINTS.md: implement PlaywrightSession in runtime, register sessions on InMemoryPlaywrightSessionProvider (or host provider), tune *source_policy() presets per tenant, add mcp_bridge behind feature flag.
Step 20 complete: exports tightened, public API test (public-surface-only assertions), handoff recorded. Sign-off after local pytest block passes.