fast-browser-with-jev
This commit is contained in:
@@ -0,0 +1,217 @@
|
||||
# Fast-browser development contracts
|
||||
|
||||
Status: frozen implementation boundary, 2026-09-19. The TypeScript source of truth is
|
||||
`extensions/fast-browser/contracts.ts`; this document explains ownership and safety
|
||||
rules, not a second protocol.
|
||||
|
||||
## Published exports
|
||||
|
||||
`BrowserBridge` remains the prototype observe/act/close contract. New work must
|
||||
implement `BrowserRuntime`, a persistent, session-owned extension of that
|
||||
contract. The published operation names are:
|
||||
|
||||
`observe`, `act`, `read`, `assert`, `navigate`, `reload`, `listTabs`,
|
||||
`selectTab`, `waitPopup`, `closeTab`, `captureEvidence`, `narrowOriginPolicy`,
|
||||
and `close`.
|
||||
|
||||
`EvidenceRequest.operation` uses the exported
|
||||
`BROWSER_EVIDENCE_OPERATIONS`/`BrowserEvidenceOperationName` allowlist. It
|
||||
contains every supported capture label except session `close`; `closeTab`
|
||||
remains a supported distinct label.
|
||||
|
||||
`BrowserOperation` is the explicit JSONL/CLI request union. `ScenarioOperation`
|
||||
wraps the existing bounded `Step` as `{op:"step",step}` and reuses the fixed
|
||||
read/assert/control request arms. It has no JavaScript, selector, raw CDP, or
|
||||
arbitrary command arm. `FAST_BROWSER_SCENARIO_FILE_VERSION` and
|
||||
`FAST_BROWSER_REPORT_VERSION` are both `1`; scenario owners may add their
|
||||
validated setup/reset/cleanup details around this union. Scenario input is an
|
||||
explicit caller-provided file path; it is not restricted to fixture IDs.
|
||||
|
||||
Assertions are finite and read-only: URL `exact` or `origin`, title `exact` or
|
||||
`contains`, and visible-text `contains`. `wait:"bounded"` uses the fixed
|
||||
3-second read/assertion budget; `wait:"none"` performs one read. A failed or
|
||||
stale read is an operation error, never a mutation or retry. `read` returns only
|
||||
bounded rendered visible text, title, and URL metadata; it excludes input/value
|
||||
properties, password values, hidden text, HTML, frames, and script output.
|
||||
|
||||
Tab IDs are opaque runtime-issued handles. A handle remains valid across calls
|
||||
for the same target; `documentId` changes on navigation/reload. `listTabs` is
|
||||
origin-filtered and deterministic. `waitPopup` returns an owned popup handle
|
||||
with `autoSwitched:false`; only `selectTab` changes the selected target. No
|
||||
operation infers a target from page text.
|
||||
|
||||
## Scenario-only tab references
|
||||
|
||||
Runtime and transport calls still use opaque `BrowserTabId` values. JSON scenario
|
||||
files use the bounded, data-only `ScenarioTabReference` instead; the CLI resolves
|
||||
it to an opaque ID at execution time. References are either the initial selected
|
||||
tab, an earlier operation's `selectedTab`/`popup` result, or an earlier
|
||||
`listTabs` result at `ScenarioTabIndex` 0–31. Operation references may use an
|
||||
explicit prior numeric `index` (0–31) or prior unique `id`; forward, missing,
|
||||
duplicate, and out-of-range references are rejected before execution.
|
||||
|
||||
A list operation may include an exact-origin or bounded-index `select` predicate.
|
||||
An origin predicate must match exactly one listed tab. Listing never changes the
|
||||
runtime selection; a later explicit `selectTab` operation does that. Popup waits
|
||||
also never auto-switch.
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"id": "popup-check",
|
||||
"operations": [
|
||||
{"id":"start","op":"read","request":{"tab":{"source":"initial","target":"selected"}}},
|
||||
{"id":"tabs","op":"listTabs","request":{"select":{"kind":"origin","origin":"http://127.0.0.1:4310"}}},
|
||||
{"id":"selected","op":"selectTab","request":{"tab":{"source":"operation","operation":{"id":"tabs"},"result":"listedTab","index":0}}},
|
||||
{"id":"popup","op":"waitPopup","request":{"opener":{"source":"operation","operation":{"id":"selected"},"result":"selectedTab"}}}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`selectedTab` is produced by `selectTab`, `popup` by `waitPopup`, and `listedTab`
|
||||
by `listTabs`; there is no interpolation, templating, selector, or script arm.
|
||||
|
||||
## Scenario fixture/server lifecycle
|
||||
|
||||
`ScenarioDefinition.fixture` is optional. When present, `baseUrl` is an explicit
|
||||
credential-free HTTP(S) URL and `server` is either:
|
||||
|
||||
- `{"mode":"managed","start":...,"readiness":...}`: the CLI starts one
|
||||
long-lived child with trusted argv (`shell:false`), optional cwd, and existing
|
||||
env-reference shape, then owns that process until the scenario `finally` path;
|
||||
- `{"mode":"external","readiness":...}`: no child is started. The caller
|
||||
supplies the explicit already-running readiness URL.
|
||||
|
||||
`ScenarioHttpReadiness` is GET-only and contains only `url`, optional expected
|
||||
status, and a bounded timeout. It has no headers, body, shell, or page-derived
|
||||
command fields. Setup/reset/cleanup hooks remain one-shot hooks and are distinct
|
||||
from the managed server child. The CLI's existing scenario-wide
|
||||
`budgets.timeoutMs` starts before any hook or server startup; no second global
|
||||
deadline knob is added. The runner polls readiness within the published
|
||||
`FAST_BROWSER_SCENARIO_SERVER_BOUNDS`, keeps the managed child owned through all
|
||||
operations, and performs bounded shutdown in `finally`. Existing definitions
|
||||
without a managed server remain valid when the CLI/user config provides an
|
||||
explicit external readiness URL.
|
||||
|
||||
Example execution order:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"id": "fixture-login",
|
||||
"fixture": {
|
||||
"baseUrl": "http://127.0.0.1:4310",
|
||||
"server": {
|
||||
"mode": "managed",
|
||||
"start": {"command":"npm","args":["run","fixture"]},
|
||||
"readiness": {"url":"http://127.0.0.1:4310/health","expectedStatus":200}
|
||||
}
|
||||
},
|
||||
"operations": [{"op":"step","step":{"instruction":"Continue","action":"click","postcondition":{"kind":"text","value":"Done"}}}]
|
||||
}
|
||||
```
|
||||
|
||||
Order is: start the existing global scenario budget → setup hook → managed
|
||||
server start and bounded GET readiness (or external readiness) → reset hook →
|
||||
browser operations → finally close browser/runtime, run cleanup while the owned
|
||||
server is still available, bounded server stop, and close uncommitted artifact
|
||||
resources while retaining committed artifacts. A readiness failure stops before
|
||||
browser operations and never becomes a page-derived command.
|
||||
|
||||
## Policy, budgets, and errors
|
||||
|
||||
`RuntimeOriginPolicy` preserves the current `"*"` default or a non-empty exact
|
||||
origin list. `narrowOriginPolicy` computes a `RuntimeOperationContext` for an
|
||||
effective request, constrained by the immutable session base; it never
|
||||
irreversibly tightens later calls. Pass that context to the scoped calls, or
|
||||
omit it on a later call to restore the session base. A restricted base never
|
||||
widens. Navigation accepts only bounded HTTP(S) URLs without credentials whose
|
||||
origin is approved. A runtime owns one session's CDP/worker state and
|
||||
serializes its calls; distinct sessions may run in parallel. Its lifetime is
|
||||
the browser-session lifetime, not the first tool call. Cancellation must stop
|
||||
the worker/daemon and release no resource until teardown is confirmed. Close is
|
||||
idempotent and best effort.
|
||||
|
||||
`FAST_BROWSER_BUDGETS` publishes defaults and hard ceilings for startup (10s),
|
||||
worker readiness (5s), bridge request (10s/120s ceiling for the existing
|
||||
compatibility seam), action verification (3s), read/assertion wait (3s),
|
||||
navigation/reload (10s), popup wait (5s), screenshot (10s/60s ceiling), failure
|
||||
evidence (2s), and a run (60s/120s). Do not add a generic unbounded timeout
|
||||
options object. Failure capture is bounded and best effort; it must never mask
|
||||
the original operation result or retry a mutation.
|
||||
|
||||
All safe failures use `BrowserErrorDetails`/`BrowserOperationError`, with an
|
||||
operation, phase, safe message, error code, retry classification, and mandatory
|
||||
`mutationState` (`not_attempted`, `verified`, or `unknown`). Raw exceptions are
|
||||
not reportable.
|
||||
|
||||
## Transport boundary
|
||||
|
||||
`BrowserTransport` is the typed JSONL boundary. Its exact API is
|
||||
`dispatch<Operation extends BrowserOperation>(request: BrowserTransportRequest<Operation>,
|
||||
signal): Promise<BrowserTransportResultFor<Operation>>` and
|
||||
`close(): Promise<void>`. `BrowserTransportRequest` contains one
|
||||
`BrowserOperation` plus an optional scoped `RuntimeOperationContext`;
|
||||
`BrowserTransportResultFor<Operation>` is the exact result mapping for every
|
||||
`BrowserOperation` variant, and `BrowserTransportResult` is its complete union.
|
||||
Evidence transport uses `BrowserTransportArtifactPayload`: JSONL
|
||||
`encoding:"base64"`, `dataBase64`, declared `byteLength`, content type, kind,
|
||||
and truncation flag. The runtime decodes those bytes and writes them to the
|
||||
parent `PrivateArtifactStore`; no caller path crosses into the worker.
|
||||
`FAST_BROWSER_ARTIFACT_LIMITS` caps screenshots at 8 MiB and diagnostics at
|
||||
256 KiB. `BrowserTransportFactory.create({cdpUrl, allowedOrigins, python?},
|
||||
parentSignal?)` owns worker framing, validation, deadlines, and teardown.
|
||||
`BrowserRuntime` owns persistent tab/policy state and delegates to this
|
||||
transport; these layers must not both implement the JSONL protocol.
|
||||
|
||||
`BrowserRuntimeOptions.python` and `BrowserTransportOptions.python` are the
|
||||
explicit interpreter-selection seam for the pinned environment. If omitted,
|
||||
the existing configured/default bridge resolution remains responsible for
|
||||
choosing Python; no worker may silently drop to a user-global environment when
|
||||
an explicit interpreter/factory is supplied.
|
||||
|
||||
## Evidence and artifact ownership
|
||||
|
||||
`PrivateArtifactStore.open(owner, kind)` is the only write API. Evidence
|
||||
requests carry the caller/scenario `runId`; the runtime creates the operation
|
||||
identity. There is no caller-selected path. The session-owned store creates
|
||||
private directories and atomically commits `0600` files under its private root,
|
||||
conceptually `<root>/<sessionId>/<runId>/<artifactId>.(png|json)`. The returned
|
||||
`PrivateArtifactMetadata` records owner, path, byte count, content type, and
|
||||
`localOnly:true/modelForwarded:false`. Runtime close closes the store. Screenshots
|
||||
are local failure/request artifacts and are never automatically sent to an LLM
|
||||
or provider.
|
||||
|
||||
`captureEvidence` may return a partial/unavailable bundle. Console summaries are
|
||||
bounded level/message/count records. The pinned browser-harness 0.1.13 source
|
||||
supports `drain_events()` and enables Page/DOM/Runtime/Network on tab attach;
|
||||
implementers may consume buffered `Runtime.consoleAPICalled` and
|
||||
`Network.loadingFailed` events only if present. Do not invent a helper method or
|
||||
claim complete console history. Network summaries contain method, origin-only
|
||||
URL, status/category—never query/fragment tokens, headers, cookies, or bodies.
|
||||
Console text can still contain arbitrary page content; local/private and bounded
|
||||
is not the same as secret-free, so public shaping must redact it.
|
||||
|
||||
## File ownership / update order
|
||||
|
||||
- `contracts.ts` — shared API owner (this change); additions must remain typed.
|
||||
- `bridge.ts` — later JSONL transport validation and request deadlines; no new
|
||||
public operation names without updating `contracts.ts` first.
|
||||
- `browser_worker.py` — later pinned browser-harness adapter and persistent
|
||||
target/diagnostic state; use only observed harness capabilities.
|
||||
- `browser-session.ts` — browser process/profile/CDP ownership and parallel
|
||||
session isolation; it does not own tab protocol state.
|
||||
- `controller.ts` — later bounded action orchestration and automatic
|
||||
failure-evidence call; preserve stale/no-blind-retry mutation semantics.
|
||||
- `index.ts` — later public redaction, tool/CLI wiring, and origin narrowing;
|
||||
never expose private artifact contents or raw URL paths.
|
||||
- `fb-scenarios-ci` — scenario file parsing, trusted argv/config hooks, CLI,
|
||||
reports, and CI examples. Composition requires an explicitly supplied model
|
||||
adapter and returns a capability error when absent; never silently selects a
|
||||
provider.
|
||||
|
||||
Existing test mocks that implement only `BrowserBridge` need no compatibility
|
||||
aliases; runtime mocks must implement every `BrowserRuntime` method. The
|
||||
scenario worker should import the exports above rather than define alternatives.
|
||||
Reference in New Issue
Block a user