> ## Documentation Index
> Fetch the complete documentation index at: https://docs.encoreos.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Portal Task Automation — Integration Contract

> PF-119 integration contract for Portal Task Automation: the @/platform/automation/portal consumer seam, dispatch/status APIs, and cross-core boundaries.

# Portal Task Automation — Integration Contract (PF-119)

**Spec:** [PF-119](../../../specs/pf/specs/PF-119-portal-task-automation.md) · **Owner:** Platform Foundation · **Status:** Phase 2 (framework) built; Phase 3 (live worker) pilot pending.

Portal Task Automation is a PF capability that runs "portal bots" (log into an external
web portal with no API and repeat steps) on a shared headless-browser worker. Consumer cores
dispatch a run and receive the result **without importing any browser code or another core**
(constitution §1).

## Consumer seam (the only supported entry point)

* **Client (Vite SPA):** `@/platform/automation/portal` — `dispatchPortalRun(input)`,
  `getPortalRunStatus(runId)`, and the `usePortal*` hooks. No Playwright/Chromium is reachable
  through this import.
* **Server (edge → edge):** `supabase/functions/_shared/portal-runner.ts` —
  `dispatchPortalRun(supabase, params)`. A consumer-core edge function (e.g. CE-69) calls this
  with the service client; it enforces the ToS gate, creates the run, enqueues it to FW-46, and
  (fixture/inline) returns the terminal result.

```ts theme={null}
// Dispatch contract
dispatchPortalRun({
  flowId: string,                 // an enabled pf_portal_flows row (ToS-approved + acknowledged)
  inputs?: Record<string, unknown>, // PHI-redacted before persistence
  consumerCore?: string,          // e.g. 'ce' — recorded on the run for traceability
  consumerRef?: string,           // the consumer's own id (e.g. a prior-auth id)
}) => {
  runId: string,
  status: 'queued' | 'running' | 'succeeded' | 'healed_succeeded' | 'failed' | 'cancelled',
  result?: { confirmation_number?: string, ... } | null,
  confirmationNumber?: string | null,
  errorCode?: string | null,      // TOS_NOT_APPROVED, FLOW_DISABLED, PORTAL_AUTOMATION_UNAVAILABLE, …
}
```

## Edge / control plane (Deno — no browser)

| Function                     | verify\_jwt                          | Purpose                                                                                                      |
| ---------------------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------ |
| `portal-bot-dispatch`        | false (validateAuth in-fn)           | authz (`pf.portal_bot.run`), ToS gate (403 on block, AC-4), create run, enqueue FW-46, inline-run in fixture |
| `portal-bot-status`          | false (reads under caller JWT, RLS)  | run status + extracted result + per-step screenshot trail                                                    |
| `portal-bot-worker-callback` | false (`PORTAL_WORKER_TOKEN` header) | external worker posts terminal result + steps (Phase 3)                                                      |

Success envelope: `{ data: {...} }`. Error envelope: `{ error: { code, message } }` (sanitized,
no PHI). Edge-functions rules apply (`getCorsHeaders`, `createLogger`, service-role sentinel).

## Reused platform substrate (no parallel infrastructure)

* **FW-46 durable worker** — runs ride `workflow_execution_queue` (pgmq) with
  `trigger_type='portal_task'` + `portal_run_id`; the `workflow-executor-worker` routes them to
  the runner before any rule lookup. `pf_portal_enqueue_due_runs()` (pg\_cron, every minute)
  enqueues due schedules. Retry/DLQ/timeout handling is inherited (FR-3).
* **PF-75/76 vault** — bot credentials are stored at source `(pf_portal_flows, 'bot_credential',
  flowId)` and brokered at run time via `pf_retrieve_credential_by_source` (service-role
  friendly). Flows store a handle, never secret material (FR-5).
* **Execution transport** — `_shared/portal-transport.ts`: deterministic Playwright (Phase 3
  external worker) → AI self-heal → vision fallback; `fixture` for hermetic tests; `unavailable`
  default in the edge runtime. The orchestration + DB shape are stable across transports.

## ToS gate (AC-4)

A flow is dispatchable only when its `pf_portal_definitions.terms_of_service_status='approved'`
**and** the flow's `tos_acknowledged_at` is set. The gate is enforced in three places: the UI
enable dialog, `dispatchPortalRun` (403, no run created), and the runner (defense in depth).

## Consumer registry

| Consumer                                   | Spec        | Wiring                                                                                                                                                 | State                                                                                                  |
| ------------------------------------------ | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------ |
| CE-69 (Optum AZ prior-auth)                | CE-69       | `ce-submit-optum-pa/transport.ts` `worker` branch (opt-in, default off)                                                                                | pilot hook documented; live cutover = Phase 3 (needs external worker)                                  |
| PM-10-EN-03 (UHC IOP pre-auth interim RPA) | PM-10-EN-03 | screenshot-intake worksheet (PF-62) → `pm_prior_authorizations` draft → fill dispatched via `dispatchPortalRun` (fill-then-pause; human clicks Submit) | spec v1.1 (draft); shares the CE-69 fill lane — both front-ends produce one approved-worksheet payload |
| PM-51 (payer-portal RPA)                   | PM-51       | `pm_rpa_*` generalized onto `pf_portal_*`; PM-51 becomes a consumer                                                                                    | sequenced after the CE-69 pilot                                                                        |
| GR / FA / RH                               | —           | recorder-authored flows                                                                                                                                | future                                                                                                 |

## Failure modes

* **Worker unreachable / no browser** → run `failed` with `PORTAL_AUTOMATION_UNAVAILABLE`;
  consumer cores degrade to their existing manual fallback (e.g. CE-69 AC-6). FW-46 retry/DLQ
  applies to scheduled runs.
* **Selector drift** → AI self-heal re-locates and caches the locator into the flow's step graph
  (AC-3); a hard miss fails loudly with a screenshot — never a silent wrong-data success.
* **Cross-tenant** → impossible by RLS on every `pf_portal_*` table (AC-6, verified).
