ruby_llm-browser 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -0
  3. data/CONTRIBUTING.md +8 -1
  4. data/README.md +25 -96
  5. data/docs/API.md +5 -172
  6. data/docs/RAILS.md +5 -162
  7. data/docs/README.md +23 -12
  8. data/docs/RELEASE_ACTION_PLAN.md +28 -13
  9. data/docs/RELEASE_AUDIT.md +19 -0
  10. data/docs/RELEASING.md +24 -12
  11. data/docs/SECURITY_MODEL.md +1 -1
  12. data/docs/WORK.md +22 -0
  13. data/docs/_advanced/development-and-testing.md +107 -0
  14. data/docs/_advanced/live-backend-qualification.md +5 -0
  15. data/docs/_advanced/rails-integration.md +147 -29
  16. data/docs/_advanced/result-delivery.md +119 -0
  17. data/docs/_config.yml +8 -4
  18. data/docs/_core_features/diagnostics.md +68 -0
  19. data/docs/_core_features/inspect-extract.md +18 -2
  20. data/docs/_data/navigation.yml +2 -0
  21. data/docs/_getting_started/getting-started.md +11 -1
  22. data/docs/_getting_started/overview.md +3 -0
  23. data/docs/_getting_started/whats-new-in-0-3.md +48 -0
  24. data/docs/_plugins/versioned_docs.rb +3 -3
  25. data/docs/_reference/api-and-configuration.md +203 -0
  26. data/docs/_reference/api-reference.md +3 -2
  27. data/docs/_reference/release-history.md +3 -1
  28. data/docs/_reference/roadmap.md +171 -0
  29. data/docs/_reference/upgrading.md +48 -14
  30. data/docs/_reference/whats-new-in-0-2.md +46 -0
  31. data/docs/index.md +2 -3
  32. data/examples/plain_ruby.rb +7 -4
  33. data/examples/rails_results.rb +141 -0
  34. data/lib/ruby_llm/browser/doctor.rb +25 -10
  35. data/lib/ruby_llm/browser/rails/operation_job.rb +16 -4
  36. data/lib/ruby_llm/browser/rails/result_delivery_error.rb +22 -0
  37. data/lib/ruby_llm/browser/rails.rb +16 -0
  38. data/lib/ruby_llm/browser/result.rb +3 -3
  39. data/lib/ruby_llm/browser/version.rb +1 -1
  40. data/lib/ruby_llm/browser.rb +11 -0
  41. metadata +11 -2
@@ -0,0 +1,119 @@
1
+ ---
2
+ layout: default
3
+ title: Rails Result Delivery
4
+ nav_order: 2
5
+ description: Persist sanitized background results in your application and recover delivery separately from browser cleanup.
6
+ ---
7
+
8
+ # {{ page.title }}
9
+
10
+ {{ page.description }}
11
+
12
+ After reading this guide, you will know:
13
+
14
+ - How to receive a background operation's bounded result.
15
+ - How to store and authorize results in your application.
16
+ - How to recover delivery without repeating browser work.
17
+
18
+ ## Persist a Result
19
+
20
+ Configure the optional Rails adapter and its context factory first. See
21
+ [Rails jobs and recovery]({% link _advanced/rails-integration.md %}). Register a trusted
22
+ callback in your initializer:
23
+
24
+ ```ruby
25
+ RubyLLM::Browser::Rails.result_delivery = lambda do |operation:, result:|
26
+ # Your host service persists result.to_h with operation.id, actor_id and run_id.
27
+ BrowserResults.persist(operation: operation, payload: result.to_h)
28
+ end
29
+ ```
30
+
31
+ `BrowserResults` is your application service. It is not a gem class. The callback
32
+ receives an immutable `Operation` snapshot and the core's sanitized, bounded `Result`.
33
+ Persist `result.to_h` or `result.to_json`, rather than the operation object, which
34
+ contains a provider session ID for trusted bookkeeping.
35
+
36
+ The callback runs synchronously in the worker after the engine attempts cleanup and
37
+ before the job returns. It receives successful, truncated, error, unknown, and
38
+ cleanup-pending results. Preflight exceptions have no `Result` and do not invoke it.
39
+ Cancellation or a changed fence detected after execution suppresses delivery. A
40
+ missing, already started, or terminal operation does not invoke it either.
41
+
42
+ The default is `nil`, which preserves the existing job return behavior. Setting the
43
+ hook to a non-callable value raises `InvalidInput`. Callback return values are ignored;
44
+ raise an exception if persistence fails.
45
+
46
+ ## Keep Storage and Access in the Host
47
+
48
+ Run the disposable, offline host example:
49
+
50
+ ```sh
51
+ bundle exec ruby examples/rails_results.rb
52
+ # RESULT_AFTER_WORKER_EXIT_PASS
53
+ # SCOPED_READS_PASS
54
+ # IDEMPOTENT_WRITE_PASS
55
+ ```
56
+
57
+ It writes a serialized Active Job to a temporary file, executes it in a separate
58
+ worker, waits for exit, and reads the schema-checked extraction from SQLite. Repeating
59
+ the job in another worker starts no browser session. The example's host table accepts
60
+ identical repeated writes and rejects conflicting bodies or actor/run identities.
61
+ Its file queue holds one job and is for demonstration; use a durable queue in your
62
+ application. The temporary database and queue file are removed on completion. Read
63
+ [the example source](https://github.com/yorzi/ruby_llm-browser/blob/main/examples/rails_results.rb).
64
+
65
+ Use a unique operation ID for idempotent writes. Repeating a write of the same result
66
+ should succeed; reject a conflicting body or actor/run identity. Retrieve with the
67
+ authenticated actor and run in the database query. Never use model-supplied actor IDs
68
+ as authorization. Recheck host revocation and retention rules when serving a result.
69
+
70
+ The gem creates no result table and adds no migrations. Keep results in your host's
71
+ database or durable storage. Do not rely on an Active Job return value or in-memory
72
+ callback state after a worker exits.
73
+
74
+ An `ok` extraction passed the pinned schema before redaction and bounding. A truncated
75
+ result can contain a preview instead of the original schema shape. Treat all page
76
+ content as untrusted. Store failures too, without interpreting `cleanup_pending` or
77
+ `unknown` as success. A stored result describes that attempt; a later reaper changes
78
+ session state, not the historical result body.
79
+
80
+ ## Recover Delivery Separately
81
+
82
+ If the callback raises, the job raises `RubyLLM::Browser::Rails::ResultDeliveryError`
83
+ with code `RESULT_DELIVERY_FAILED`. The original callback exception is not chained,
84
+ so automatic job logs cannot print its potentially sensitive message. The error
85
+ exposes `operation` and `result` to trusted, in-process host recovery code:
86
+
87
+ ```ruby
88
+ # Inside host code that catches ResultDeliveryError while these objects still exist:
89
+ RubyLLM::Browser::Rails.result_delivery.call(
90
+ operation: error.operation, result: error.result
91
+ )
92
+ ```
93
+
94
+ This retries only your callback. Reauthorize recovery in the host. Keep external side
95
+ effects idempotent and persist the result before sending notifications. For durable
96
+ notification retries, write the result and a host outbox entry in one transaction.
97
+ An exception object does not survive worker exit as durable result storage.
98
+
99
+ A failed callback does not change the operation's session state or error code. Queue
100
+ redelivery of `OperationJob` does not repeat a started browser operation and does not
101
+ redeliver its result. Keep delivery failures visible in your job monitoring; retrying
102
+ the browser job is not a result-recovery mechanism.
103
+
104
+ There is no atomic transaction across provider execution, operation bookkeeping, and
105
+ host persistence. A process crash after execution but before persistence can lose the
106
+ result. The hook offers one automatic delivery attempt, not guaranteed or exactly-once
107
+ delivery. If host storage is unavailable, a recovery handler must save the retained
108
+ result to a separate durable destination before it exits. Never recover a lost result
109
+ by replaying an uncertain browser operation automatically.
110
+
111
+ Cancellation can race after the worker's final check. If your host forbids storing a
112
+ result after revocation, enforce that rule in the persistence transaction as well as
113
+ at read time. A callback failure must not clear cancellation, pending cleanup, or quota.
114
+
115
+ ## Next Steps
116
+
117
+ Read [the API reference]({% link _reference/api-and-configuration.md %}) for result
118
+ fields and [Rails jobs and recovery]({% link _advanced/rails-integration.md %}) for
119
+ cancellation and reaper behavior.
data/docs/_config.yml CHANGED
@@ -94,16 +94,20 @@ exclude:
94
94
  - Gemfile
95
95
  - Gemfile.lock
96
96
  - README.md
97
+ - API.md
98
+ - RAILS.md
97
99
  - RELEASE_ACTION_PLAN.md
98
100
  - SPEC_BASELINE.md
99
101
  - WORK.md
102
+ - evidence
100
103
 
101
104
  # The language of code examples; `project-docs` uses it for new pages.
102
105
  code_language: ruby
103
106
 
104
107
  # Values that change between releases. Pages read them as {{ site.version }}
105
- # and {{ site.examples.NAME }}. Keep `released: false` on main; the docs builder
106
- # reads a release's version from its matching Git tag.
107
- released: false
108
- version: "0.1.0"
108
+ # and {{ site.examples.NAME }}. Candidates remain unreleased; the docs builder
109
+ # reads stable docs from the latest release tag and main under /next/.
110
+ released: true
111
+ version: "0.3.0"
112
+ stable_version: "0.3.0"
109
113
  examples: {}
@@ -0,0 +1,68 @@
1
+ ---
2
+ layout: default
3
+ title: Checking Configuration
4
+ nav_order: 5
5
+ description: Diagnose the configured backend without opening a session or making a provider request.
6
+ ---
7
+
8
+ # {{ page.title }}
9
+
10
+ {{ page.description }}
11
+
12
+ After reading this guide, you will know:
13
+
14
+ - How to check offline configuration without provider credentials.
15
+ - Which checks apply to each backend.
16
+ - Why a local pass cannot qualify a live deployment.
17
+
18
+ ## Check an Offline Backend
19
+
20
+ ```ruby
21
+ require "ruby_llm/browser"
22
+
23
+ report = RubyLLM::Browser::Doctor.new(backend: RubyLLM::Browser::FakeBackend.new).report
24
+ puts report[:mode] # => offline
25
+ puts report[:ok] # => true
26
+ puts report[:summary]
27
+ # => configuration checks passed; live provider guarantees are not verified
28
+ ```
29
+
30
+ Doctor checks RubyLLM compatibility and the fake backend's capabilities. It does not
31
+ load the Stagehand SDK, inspect provider credentials or endpoint overrides, or open a
32
+ session. You do not need provider credentials to run offline checks.
33
+
34
+ ## Interpret the Report
35
+
36
+ Use `ok` to decide whether configuration checks passed. Use `mode` to identify which
37
+ backend the report describes, not whether that backend is supported. The existing
38
+ `summary` and `checks` fields remain available. Each check has `name`, `status`, and
39
+ `detail`; statuses are `pass`, `warn`, `info`, or `fail`. Any failure makes `ok` false.
40
+
41
+ | Mode | Meaning | Checks |
42
+ | --- | --- | --- |
43
+ | `offline` | FakeBackend with fake server mode | RubyLLM and backend capabilities; no provider environment or SDK checks |
44
+ | `live` | Stagehand or another non-offline backend | RubyLLM and backend capabilities; Stagehand also checks its SDK and environment |
45
+ | `unconfigured` | No backend supplied | RubyLLM and a failed backend/egress check |
46
+
47
+ A custom backend does not depend on Stagehand diagnostics. Its capability checks
48
+ still enforce unverified egress, an unpinned local server, and unapproved custom-header
49
+ capabilities as failures. A backend's enforced-egress assertion is not independent
50
+ evidence of its deployment behavior.
51
+
52
+ ## Diagnose Stagehand Without Calling It
53
+
54
+ Pass your host-created Stagehand backend to `Doctor.new(backend:)`. It reports `live`
55
+ mode and a failed `egress_isolation` check because the built-in adapter is unsupported.
56
+ Having an installed SDK or environment credentials cannot enable live operations.
57
+
58
+ Stagehand diagnostics report the SDK version, endpoint override names, and credential
59
+ presence in the supplied environment without printing values. These observations do
60
+ not configure the backend or replace explicit constructor credentials. Doctor never
61
+ calls the provider to test credentials, billing, egress, or session cleanup.
62
+
63
+ ## Next Steps
64
+
65
+ - Read [live-backend qualification]({% link _advanced/live-backend-qualification.md %})
66
+ for the deployment evidence needed before live use.
67
+ - Use [development and testing]({% link _advanced/development-and-testing.md %}) to
68
+ exercise diagnostics in the offline browser workbench.
@@ -41,8 +41,7 @@ schema = {
41
41
  "required" => [ "title" ], "additionalProperties" => false
42
42
  }
43
43
 
44
- inspect_tool = RubyLLM::Browser::Inspect.new(context: context)
45
- extract_tool = RubyLLM::Browser::Extract.new(context: context, schema: schema)
44
+ inspect_tool, extract_tool = RubyLLM::Browser.tools(context: context, schema: schema)
46
45
 
47
46
  puts inspect_tool.execute(url: "https://docs.example.com/")[:status] # => ok
48
47
  puts extract_tool.execute(url: "https://docs.example.com/")[:data] # => {"title"=>"Example"}
@@ -54,6 +53,23 @@ instances on a configured RubyLLM chat. The host should select the context and s
54
53
  the model supplies only the URL, optional instruction, and an optional positive
55
54
  `max_bytes` limit.
56
55
 
56
+ ## Register the Read-only Pair
57
+
58
+ Once your host has a context and schema, register both tools in one call:
59
+
60
+ ```ruby
61
+ chat.with_tools(*RubyLLM::Browser.tools(context: context, schema: schema))
62
+ ```
63
+
64
+ The factory returns fresh Inspect and Extract instances in that order. Both use the
65
+ same context; Extract keeps its own immutable schema copy. It never includes Act,
66
+ loads credentials, or starts an operation. A missing context or schema is rejected
67
+ by the existing constructors. Schema contents are checked by policy when Extract
68
+ runs, before a session starts.
69
+
70
+ You can still construct either tool separately with `Inspect.new(context:)` or
71
+ `Extract.new(context:, schema:)`. That remains useful when a chat needs only one tool.
72
+
57
73
  ## Inspect a Page
58
74
 
59
75
  `Inspect` navigates to one approved URL and asks the backend for observations. It does
@@ -8,5 +8,7 @@
8
8
  url: /api-reference/
9
9
  collections:
10
10
  - reference
11
+ - title: "What's New"
12
+ url: /whats-new-in-0-3/
11
13
  - title: Releases
12
14
  url: https://github.com/yorzi/ruby_llm-browser/releases
@@ -18,7 +18,16 @@ After reading this guide, you will know:
18
18
 
19
19
  ## Installation
20
20
 
21
- The stable release is `{{ site.version }}`. Add it to your Gemfile:
21
+ {% if site.docs_unreleased %}This guide follows `main` at source version `{{ site.version }}`.
22
+ Install the default branch to use the APIs documented here:
23
+
24
+ ```sh
25
+ bundle add ruby_llm-browser --git https://github.com/yorzi/ruby_llm-browser --branch main
26
+ ```
27
+
28
+ The latest stable release is `{{ site.stable_version }}`; select its documentation
29
+ from the version menu before installing that release.
30
+ {% else %}The stable release is `{{ site.version }}`. Add it to your Gemfile:
22
31
 
23
32
  ```ruby
24
33
  gem "ruby_llm-browser", "~> {{ site.version }}"
@@ -29,6 +38,7 @@ For unreleased development changes, install the default branch instead:
29
38
  ```sh
30
39
  bundle add ruby_llm-browser --git https://github.com/yorzi/ruby_llm-browser --branch main
31
40
  ```
41
+ {% endif %}
32
42
 
33
43
  Use Ruby 3.2 or later. The gem supports RubyLLM `>= 2.0, < 3` and pins Stagehand to the
34
44
  3.21.x series. Rails is optional and is not loaded by the core entry point. See
@@ -50,3 +50,6 @@ does not click or submit forms.
50
50
  * [Getting Started]({% link _getting_started/getting-started.md %}) - install and run the offline example.
51
51
  * [Configuration]({% link _getting_started/configuration.md %}) - choose host-owned defaults.
52
52
  * [API and configuration reference]({% link _reference/api-reference.md %}) - see options, statuses, and result fields.
53
+
54
+ Read [Rails Result Delivery]({% link _advanced/result-delivery.md %}) to persist a
55
+ background result in host storage and recover delivery separately from session cleanup.
@@ -0,0 +1,48 @@
1
+ ---
2
+ layout: default
3
+ title: What's New in 0.3
4
+ redirect_from:
5
+ - /whats-new/
6
+ nav_order: 4
7
+ description: Deliver Rails background results to host storage without repeating browser work on delivery failure.
8
+ ---
9
+
10
+ # {{ page.title }}
11
+
12
+ {{ page.description }}
13
+
14
+ After reading this guide, you will know:
15
+
16
+ - How to persist the result of a background operation.
17
+ - Which delivery failures your application must recover.
18
+ - Which capabilities remain unsupported.
19
+
20
+ ## Rails Result Delivery
21
+
22
+ Register a host callback to receive each eligible job's sanitized `Result` after
23
+ execution and cleanup:
24
+
25
+ ```ruby
26
+ RubyLLM::Browser::Rails.result_delivery = lambda do |operation:, result:|
27
+ BrowserResults.persist(operation: operation, payload: result.to_h)
28
+ end
29
+ ```
30
+
31
+ Your application supplies `BrowserResults`, storage, access checks, retention, and
32
+ idempotent recovery. The gem adds no database tables or runtime dependencies. Read
33
+ [Rails Result Delivery]({% link _advanced/result-delivery.md %}) for the contract.
34
+
35
+ Run `bundle exec ruby examples/rails_results.rb` for a disposable SQLite host example.
36
+ It executes a serialized job in a separate process, waits for the worker to exit,
37
+ then verifies result retrieval, actor/run isolation, and idempotent persistence.
38
+
39
+ ## Delivery Failure Is Separate from Cleanup
40
+
41
+ A callback failure raises `Rails::ResultDeliveryError` with the retained operation and
42
+ result. It does not change session state. A duplicate browser job does not rerun the
43
+ operation or redeliver the result. Recover host delivery using the retained result;
44
+ do not enqueue a new browser operation to recover persistence.
45
+
46
+ The hook cannot guarantee delivery across worker crashes. Stagehand and live Act remain
47
+ unsupported. Review [the roadmap]({% link _reference/roadmap.md %}) for live-backend
48
+ qualification and the remaining 1.0 requirements.
@@ -6,9 +6,9 @@
6
6
  # while _config.yml says `released: false`. Pages use it to show the install
7
7
  # command for the default branch. A build of a release tag is never
8
8
  # unreleased, even when that tag forgot to set `released: true`.
9
- # - A versioned build takes `version` from _data/versions.yml, which
9
+ # - A stable or archive build takes `version` from _data/versions.yml, which
10
10
  # bin/prepare_versions.rb fills in from the tag, so a tag that forgot to bump
11
- # `version` still shows the right install command.
11
+ # `version` still shows the right install command. Next keeps its source version.
12
12
  # - An absolute canonical_url on this site becomes a path, so the theme adds
13
13
  # this build's base path (/next/, /v1/) and the page stays in its own
14
14
  # build's llms.txt and sitemap.
@@ -22,7 +22,7 @@ Jekyll::Hooks.register :site, :post_read, priority: :high do |site|
22
22
  else
23
23
  site.config['docs_unreleased'] = [ 'next', 'Next (unreleased)' ].include?(current)
24
24
  release = versions['release'].to_s
25
- site.config['version'] = release unless release.empty?
25
+ site.config['version'] = release unless release.empty? || site.config['docs_unreleased']
26
26
  end
27
27
 
28
28
  origin = site.config['url'].to_s.delete_suffix('/')
@@ -0,0 +1,203 @@
1
+ ---
2
+ layout: default
3
+ title: API and Configuration
4
+ nav_order: 3
5
+ permalink: /api-and-configuration/
6
+ description: Configure trusted host objects and interpret bounded untrusted results.
7
+ ---
8
+
9
+ # API and configuration
10
+
11
+ Configure trusted host objects and interpret bounded, untrusted results.
12
+
13
+ After reading this guide, you will know:
14
+
15
+ - How to register read-only tools.
16
+ - Which inputs belong to the host and which belong to the model.
17
+ - How to configure budgets, schemas, backends, and error handling.
18
+
19
+ ## Creating a context
20
+
21
+ Use the complete offline example in [Getting Started]({% link _getting_started/getting-started.md %}). `Context.new` requires:
22
+
23
+ | Keyword | Type and meaning |
24
+ | --- | --- |
25
+ | `actor_id`, `run_id` | Nonempty strings from authenticated host records. Convert numeric database IDs with `to_s`. |
26
+ | `backend` | A `Backend` instance. Use `FakeBackend` for offline tests. |
27
+ | `source_policy` | A host-created `Policy`. |
28
+ | `operation_store` | An `OperationStore`; durable storage is required across restarts or workers. |
29
+ | `approval_store` | Optional `ApprovalStore`, needed only for experimental Act. |
30
+ | `budget` | Optional `Budget`; defaults listed below. |
31
+ | `clock` | Optional callable returning a UTC `Time`. |
32
+ | `logger` | Optional host logger. Do not enable raw provider or tool payload logging. |
33
+ | `consent` | Optional host consent record. The gem records its presence; it does not collect or authenticate consent. |
34
+ | `metadata` | Optional host metadata. Never supply credentials here. |
35
+
36
+ The host must reauthorize actor/run access when building a context, including background
37
+ jobs. Passing IDs alone is not authentication. The model must never choose a backend,
38
+ credential, schema, source allowlist, consent record, or budget.
39
+
40
+ ## Registering tools
41
+
42
+ ```ruby
43
+ chat.with_tools(*RubyLLM::Browser.tools(context: context, schema: schema))
44
+ ```
45
+
46
+ `Browser.tools` returns fresh Inspect and Extract instances in that order, sharing
47
+ the supplied context. It never includes Act or dispatches an operation. You can also
48
+ construct either tool separately with `Inspect.new(context:)` and
49
+ `Extract.new(context:, schema:)`. Constructors reject missing host bindings; schema
50
+ contents are validated by policy when Extract runs, before a session starts.
51
+
52
+ Both tools expose `url`, optional `instruction`, and optional positive integer
53
+ `max_bytes`. `max_bytes` can only tighten the policy and operation ceilings.
54
+ `Extract` takes its schema in the constructor, not in model arguments.
55
+
56
+ `Inspect` returns **candidate interactions**, not a screenshot or arbitrary page dump.
57
+ Neither read-only tool dispatches candidates automatically. Pages may still run scripts
58
+ and initiate requests; read-only does not mean browsing has no external effects.
59
+
60
+ `Tool#execute` and RubyLLM `Tool#call` return a Hash. If you need a `Result` object, use:
61
+
62
+ ```ruby
63
+ engine = RubyLLM::Browser::Engine.new(context: context)
64
+ result = engine.inspect(url: url)
65
+ result = engine.extract(url: url, schema: schema, instruction: "Find the title")
66
+ ```
67
+
68
+ The optional `operation:` Engine keyword accepts a host-reserved operation with the
69
+ same actor/run and budget. Starting it is an atomic reserved-to-starting claim. It is
70
+ not a resume or retry API for active, unknown, or closed operations.
71
+
72
+ ## Source policy
73
+
74
+ `Policy.new(allowed_hosts: [...])` accepts exact hosts and `*.example.com` subdomains.
75
+ A wildcard does not allow the bare domain. Defaults are HTTP/HTTPS and ports 80/443.
76
+ Keep `verify_dns: true`; `resolver:` is injectable for offline fixtures.
77
+
78
+ Other options are `allowed_schemes`, `allowed_ports`, `max_result_bytes` (65,536),
79
+ `max_schema_depth` (8), `max_schema_properties` (64 root properties), and
80
+ `max_schema_bytes` (16,384). Browser-side enforcement must also cover explicitly
81
+ approved asset origins, redirects, frames, popups, WebSockets, and subresources.
82
+
83
+ ## Extraction schemas
84
+
85
+ Schemas must be bounded object JSON Schemas, passed as a Hash, JSON object string, or
86
+ an object/class supporting `to_json_schema`. Supported keywords are `type`,
87
+ `description`, `properties`, `required`, `items`, `enum`, `additionalProperties`,
88
+ `title`, `minimum`, `maximum`, `minLength`, `maxLength`, `minItems`, `maxItems`,
89
+ `format`, and `default`. Remote references and arbitrary schema extensions are refused.
90
+
91
+ Use `required` and `additionalProperties: false` when an exact shape matters. Provider
92
+ data must satisfy the schema before redaction or truncation. Invalid data returns
93
+ `PROVIDER_ERROR`; it is not returned as a successful extraction. A truncated preview
94
+ or redacted field need not retain the original schema's type. Always check the status.
95
+
96
+ ## Results and errors
97
+
98
+ `Result` copies and freezes its strings and nested data. This includes operation ID,
99
+ error code and error message; changes to constructor inputs cannot alter a retained
100
+ result. Duplicate a returned string if host code needs to edit it.
101
+
102
+ `Result#to_h` contains symbol keys: `status`, `operation_id`, `final_url`,
103
+ `captured_at` (UTC ISO 8601), `locator`, `digest`, `truncated`, `data`, and optional
104
+ `error: { code:, message: }`. Locator/data objects use string keys. JSON encodes all
105
+ keys as strings. Credential-like query values are redacted in URL provenance.
106
+
107
+ Statuses are `ok`, `truncated`, `error`, `unknown`, and `cleanup_pending`.
108
+ `Result#ok?` accepts `ok` and `truncated`; it makes no factual verification claim.
109
+ `digest` is SHA-256 of canonical JSON of the returned, sanitized data. Very small byte
110
+ limits can produce an empty string or scalar preview; `truncated` is authoritative.
111
+
112
+ Engine preflight denials raise typed errors. Tools convert them into error Hashes.
113
+ After a session starts, the Engine attempts cleanup and returns a failure result.
114
+ Cleanup failure can supersede the original result with `cleanup_pending`; inspect the
115
+ durable operation's `error_code` and arrange host review for unknown effects.
116
+
117
+ Safe error codes: `INVALID_INPUT`, `NOT_AUTHORIZED`, `UNSUPPORTED_SECURITY_CAPABILITY`,
118
+ `BUDGET_EXHAUSTED`, `SESSION_UNKNOWN`, `CLEANUP_PENDING`, `APPROVAL_REQUIRED`,
119
+ `APPROVAL_STALE`, and `PROVIDER_ERROR`.
120
+
121
+ ## Budgets
122
+
123
+ `Budget.new` defaults to `max_seconds: 60`, `max_steps: 12`, `max_bytes: 262_144`,
124
+ `max_concurrent: 1`, and `max_cost_usd: nil`.
125
+
126
+ `RubyLLM::Browser.configure` sets process defaults for `default_budget`,
127
+ `max_result_bytes`, `clock`, and `logger`. New contexts/policies use those defaults;
128
+ existing objects do not change. `configuration.backend` is only a reserved host slot:
129
+ contexts always require an explicitly supplied backend and never use a fallback.
130
+
131
+ - Steps count navigation, observation, extraction, and action calls, not start/cleanup.
132
+ - Bytes count retained sanitized result data, not all bytes the remote browser receives.
133
+ - Wall-clock checks refuse new calls after expiry. Stagehand request timeouts are bounded
134
+ by remaining time. Cleanup has a separate timeout and may outlive this budget.
135
+ - Concurrency is scoped to the actor across runs. Unknown/pending cleanup holds a slot.
136
+ - `max_cost_usd` is **advisory only**. There is no cost ledger or provider hard spending
137
+ cap in this release. Enforce billing limits in the provider/host.
138
+
139
+ ## Stagehand backend status
140
+
141
+ `StagehandBackend` is unsupported for live operations in this release. It always reports
142
+ `egress_isolation: :unverified` and `act_supported?: false`. `Engine` refuses it before
143
+ the first provider request, and direct start, navigation, observation, extraction, and
144
+ Act calls also fail closed. No constructor option or injected client enables live use.
145
+
146
+ The adapter remains in the source for future qualification. Its client pins credentials
147
+ and base URL, disables retries, self-healing, CAPTCHA solving, session recording and
148
+ session logging, and requests a provider timeout. These controls do not prove egress,
149
+ TTL, billing, final-URL reporting, or cleanup. `end_session` sends no request in this
150
+ release and returns `false`; the operation remains `cleanup_pending`.
151
+
152
+ The constructor accepts `server: :remote`, `base_url:`, `timeout: 60.0`,
153
+ `custom_headers_approved: false`, `env: ENV`, and an injected `client:` for internal
154
+ contract tests. A future live implementation must verify all relevant environment
155
+ settings, egress across every browser request path, TTL and closure status, and provider
156
+ billing controls before it can advertise support.
157
+
158
+ ## Configuration diagnostics
159
+
160
+ For diagnostics without network requests, use `Doctor.new(backend:).report`. It
161
+ returns `mode`, `ok`, `summary`, and `checks`. See
162
+ [Checking Configuration]({% link _core_features/diagnostics.md %}) for the backend-specific checks.
163
+
164
+ ## Dependency security floors
165
+
166
+ Old Ruby installations can supply vulnerable default gems even when a developer's
167
+ lockfile audit passes. Runtime requirements explicitly exclude those versions:
168
+ `cgi >= 0.4.2, < 1`, `resolv >= 0.7.2, < 1`, `uri >= 1.0.4, < 2`, and
169
+ `zlib >= 3.2.3, < 4`. A fresh install may build native extensions for these gems.
170
+ Use a supported, patched Ruby interpreter as well; these floors do not patch Ruby or
171
+ system libraries. Audit the versions actually activated by your application, including
172
+ default gems, not just a development lockfile.
173
+
174
+ ## Experimental Act
175
+
176
+ `Act.new(context:)` declares RubyLLM `requires_approval`. Its arguments are `url`,
177
+ `approval_id`, `method`, `selector`, optional `description`, and optional `arguments`.
178
+ The resolver reads the host store without consuming. Consumption immediately before
179
+ dispatch checks actor/run, exact URL/action digest, expiry, and any supplied
180
+ page/operation/session bindings. Revocation and consumption are atomic in the stores.
181
+
182
+ **Do not use this as a production authorization protocol yet.** Bindings are currently
183
+ optional; a candidate-list digest is not a DOM/target revision, and each Engine action
184
+ opens a fresh session. The same-session host approval flow required by the specification
185
+ is deferred to a later version. The Engine refuses all non-fake Act calls before starting
186
+ a session. Selector/description keyword filters are defense in depth, not semantic proof
187
+ that a click is harmless. The Stagehand adapter does not support Act in this release.
188
+
189
+ ## Rails Result Delivery
190
+
191
+ After `require "ruby_llm/browser/rails"`, set `Rails.result_delivery` to a callable
192
+ accepting `operation:` and `result:`, or `nil` to disable it. `Rails.reset!` clears the
193
+ hook. The callback runs synchronously after execution and cleanup. Return values are
194
+ ignored; exceptions become `Rails::ResultDeliveryError` (`RESULT_DELIVERY_FAILED`),
195
+ which exposes `operation` and `result` to trusted recovery code. These objects are
196
+ not automatically persisted. Duplicate browser jobs do not retry delivery. Read
197
+ [Rails Result Delivery]({% link _advanced/result-delivery.md %}) for storage, access,
198
+ cancellation, and crash boundaries.
199
+
200
+ ## Next steps
201
+
202
+ - Read [Rails integration]({% link _advanced/rails-integration.md %}) for durable workers and recovery.
203
+ - Read [security boundaries]({% link SECURITY_MODEL.md %}) before qualifying a live backend.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  layout: default
3
3
  title: API Reference
4
- nav_order: 1
4
+ nav_order: 4
5
5
  guide: false
6
6
  description: Find the RubyLLM::Browser classes for tools, policy, results, backends, and Rails jobs.
7
7
  ---
@@ -20,10 +20,11 @@ The [generated RDoc reference]({{ '/api/' | relative_url }}) documents classes a
20
20
  | Simulate one host-approved action offline | `Act`, `Approval`, `ApprovalStore` |
21
21
  | Read a bounded result | `Result` |
22
22
  | Use an offline backend | `FakeBackend` |
23
+ | Diagnose configuration without dispatch | `Doctor` ([guide]({% link _core_features/diagnostics.md %})) |
23
24
  | Adapt the Stagehand Ruby SDK | `StagehandBackend`, `Capabilities`, `Doctor` |
24
25
  | Persist operation or approval state | `OperationStore`, `ApprovalStore` and their implementations |
25
26
  | Run Rails background jobs and cleanup | `RubyLLM::Browser::Rails::OperationJob`, `Reaper` |
26
27
 
27
- Start with [Getting Started]({% link _getting_started/getting-started.md %}) for a runnable offline example. The [API and configuration guide]({% link API.md %}) explains inputs, results, limits, and errors.
28
+ Start with [Getting Started]({% link _getting_started/getting-started.md %}) for a runnable offline example. The [API and configuration guide]({% link _reference/api-and-configuration.md %}) explains inputs, results, limits, and errors.
28
29
 
29
30
  The reference is generated from source comments. Build it locally with `docs/bin/build-api.sh tmp/api`.
@@ -11,13 +11,15 @@ description: Follow release tags and their documentation for RubyLLM::Browser.
11
11
  {{ page.description }}
12
12
  {: .fs-6 .fw-300 }
13
13
 
14
- {% if site.docs_unreleased %}The current source build follows `main`. The latest stable release is `0.1.0`.
14
+ {% if site.docs_unreleased %}The current source build follows `main`. The latest stable release is `{{ site.stable_version }}`.
15
15
  {% else %}This documentation describes the `{{ site.version }}` release. Use the tag and
16
16
  release notes to identify the exact source for this version.
17
17
  {% endif %}
18
18
 
19
19
  | Release | Date | Highlights |
20
20
  | --- | --- | --- |
21
+ | [0.3.0]({% link _getting_started/whats-new-in-0-3.md %}) | 2026-10-19 | Opt-in Rails result delivery, durable host example, recovery and upgrade verification |
22
+ | [0.2.0]({% link _reference/whats-new-in-0-2.md %}) | 2026-10-19 | Read-only tool factory, backend-aware Doctor, and agent development/browser verification |
21
23
  | [0.1.0](https://github.com/yorzi/ruby_llm-browser/releases/tag/v0.1.0) | 2026-10-18 | Offline read-only Inspect and Extract, Rails adapter, and Act simulation; live Stagehand and live Act are unsupported |
22
24
 
23
25
  For changes to existing applications, follow [Upgrading]({% link _reference/upgrading.md %}).