16 KiB
Wave 3 browser authority: observations with page execution disabled
Starting Checkpoint A: bc5e1ee6922000a290371f8c2aa18802a03ffcad, tree
8e09cc2560f50a3472e06ec614d6ada028b7eb18. Branch, cleanliness, both A
commits and canonical Wave 5B ancestry were verified before edits. Existing
145-file Checkpoint A baseline passed 3369 tests, with 3 platform skips
and 2 existing xfails.
Producer decision and live evidence
The actual release Docker image was available locally:
sha256:cc2d47e2327d573af01c6b027f23d2ab0f2ee9b85d658e9eb8065bd02b9c3515
(Linux amd64). Its native binary reports exactly agent-browser 0.35.0.
The isolated local-launch probe performed:
- Fresh local browser launch with the first
--pin-tabrequest. - Create a sibling tab; capture and select an exact producer targetId.
session info --no-pin-tab, thensession info --pin-tab.- Destroy the captured target using an external test fixture.
snapshot --pin-tab.
Both re-arm calls succeeded. The snapshot also succeeded, a replacement target
became active, and there was no tab_gone. Lifecycle metadata reported
relaunchedBrowser=false, restartedBackground=false, launched=false.
The CLI's special session info path does not attach the pin fields to its
daemon request. Successful flags therefore cannot establish pin_armed_for.
The producer audit's proposed re-arm sequence is not valid in this mode.
tests/test_browser_producer_live_contract.py reproduces this defect against
the actual binary, rather than treating the defect as a passing pin contract.
The four live tests also validate target/loader stability, reload/navigation,
same-document history change, distinct same-URL pages, and exact target switch
responses. Four passed in the actual release image. Raw GUIDs/CDP capability URLs
are neither printed nor saved by the tests or production adapter.
Page/document reads and effects are unconditionally disabled before producer dispatch. Observations, matching preconditions, matching postconditions, successful pin flags, exact approval and child scope never override this gate.
Identity architecture
src/browser_identity.py owns producer validation, private configuration,
registration, observations, metadata execution, resource binding and CDP
observation. src/agent_runtime/resources.py supplies immutable types:
BrowserSessionObservation: trusted namespace, version, platform, binary digest, configuration digest, selector-only session key, one nested Wave 5BProcessIdentity, domain-separated browser GUID digest, and deterministic session-incarnation digest. No duplicated start-token abstraction.BrowserSessionResource: the observation plus mandatory owner/thread binding.BrowserPageResource: exact parent session, producer targetId, opaque loaderId, explicit page/document scope, and alias/URL audit metadata. Page authority is session + target; document authority additionally includes loader. Metadata does not participate in the authority key.
Registration is server-only, checks the installed producer and creates private owned configuration. It does not spawn or adopt a daemon/browser. Model-facing lookup never creates a session. Legacy lifecycle records are not authority. There is currently no model-facing launch/enrolment operation; default/legacy sessions without a registered observation fail closed.
An explicit trusted observation checks active producer state, captures the daemon incarnation around exact executable observation, obtains the local CDP capability, rejects lifecycle launch/replacement, validates tab schema and the absence of labels, cross-checks CDP target type, captures main-frame loaderId, detaches and rechecks daemon/browser identity. A changed session invalidates every earlier page/document observation. A changed loader invalidates document scope; a same-URL or same-alias replacement never inherits target scope.
The proposed pin re-arm is not implemented as an authority-establishing
action. pin_armed_for stays unset; even modifying this field cannot enable
page execution. No alternate pin workaround or producer fork is introduced.
Trusted producer and observation transport
Only explicit glibc Linux release binaries are allowlisted:
| Platform | Version | Native binary SHA-256 |
|---|---|---|
| linux-x64 | 0.35.0 | b7a28c3a43a7008dd02585e2e60c391c08983f7a099149caed63c9f13f57b752 |
| linux-arm64 | 0.35.0 | 92cd7d0897837ac648b9a6ab1965c69c5920e0f54df57e4295cdb1143b0541c8 |
These digests were observed from the release image's installed package. x64 was
executed live; arm64 execution remains a separate architecture gate. Selection
uses /usr/local/lib/node_modules/agent-browser/bin/agent-browser-<platform>.
Version, hash, ownership, permissions and schema are checked. No PATH search,
npx execution/download, cache glob, mtime selection or replacement download.
0.27.0, unknown versions, platforms and hashes fail closed.
The CDP sidecar accepts only loopback browser websocket capability URLs and
only Target.getTargets, Target.getTargetInfo, Target.attachToTarget,
Page.getFrameTree, Target.detachFromTarget. It does not enable domains,
evaluate, navigate, close targets or expose arbitrary CDP to tools. Frame identity
must equal the captured target and loaderId must be nonempty. Requests have
3-second bounds and bounded frame/message sizes. This is producer identity
observation, not semantic evidence or trust elevation.
The capability URL stays in a non-serializable, non-repr memory field. Metadata
revalidation connects to that captured browser endpoint, rather than calling
get cdp-url again: that getter can auto-launch a replacement. Failed or changed
daemon/CDP observations invalidate the registered session; no rediscovery/retry.
Configuration is exactly {} in an owned private cwd, with observed inode and
permissions checked. Client environment is constructed from an explicit fixed
allowlist: owned HOME/TMPDIR/socket directory, system PATH, Chromium path and
idle timeout. Ambient AGENT_BROWSER/CDP/provider/profile/state/config/proxy/XDG
settings and model subprocess environment are not inherited. Configuration is
part of the incarnation digest; credentials are not serialized.
Operation and approval boundaries
| Operation | Binding | Current execution |
|---|---|---|
session_info |
Exact registered session + caller/request | Supported metadata only; no URL/title/content, target selection or launch |
| New page, initial open, tab list, whole-session close | Session/creation producer guarantee | Disabled; no trustworthy atomic creation/control contract admitted |
| Select/close page, navigate/reload/back/forward, time wait, viewport scroll, page network/console | Exact session + target | Disabled before dispatch |
| Click/fill/press/evaluate, selector/ref interactions and waits | Exact session + target + loader | Disabled before dispatch |
| Snapshot/read/find/screenshot | Exact page, loader sandwich for any future read | Disabled before dispatch; no replacement-page read |
Failure is structured: failure_kind=browser_page_authority_unavailable,
executed=false, retryable=false, producer_capability_unavailable=true.
Missing session authority produces a separate session-unavailable failure.
No timeout or post-check can authorize execution against a replacement.
RequestAuthority version 5 carries explicit session/page ceilings. Old snapshots restore empty browser scopes. Exact proposal capture binds normalized operation, request/owner/thread and the exact session/page/document observation. Metadata execution revalidates before one-use claim and at producer entry. Restoration adds no general scope. Unsupported page approvals are never claimed/executed.
Child scopes validate parent observations before intersection. Session ceilings require exact incarnation; page ceilings require exact parent + target; document ceilings also require loader. A page child cannot acquire session control, and a document child cannot renew a replaced document. Discovery adds no authority.
Model batches, raw tab/window/frame/connect commands, labels, raw targetIds,
configuration/session/CDP/provider/profile/state flags and flag-like positional
values are rejected. page: tN is strictly validated. The preview's automatic
open/snapshot batch rewrite and native read/post-click batches/recovery engine
are removed. Raw global Playwright browser control calls fail closed as well;
remote backend/stdio identity is not page authority. Other remote/MCP transport
mechanics remain unchanged and external.
Client invocations are bounded at 20 seconds, below the source-verified 30-second read/resend floor, with held-handle kill/wait on timeout/cancellation and no Odysseus retries. Immediate producer EOF/reset retries cannot be eliminated by this wrapper. No exactly-once claim is made; all effects remain disabled.
Control state and prior unsupported paths
Private browser runtime/configuration is protected by central control-plane resolution and native launch workspace guards, including actual configured directories. Direct, symlink and hardlink tests cover it. These are pathname/ inode observations, not race-freedom claims or a new containment policy. Service-owned Wave 5B cleanup remains independent of model authority; shutdown does not discover/download/run an untrusted producer binary.
Re-audit of Checkpoint A seams found:
| Path | Remaining enforcement |
|---|---|
| PTY/native manager routes | routes/shell_routes.py:setup_shell_routes.shell_exec/shell_stream call _require_admin before _exec_shell/_generate_pty/_generate_tmux; internal/anonymous controls denied, authenticated human administration separate |
| Additional process producers | resources.ProcessResource.__post_init__ admits only frozen native producer/role combinations; process_resources.resolve_process_operation requires sealed observations |
| Raw scheduled SSH | TaskScheduler._execute_action → builtin_actions.action_ssh_command → _run_subprocess refuses SSH without an external workload adapter |
| Local Cookbook scheduled auto-stop | routes/cookbook_routes.py:setup_cookbook_routes.protect_native_control applies shell admin boundary to local mutation; tools/cookbook._cookbook_kill_session refuses registry-less local control; legacy internal shell route cannot gain administration |
| Legacy/unscoped tasks | authority.restore_task_authority → process_resources.resolve_process_operation admits no missing creation scope |
| Anonymous administration / generic app_api | owned_resources.needs_owned_binding rejects shell/model/Cookbook namespaces; _require_admin also rejects unlabelled loopback when anonymous or unauthenticated |
No model-reachable page producer entry remains in the native/research wrapper. Trusted observation/setup methods are not tools or routes. Native arbitrary program/network effects and remote workload effects retain their existing explicit launch/backend boundaries; this checkpoint adds no general network egress/provenance policy (Wave 4).
Validation and remaining release gates
wave-3-final-tests.txt contains 149 files, retaining all 145 Checkpoint A files
and the exact prior 88-file selection. Legacy positive page/batch/recovery tests
are replaced by explicit unsupported-before-dispatch tests; formatting,
filesystem, YouTube, Wave 5B ownership/cleanup and research fallback tests remain.
Final resource/authority/approval focused run: 1,425 passed. Final 149-file
integrated gate: 3,776 passed, 7 skipped, 2 xfailed. The exact old 88-file
selection and all 145 Checkpoint A files were verified as subsets of this gate.
The 7 skips are /tmp not being a symlink, applicable RLIMIT_AS already
available, the Windows Ollama startup guard, and four explicit Docker-only
producer probes. Those four probes ran separately: 4 passed on the actual
release x64 image. Index/schema/configuration checks separately passed 40 tests.
Full-suite failure classification was performed against an isolated archive of
the frozen Checkpoint A (no checkout/rewrite): replay of the initial 82 failing
cases reproduced 79. Two browser/schema regressions were corrected. The third
case, test_dispatcher_rejects_approved_document_action_without_target, passed
alone but failed identically on the frozen archive when preceded by
test_scheduler_restart_doublefire.py. That fixture permanently replaces
core.database.SessionLocal/engine with a task-only database. This is an
existing suite-order issue, not a browser authority regression. Missing Node
Playwright dependencies and legacy fixtures that expect unscoped execution
also remain explicit full-suite limitations; they are not skipped or counted
as passes. New browser test environment documentation also records the existing
memory backend owner settings required to regenerate the configuration page.
Final full repository run: 12,310 passed, 76 failed, 65 skipped, 2 xfailed,
6 subtests passed (403.66 seconds). Every final failed node was reproduced on
frozen Checkpoint A, using the scheduler-order reproduction for the document
case. This is not a green full-suite gate. Exact failed node IDs and totals
are in validation/wave-3-browser-final-results.json.
Full-suite skips include smoke/live endpoints without an instance or opt-in,
the four separately executed release producer probes, the three platform cases,
missing caldav/chromadb/fitz/openpyxl/markitdown/libmagic/Node Playwright,
ffmpeg format limitations and missing rsvg-convert. Nothing was silently
converted into a pass. The two existing strict xfails in
test_runtime_behavior_regressions.py cover negative web-search wording that
does not yet suppress the offered web tools: "Do not search the web" and
"No web search please".
Compileall, whitespace, conflict-marker and unmerged-index checks pass. The coherent fail-closed implementation is available for independent review; full-suite cleanup remains outstanding and page enabling is not merge-ready.
Exact production changes since Checkpoint A
src/browser_identity.py
src/agent_runtime/resources.py
src/agent_runtime/authority.py
src/agent_runtime/process_resources.py
src/agent_tools/web_tools.py
src/tool_execution.py
src/tool_approvals.py
src/tool_schemas.py
src/tool_index.py
src/clean_agent_preview.py
src/agent_loop.py
src/constants.py
scripts/generate_env_reference.py
website/configuration-reference.md is regenerated documentation. Runtime
instructions/schema/index no longer advertise executable page interactions.
The agent loop change is only the browser prompt snippet; it is not decomposed.
Wave 5B lifecycle mechanics and MCP transport are not modified.
python3 -m pytest -q -rs $(cat docs/runtime-decomposition/wave-3-final-tests.txt)
python3 -m pytest -q -rs
python3 -m compileall -q app.py core routes services src tests scripts
git diff --check
git grep -n -E '^(<<<<<<< |=======$|>>>>>>> )' || true
git ls-files -u
Live release probe (source checkout mounted read-only, isolated container state):
docker run --rm --network none \
-e ODYSSEUS_BROWSER_LIVE_CONTRACT=1 -e ODYSSEUS_DATA_DIR=/tmp/w3-data \
-e DATABASE_URL=sqlite:///:memory: -v "$PWD:/app:ro" \
--entrypoint python odysseus-maintainer-preview-odysseus:latest \
-m pytest -q -rs -o cache_dir=/tmp/w3-pytest-cache \
tests/test_browser_producer_live_contract.py
The x64 probes pass by proving observation contracts and the known defect. They are not a positive merge gate for enabling page effects. Re-enabling needs a separately audited/allowlisted producer that executes only while expected browser incarnation, targetId and optional loaderId still match, rejects stale state atomically before reading/effect, and does not resend an indeterminate effect. No producer changes are implemented here.
The original positive 18-case Docker gate remains mandatory before re-enabling: stable/repeated targets; reload; cross-/same-document navigation; identical URLs; close/recreate; browser and daemon replacement; popup races; destroyed targets; local-launch pin/atomic binding; exact target switch; A-F label collision; lifecycle metadata; timeout/duplicate effects; bfcache; prerender/frame invariant; strict schema. It must run per supported release architecture. Pin success and pre/post checking alone can never substitute for atomic binding.
P1: producer page/document capability unavailable; unregistered sessions and Checkpoint A compatibility paths intentionally denied. P2: private-runtime scan cost/retention, filesystem observation races and architecture-specific live coverage. Wave 4 remains responsible for effects/provenance/egress and truthful completion evidence; no Wave 4 journal or lifecycle redesign is introduced.