ruby_llm-browser 0.1.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 (85) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +61 -0
  3. data/CODE_OF_CONDUCT.md +11 -0
  4. data/CONTRIBUTING.md +42 -0
  5. data/LICENSE.txt +21 -0
  6. data/NOTICE.md +92 -0
  7. data/README.md +139 -0
  8. data/SECURITY.md +46 -0
  9. data/docs/API.md +177 -0
  10. data/docs/CNAME +1 -0
  11. data/docs/RAILS.md +167 -0
  12. data/docs/README.md +16 -0
  13. data/docs/RELEASE_ACTION_PLAN.md +81 -0
  14. data/docs/RELEASE_AUDIT.md +158 -0
  15. data/docs/RELEASING.md +131 -0
  16. data/docs/SECURITY_MODEL.md +117 -0
  17. data/docs/SPEC_BASELINE.md +107 -0
  18. data/docs/WORK.md +260 -0
  19. data/docs/_advanced/live-backend-qualification.md +74 -0
  20. data/docs/_advanced/rails-integration.md +56 -0
  21. data/docs/_config.yml +109 -0
  22. data/docs/_core_features/inspect-extract.md +91 -0
  23. data/docs/_core_features/offline-act.md +110 -0
  24. data/docs/_core_features/operation-lifecycle.md +63 -0
  25. data/docs/_core_features/source-policy.md +61 -0
  26. data/docs/_data/navigation.yml +12 -0
  27. data/docs/_data/sidebar.yml +8 -0
  28. data/docs/_data/social_links.yml +3 -0
  29. data/docs/_getting_started/configuration.md +68 -0
  30. data/docs/_getting_started/getting-started.md +77 -0
  31. data/docs/_getting_started/overview.md +52 -0
  32. data/docs/_plugins/versioned_docs.rb +46 -0
  33. data/docs/_reference/api-reference.md +29 -0
  34. data/docs/_reference/release-history.md +23 -0
  35. data/docs/_reference/upgrading.md +33 -0
  36. data/docs/bin/build-api.sh +23 -0
  37. data/docs/bin/build.sh +161 -0
  38. data/docs/bin/export-api-markdown.rb +53 -0
  39. data/docs/bin/link_versions.rb +54 -0
  40. data/docs/bin/postprocess_api_seo.rb +129 -0
  41. data/docs/bin/prepare_versions.rb +40 -0
  42. data/docs/bin/serve.sh +12 -0
  43. data/docs/bin/versions.yml +14 -0
  44. data/docs/index.md +82 -0
  45. data/examples/plain_ruby.rb +34 -0
  46. data/examples/rails_adapter.rb +47 -0
  47. data/lib/generators/ruby_llm/browser/install_generator.rb +29 -0
  48. data/lib/generators/ruby_llm/browser/templates/create_ruby_llm_browser_tables.rb.tt +60 -0
  49. data/lib/ruby_llm/browser/approval.rb +128 -0
  50. data/lib/ruby_llm/browser/approval_store.rb +38 -0
  51. data/lib/ruby_llm/browser/backend.rb +99 -0
  52. data/lib/ruby_llm/browser/budget.rb +90 -0
  53. data/lib/ruby_llm/browser/capabilities.rb +76 -0
  54. data/lib/ruby_llm/browser/configuration.rb +74 -0
  55. data/lib/ruby_llm/browser/context.rb +117 -0
  56. data/lib/ruby_llm/browser/doctor.rb +134 -0
  57. data/lib/ruby_llm/browser/engine.rb +326 -0
  58. data/lib/ruby_llm/browser/errors.rb +86 -0
  59. data/lib/ruby_llm/browser/fake_backend.rb +134 -0
  60. data/lib/ruby_llm/browser/immutable.rb +19 -0
  61. data/lib/ruby_llm/browser/in_memory_approval_store.rb +67 -0
  62. data/lib/ruby_llm/browser/in_memory_operation_store.rb +174 -0
  63. data/lib/ruby_llm/browser/operation.rb +218 -0
  64. data/lib/ruby_llm/browser/operation_store.rb +92 -0
  65. data/lib/ruby_llm/browser/policy.rb +298 -0
  66. data/lib/ruby_llm/browser/rails/active_record_approval_store.rb +72 -0
  67. data/lib/ruby_llm/browser/rails/active_record_operation_store.rb +231 -0
  68. data/lib/ruby_llm/browser/rails/actor_record.rb +13 -0
  69. data/lib/ruby_llm/browser/rails/approval_record.rb +49 -0
  70. data/lib/ruby_llm/browser/rails/operation_job.rb +163 -0
  71. data/lib/ruby_llm/browser/rails/operation_record.rb +56 -0
  72. data/lib/ruby_llm/browser/rails/reaper.rb +117 -0
  73. data/lib/ruby_llm/browser/rails/schema.rb +81 -0
  74. data/lib/ruby_llm/browser/rails.rb +79 -0
  75. data/lib/ruby_llm/browser/redactor.rb +159 -0
  76. data/lib/ruby_llm/browser/result.rb +102 -0
  77. data/lib/ruby_llm/browser/stagehand_backend.rb +332 -0
  78. data/lib/ruby_llm/browser/tool.rb +70 -0
  79. data/lib/ruby_llm/browser/tools/act.rb +91 -0
  80. data/lib/ruby_llm/browser/tools/extract.rb +60 -0
  81. data/lib/ruby_llm/browser/tools/inspect.rb +38 -0
  82. data/lib/ruby_llm/browser/url_guard.rb +253 -0
  83. data/lib/ruby_llm/browser/version.rb +9 -0
  84. data/lib/ruby_llm/browser.rb +79 -0
  85. metadata +267 -0
data/docs/RAILS.md ADDED
@@ -0,0 +1,167 @@
1
+ ---
2
+ layout: default
3
+ title: Rails Jobs and Recovery
4
+ description: Run bounded read-only operations with durable state and independently scheduled cleanup.
5
+ ---
6
+
7
+ # Rails jobs and recovery
8
+
9
+ Run bounded read-only operations with durable state and independently scheduled cleanup.
10
+
11
+ After reading this guide, you will know:
12
+
13
+ - How to install the adapter's tables.
14
+ - How to build a freshly authorized job context with matching budgets.
15
+ - How to inspect status, cancel work, and recover orphaned sessions.
16
+
17
+ ## Installing the adapter
18
+
19
+ Your application supplies Rails 8, Active Record, Active Job, and a database adapter.
20
+ They are not runtime dependencies of the plain-Ruby gem.
21
+
22
+ ```ruby
23
+ require "ruby_llm/browser/rails"
24
+ ```
25
+
26
+ ```bash
27
+ bin/rails generate ruby_llm:browser:install
28
+ bin/rails db:migrate
29
+ ```
30
+
31
+ The generator creates operations, approvals, and per-actor reservation-lock tables.
32
+ SQLite and PostgreSQL are accepted by the store, but only SQLite concurrency is tested
33
+ locally. MySQL is not supported by this adapter's partial indexes/locking contract.
34
+ Use a database timeout appropriate to your workload. Database lock failures fail closed;
35
+ they must not be handled by retrying a browser call with a new operation ID.
36
+
37
+ `Schema.create!` is for disposable fixtures, not production migrations or schema upgrades.
38
+ The [offline Rails example](https://github.com/yorzi/ruby_llm-browser/blob/main/examples/rails_adapter.rb) requires no full Rails app.
39
+
40
+ ## Configuring the trusted context factory
41
+
42
+ ```ruby
43
+ # config/initializers/ruby_llm_browser.rb
44
+ require "ruby_llm/browser/rails"
45
+
46
+ browser_budget = RubyLLM::Browser::Budget.new(max_seconds: 30, max_steps: 6)
47
+ RubyLLM::Browser::Rails.context_factory = lambda do |actor_id:, run_id:|
48
+ # Resolve authenticated host records and recheck authorization, consent and revocation.
49
+ # Build policy/backend from those records, never from job or model-supplied credentials.
50
+ RubyLLM::Browser::Context.new(
51
+ actor_id: actor_id, run_id: run_id,
52
+ backend: qualified_backend, source_policy: policy,
53
+ operation_store: RubyLLM::Browser::Rails.operation_store,
54
+ budget: browser_budget
55
+ )
56
+ end
57
+ ```
58
+
59
+ `qualified_backend` and `policy` above are host objects, not global APIs supplied by the
60
+ gem. The factory must use the **same operation-store instance and budget limits** as
61
+ the queued reservation. Construct different budgets per run only from trusted records
62
+ that the factory can retrieve again. An optional approval store does not enable Act in
63
+ jobs; `OperationJob` accepts only `inspect` and `extract`.
64
+
65
+ ## Enqueuing a read-only operation
66
+
67
+ ```ruby
68
+ operation = RubyLLM::Browser::Rails::OperationJob.enqueue(
69
+ actor_id: current_user.id.to_s, run_id: research_run.id.to_s,
70
+ kind: :inspect, url: "https://docs.example.com/",
71
+ budget: browser_budget, idempotency_key: "research-#{research_run.id}"
72
+ )
73
+ ```
74
+
75
+ For extraction, pass `kind: :extract` and a host-pinned JSON `schema:`. Optional
76
+ `instruction:` and `max_bytes:` have the same meaning as on the tools.
77
+
78
+ One actor/run/key identifies one exact payload and budget. A changed payload using the
79
+ same key is rejected. Repeated enqueue calls may add duplicate queue deliveries, but
80
+ they attach to the same durable operation. Starting is atomically claimed once. A job
81
+ that sees a started, unknown, pending-cleanup, or terminal operation cannot start it
82
+ again. No automatic browser retry is configured.
83
+
84
+ Reservation and queue insertion are separate transactions. If enqueueing fails, retain
85
+ the returned operation identity when available and use the same idempotency key after
86
+ investigation. Schedule the reaper to reclaim expired reservations that never started.
87
+
88
+ Configure `RubyLLM::Browser::Rails.job_queue = :browser` if you need a separate queue.
89
+ Jobs suppress argument logging, but queues still persist URLs, instructions and schemas.
90
+ Protect queue access, storage, retention and encryption in the host application.
91
+
92
+ ## Reading status and progress
93
+
94
+ ```ruby
95
+ store = RubyLLM::Browser::Rails.operation_store
96
+ operation = store.fetch!(operation_id)
97
+ # Authorize this read against the authenticated actor/run before returning anything.
98
+ summary = operation.summary
99
+ ```
100
+
101
+ `summary` omits provider session IDs and budget internals. It reports state, steps,
102
+ bytes, fence, timestamps, error code and cancellation flag. It is coarse progress,
103
+ not a percentage-complete promise. No result body is durably stored by the job; the
104
+ host must explicitly implement authorized result storage/delivery if needed. Do not
105
+ rely on an Active Job return value after asynchronous execution.
106
+
107
+ The stores are trusted server APIs, not tenant-safe controllers. They expose unscoped
108
+ fetch/sweep methods for workers; never expose them directly to model arguments or HTTP.
109
+ Tool progress callbacks are feature-detected on RubyLLM 2.1+, and contain no raw URL.
110
+
111
+ ## Cancelling and reaping
112
+
113
+ ```ruby
114
+ store.cancel(operation_id, actor_id: current_user.id.to_s,
115
+ run_id: research_run.id.to_s, now: Time.now.utc)
116
+ RubyLLM::Browser::Rails::Reaper.new(backend: qualified_backend).sweep
117
+ ```
118
+
119
+ Cancellation of a never-started reservation is terminal immediately. Otherwise it sets
120
+ `cancel_requested`, fences stale workers, and remains `cleanup_pending` with quota held.
121
+ It cannot undo an in-flight provider request. A returned session ID is persisted even
122
+ if cancellation raced with session creation.
123
+
124
+ Run a reaper regularly in an independent scheduler, not only in the operation worker's
125
+ ensure block. A reaper fences workers **before** reconciliation/cleanup, tries to end
126
+ known sessions even when provider reconciliation is unavailable, and reports `failed`
127
+ rather than claiming a database write succeeded. `ttl_seconds:` is an optional positive
128
+ idle threshold, not proof that the provider's billable session has expired.
129
+
130
+ Stagehand exposes no correlation endpoint here. An unknown start without a session ID
131
+ remains unknown and holds quota until the host supplies provider-confirmed evidence.
132
+ Alert on unknown/pending/failed entries. Never free quota just because enough local time
133
+ passed. An unknown action outcome remains in the result/error code even if session
134
+ cleanup succeeds; do not replay it.
135
+
136
+ One reaper/backend instance must cover only operations belonging to that provider
137
+ configuration. The current table has no backend credential-configuration binding.
138
+ Multi-provider/multi-credential deployments need a host-scoped store or a future schema
139
+ extension before a global sweep is safe.
140
+
141
+ ## Upgrading an earlier development snapshot
142
+
143
+ Nothing has been publicly released. If you already ran the earlier generator, preserve
144
+ your data and add an explicit migration for these new fields instead of rerunning
145
+ `create_table` or dropping the database:
146
+
147
+ ```ruby
148
+ class HardenBrowserBookkeeping < ActiveRecord::Migration[8.1]
149
+ def change
150
+ create_table :ruby_llm_browser_actors, id: :string do |t|
151
+ t.integer :sequence, null: false, default: 0
152
+ end
153
+ add_column :ruby_llm_browser_operations, :cancel_requested, :boolean, default: false, null: false
154
+ add_column :ruby_llm_browser_operations, :request_digest, :string
155
+ end
156
+ end
157
+ ```
158
+
159
+ Adapt this migration if any additions already exist. Drain workers during the upgrade.
160
+ Legacy rows without request digests are not payload-bound, so do not reuse their
161
+ idempotency keys for new work. Inspect earlier terminal cancellations for sessions that
162
+ may not have been confirmed closed before reclaiming them.
163
+
164
+ ## Next steps
165
+
166
+ - Review [the API]({% link API.md %}) for result interpretation and budget semantics.
167
+ - Review [release blockers]({% link RELEASE_AUDIT.md %}) before production deployment.
data/docs/README.md ADDED
@@ -0,0 +1,16 @@
1
+ # Documentation
2
+
3
+ Use these guides to configure browser tools and understand their limits.
4
+
5
+ - [API reference](API.md): contexts, tools, schemas, results, budgets, and backend options.
6
+ - [Rails integration](RAILS.md): durable stores, jobs, cancellation, recovery, and migrations.
7
+ - [Security model](SECURITY_MODEL.md): trust boundaries and live deployment qualification.
8
+ - [Release checklist](RELEASING.md): compatibility, package verification, and owner approvals.
9
+ - [Release audit](RELEASE_AUDIT.md): findings, fixes, and remaining publication blockers.
10
+ - [Owner action plan](RELEASE_ACTION_PLAN.md): decisions you own and work you can delegate.
11
+ - [WORK ledger](WORK.md): exact commands and offline requirement evidence.
12
+ - [Specification baseline](SPEC_BASELINE.md): product intent and governance boundaries.
13
+
14
+ Start with the runnable offline example in the [README](../README.md). No guide grants
15
+ authorization to access a source or spend provider credits. Gem releases use the reviewed
16
+ tag workflow in [the release checklist](RELEASING.md).
@@ -0,0 +1,81 @@
1
+ ---
2
+ layout: default
3
+ title: Release Action Plan
4
+ description: Record the offline first-release decision and the automated gem release path.
5
+ llms: false
6
+ ---
7
+
8
+ # Release action plan
9
+
10
+ The owner selected the offline-only first release as `0.1.0`, authorized its publication,
11
+ and requested tag-based release automation. This plan preserves the live-provider limits
12
+ and records the release checks.
13
+
14
+ ## Current position
15
+
16
+ - Hardening baseline: `0ec34d4`.
17
+ - Expanded remote CI is green: seven test jobs and the package/install/advisory job.
18
+ - Owner decision on 2026-10-18: first release is read-only; defer live Act and mark the
19
+ built-in Stagehand backend unsupported until egress and cleanup are qualified.
20
+ - No live provider calls are authorized or were run for this decision.
21
+ - Independent H1 and owner H2 are complete for exact candidate Q
22
+ `22d488987517b10dcd96d2713ed3d47e8818b36b`. H2 approves handoff, not RubyGems publication.
23
+ - The RubyGems page for `ruby_llm-browser` returned 404 during the pre-release check on
24
+ 2026-10-18; that did not reserve the name. The resolved 26-gem runtime license inventory
25
+ matches installed gemspec metadata; Stagehand is covered by its pinned source MIT text.
26
+ - The owner later instructed publication of gem version `0.1.0`; that instruction is
27
+ separate from H2's handoff approval. RubyGems MFA is enabled on the signed-in publisher
28
+ account. Trusted publishing must match this repository's `release.yml` workflow and
29
+ `release` environment.
30
+
31
+ ## Release scope
32
+
33
+ The first version supports offline `FakeBackend` operations. `StagehandBackend` remains in
34
+ the source but cannot start or operate on a live session. The full same-session Act
35
+ protocol is deferred to a later version. H1/H2 approved this scope for handoff against
36
+ exact Q `22d488987517b10dcd96d2713ed3d47e8818b36b`; the approved Specs candidate label
37
+ `0.3.0-draft` is historical provenance. The package and Git release use `0.1.0`.
38
+
39
+ ## Remaining work and ownership
40
+
41
+ | Gate | What you decide or provide | What the assistant can do |
42
+ | --- | --- | --- |
43
+ | Release scope / Act | H2 approved the read-only scope and KPI proposal against exact Q. Live Act remains a later-version feature. | Keep the Stagehand adapter disabled and preserve the H2 decision in release docs. |
44
+ | Provider qualification | No live call is authorized for the first release. A later live release needs a named deployment and approval of data-processing/billing owners. | Record the unsupported egress and cleanup guarantees. Do not use a host assertion as qualification. |
45
+ | Name and account | The owner authorized publication as `ruby_llm-browser`; the account has MFA enabled. | Recheck name availability at publication and use the pending trusted publisher configured for this repo and workflow. |
46
+ | Formal approval | H1 and H2 are complete for exact Q; C/R registration and implementation baseline receipt remain pending. | Preserve the historical approval. Do not represent H2 as RubyGems authorization; the owner's later release instruction supplies that authorization. |
47
+ | Publication | Owner instructed publication of package version `0.1.0`. | Verify the matching tag, run the release workflow, then record the RubyGems, GitHub Release and Pages outcomes. |
48
+
49
+ ## Provider questions to resolve before live tests
50
+
51
+ 1. Can the actual browser enforce approved destinations and public IP ranges at
52
+ connection time across navigation, redirect hops, subresources, iframes, popups,
53
+ WebSockets and DNS rebinding? A Ruby allowlist is insufficient.
54
+ 2. Does the actual Stagehand server return a verified final top-level URL? The SDK
55
+ permits null navigation results; the gem refuses to invent provenance.
56
+ 3. How are session TTL and confirmed closure enforced? Is a start with a lost response
57
+ recoverable by correlation? If not, unknown starts must remain held for host review.
58
+ 4. Which provider-side billing controls exist? The gem's `max_cost_usd` field is
59
+ advisory, not an enforceable currency cap.
60
+
61
+ If the provider cannot meet a required guarantee, a test passing once is not a substitute.
62
+ Keep the Stagehand backend unsupported until the missing controls have been qualified.
63
+ The current adapter does not accept `egress_isolation: :enforced`; do not add a host
64
+ assertion merely to bypass the preflight gate.
65
+
66
+ ## Authorizing a later live test
67
+
68
+ Approve the provider/deployment, public test origin you control or have permission to
69
+ use, data to be sent to browser/model services, session/request/time limits, billing
70
+ owner, and spending limit. A hard monetary ceiling requires an actual provider control.
71
+ Use controlled negative fixtures, not genuine cloud-metadata or third-party private
72
+ services. Supply credentials through local secret management, never in chat or commits.
73
+
74
+ The assistant can first prepare this plan without credentials or paid calls. If required
75
+ network isolation cannot be established, do not proceed with live browsing.
76
+
77
+ ## Provider boundary
78
+
79
+ The release instruction does not authorize provider calls or live browsing. Keep
80
+ Stagehand and live Act disabled until the missing provider guarantees are separately
81
+ qualified and approved. A Ruby source URL allowlist is not browser-side egress proof.
@@ -0,0 +1,158 @@
1
+ ---
2
+ layout: default
3
+ title: Release Audit
4
+ description: Record release fixes, provider limits, and the scope of available evidence.
5
+ ---
6
+
7
+ # Release audit: 2026-10-18
8
+
9
+ **Audit snapshot before the 0.1.0 release instruction: publication was on hold; the selected first-release scope was offline read-only.**
10
+
11
+ The original 270-example suite was green, but source review and new negative tests
12
+ found security/lifecycle defects and documentation errors. Those defects are hardened
13
+ in commit `0ec34d4`. A green offline suite does not close the provider, authorization,
14
+ name/license, or specification-governance gates below. Nothing has been published.
15
+
16
+ ## Fixed findings
17
+
18
+ | Severity | Finding | Correction / evidence |
19
+ | --- | --- | --- |
20
+ | High | A stale reserved copy or duplicate worker could start an operation again. | Atomic `claim_start`, exact fence and legal-transition checks; stale-copy and independent-connection races. |
21
+ | High | Locking existing open rows did not serialize an actor's first concurrent reservations. | Durable actor lock row written before quota checks, including an initially empty actor. |
22
+ | High | Cancellation released a live session's quota and hid it from the reaper. | Cancellation flag plus pending cleanup; quota held until exact-session closure is confirmed. |
23
+ | High | Cancellation during start or a persistence exception could bypass cleanup/session tracking. | Persist returned identity across cancellation; ensure-style close after post-start failures; regressions. |
24
+ | High | Stagehand reconciliation always returned unknown, so the reaper did not close known sessions. | Reaper fences before I/O and attempts known-session cleanup without requiring an active reconciliation response. Failed writes are reported as failed. |
25
+ | High | Model byte requests overrode host limits, operation bytes were not enforced, and cleanup overwrote counters. | Minimum of model/policy/remaining budget; retained accounting; exact encoded-byte bounding. |
26
+ | High | Extraction provider data was not validated; malformed schema shapes could pass or raise untyped errors. | JSON Schema meta-validation plus instance validation before returning data. |
27
+ | High | Nested mutable host objects could change frozen policy/approval/schema objects. | Defensive copies and immutable host bindings. |
28
+ | High | A clean install activated vulnerable Ruby default gems despite a green development lockfile audit. | Explicit patched CGI, DNS resolver, URI and zlib dependency floors; audit both the resolved lockfile and clean runtime. |
29
+ | High | Live Act accepted incomplete grants despite missing same-session/target proof. | Engine now refuses every non-fake Act before start. Production protocol remains a blocker, not a claimed fix. |
30
+ | Medium | SDK response failures/missing identities could be treated as closed or successful; final URL was guessed. | Unknown starts remain reserved; unsuccessful responses are refused; missing final URL fails closed. |
31
+ | Medium | Provider defaults permitted self-healing/CAPTCHA solving and logging; relevant ENV could drift after qualification. | Explicit disabled options; snapshot recheck; remaining-time request timeout; real-SDK HTTP regressions. |
32
+ | Medium | URL/selector progress, parser errors and Active Job arguments exposed avoidable data. | Coarse progress, generic parser/backend failures, URL query redaction, disabled job argument logging. |
33
+ | Medium | Duplicate idempotency keys could select a different queued request; factory could use a different store. | Payload/budget digest binding and job/context store identity checks. |
34
+ | Medium | Durable timestamp precision could reject a valid direct Engine call using a real clock; configured defaults were ignored. | Return canonical stored identities, honor process budget/result defaults, and test both real clocks and configuration. |
35
+ | Medium | Rails generator command did not resolve (`RubyLLM` underscored incorrectly). | Explicit namespace, lookup test, generated migration up/down test. |
36
+ | Medium | README used `egress_isolated`, omitted required `schema:`, passed numeric IDs, and misstated return type. | Runnable offline README plus API/Rails guides and tested snippets. |
37
+ | Medium | Built gem omitted guides/examples linked by the README; dependency range admitted untested Stagehand minors. | Package includes docs/examples; Stagehand restricted to 3.21.x; packaged-link checks. |
38
+ | Low | Changelog implied a release, CI missed the RubyLLM minimum, and Doctor overstated readiness. | Unreleased changelog, compatibility matrix, public Chat API tests across versions, qualification caveat in Doctor. |
39
+
40
+ The first clean runtime activated CGI 0.4.1 (CVE-2025-27219/27220), Resolv 0.3.0
41
+ (CVE-2026-80212/80213), URI 0.13.1 (CVE-2025-27221/61594), and zlib 3.1.1
42
+ (CVE-2026-27820). The project lockfile did not enumerate all of these default gems.
43
+ Patched runtime dependency floors close this resolution gap. The native interpreter
44
+ and its system libraries still require normal host security updates.
45
+
46
+ The new audit regressions were first run against the original implementation and
47
+ demonstrated the limit/schema/start/cancellation failures before correction.
48
+ Primary evidence: `spec/security/release_audit_spec.rb`,
49
+ `spec/integration/{concurrent_store,stagehand_http,generator,documentation}_spec.rb`,
50
+ and the existing engine/store/job suites. The source repository's `docs/WORK.md` records the commands and dependency versions.
51
+
52
+ ## Open live-support and publication gates
53
+
54
+ ### P1: Provider-side security and protocol qualification (BR-02; blocks live support)
55
+
56
+ The gem contains no browser firewall. A custom backend's `egress_isolation: :enforced`
57
+ capability is a declaration, not independent proof. `StagehandBackend` stays
58
+ `:unverified` and disabled. No live provider egress, TTL, billing, or cleanup
59
+ qualification was performed. Stagehand 3.21 allows null navigation results, so final-URL
60
+ reporting must be verified against an actual server. Unknown starts without session IDs
61
+ have no correlation API in this adapter and cannot be automatically proven closed. Local
62
+ time alone never releases their quota. These are provider/host evidence gaps, not
63
+ mock-test tasks.
64
+
65
+ ### P1: Full live Act protocol is not implemented (deferred beyond the first release)
66
+
67
+ The current Engine starts a new session for each call. Approval operation/session/page
68
+ bindings are optional, and a digest of observed action candidates does not prove DOM
69
+ or target revision. Keyword filters cannot prove semantic safety. Offline single-use
70
+ store behavior is correct, but it is not the required same-session host decision.
71
+ Live Act is disabled instead of presenting this as production-safe. Implement the
72
+ protocol in a later version. No unaudited opt-in flag should bypass this Engine refusal.
73
+
74
+ ### P2: Distribution ownership and approval (BR-03 / BR-04; publication blockers)
75
+
76
+ The RubyGems API returned 404 for `ruby_llm-browser` on 2026-10-18. It was
77
+ unregistered at that check, not reserved or owned. The signed-in publisher account has
78
+ MFA disabled; this gem requires MFA, so a push will be rejected until the owner enables
79
+ it. Exact name availability, trademark/affiliation wording and owner/distributor
80
+ clearance remain open. The 26-gem runtime license metadata inventory now matches the
81
+ resolved lockfile; the Stagehand source MIT text is pinned in NOTICE. This technical
82
+ inventory is not legal clearance.
83
+
84
+ Independent H1 is complete and owner H2 approved the offline read-only scope and KPI
85
+ proposal against exact Q `22d488987517b10dcd96d2713ed3d47e8818b36b` on 2026-10-18.
86
+ The approval record is in the OddlyNiche Specs handoff. Baseline registration R and
87
+ implementation receipt remain pending. H2 does not approve RubyGems publication or the
88
+ exact release artifact.
89
+
90
+ ## Limits and follow-up checks
91
+
92
+ - PostgreSQL remains untested. Local macOS Ruby 3.3.5 and the Linux Ruby 3.2/3.3/3.4
93
+ CI combinations pass with SQLite. Rails 7 is not a supported target.
94
+ - A global reaper cannot route between backend credential configurations; use one
95
+ configuration per scoped store until provider identity is durably represented.
96
+ - `max_cost_usd` is advisory. No provider-enforced currency cap or cost ledger exists.
97
+ - Retained-output bounds are not remote-response, browser-memory, or bandwidth caps.
98
+ - Sanitization is not a universal secret/PII detector. Host logs, queue arguments,
99
+ database contents and authenticated/private content require separate protection.
100
+ - Job return values are not durable result delivery. Hosts need explicit authorized
101
+ result storage if they want it. Reservation and enqueue are not a transactional outbox.
102
+ - The release hardening changes the development schema. Follow the additive migration
103
+ guidance; do not discard existing operation/approval records.
104
+
105
+ ## Checks completed
106
+
107
+ Full offline suites run against RubyLLM 2.0.0 and 2.1.1, Rails components 8.0.5.1 and
108
+ 8.1.4, Stagehand 3.21.0, and SQLite 2.10.0 on Ruby 3.3.5: **302 examples, 0 failures**
109
+ in each combination, with **89 Ruby files, no RuboCop offenses**. SDK HTTP requests are stubbed,
110
+ with all other network requests disabled. A fresh ruby-advisory-db check reports no
111
+ known vulnerable dependencies (database commit
112
+ `fa2295251efb3b0d1a300e5ce8ad700ff1f1b485`). This is a point-in-time dependency check,
113
+ not a guarantee of no vulnerabilities. Fresh installation also passes the core/Rails
114
+ examples and installed generator/migration, and the activated clean core runtime is
115
+ checked separately against advisories. Artifact verification is recorded in WORK.
116
+
117
+ ## Submission verification
118
+
119
+ Commit `0ec34d4` is pushed to the repository. The expanded
120
+ [GitHub Actions run](https://github.com/yorzi/ruby_llm-browser/actions/runs/39086570934)
121
+ passed all seven compatibility jobs (302 examples each) and the build/install/advisory
122
+ job, including `CORE_PACKAGE_PASS`, `CLEAN_RUNTIME_ADVISORIES_PASS`, and
123
+ `RAILS_PACKAGE_PASS`. This closes the expanded-CI follow-up, not the live-provider or
124
+ owner-approval gates. The source repository's `docs/RELEASE_ACTION_PLAN.md` records the owner decisions and follow-up actions.
125
+
126
+ ## Owner scope decision and qualification follow-up (2026-10-18)
127
+
128
+ The owner selected a read-only first release, deferred the full live Act protocol to a
129
+ later version, and explicitly declined live provider calls for this qualification. The
130
+ built-in `StagehandBackend` now always reports egress as unverified and refuses live
131
+ start, navigation, observation, extraction, and Act calls. The host cannot enable it with
132
+ an `:enforced` assertion. The offline `FakeBackend` remains available.
133
+
134
+ Browserbase's [Allowed Domains documentation](https://docs.browserbase.com/platform/browser/security/allowed-domains)
135
+ describes top-frame navigation checks and does not establish blocking for iframes or
136
+ subresources. This is insufficient evidence for the required browser-side egress policy.
137
+ The [Update a Session endpoint](https://docs.browserbase.com/reference/api/update-a-session)
138
+ accepts `REQUEST_RELEASE` and may return a session in `PENDING`; the [Get a Session endpoint](https://docs.browserbase.com/reference/api/get-a-session)
139
+ exposes `status` and `endedAt`. Stagehand 3.21's `SessionEndResponse.success` only says
140
+ the request succeeded, and its SDK does not expose that status lookup. The implementation
141
+ therefore sends no Stagehand provider request, including `end_`; cleanup remains
142
+ `cleanup_pending` until future work can verify terminal status. The stubbed HTTP contract
143
+ tests now assert that no cleanup request is dispatched.
144
+
145
+ No egress, TTL, billing, or cleanup live calls were made. BR-02 remains open for any
146
+ future live backend, but it does not block the offline-only first-release scope. At the
147
+ time of this initial audit snapshot, distribution decisions and the final release
148
+ instruction were pending.
149
+
150
+ ## Owner release instruction (2026-10-18)
151
+
152
+ After the initial audit snapshot, the owner selected gem version `0.1.0`, requested a
153
+ matching Git tag, instructed RubyGems publication, and requested GitHub Actions automation
154
+ for later version tags. This instruction authorizes the offline-only package release; it
155
+ does not authorize provider calls, make Stagehand supported, or implement live Act. The
156
+ approved Specs Q remains identified by its historical candidate label `0.3.0-draft`.
157
+ RubyGems publication and GitHub Pages deployment results are recorded separately from
158
+ this pre-release audit snapshot.
data/docs/RELEASING.md ADDED
@@ -0,0 +1,131 @@
1
+ ---
2
+ layout: default
3
+ title: Compatibility and Release Checklist
4
+ description: Verify the package and release a version from its matching Git tag.
5
+ ---
6
+
7
+ # Compatibility and release checklist
8
+
9
+ Build and verify the package before you publish a version. GitHub Actions releases the
10
+ gem from the matching version tag and deploys that version's documentation after success.
11
+
12
+ After reading this guide, you will know:
13
+
14
+ - Which combinations were exercised.
15
+ - How to run offline checks and build an artifact.
16
+ - Which evidence the automated release checks require.
17
+
18
+ ## Compatibility evidence
19
+
20
+ The 2026-10-18 audit runs on Ruby **3.3.5**, arm64 macOS, with Stagehand **3.21.0**,
21
+ JSONSchemer **2.5.0**, and sqlite3 **2.10.0**. Full offline suites cover:
22
+
23
+ | RubyLLM | Active Record / Active Job | Result |
24
+ | --- | --- | --- |
25
+ | 2.1.1 | 8.1.4 | PASS |
26
+ | 2.0.0 | 8.1.4 | PASS |
27
+ | 2.0.0 | 8.0.5.1 | PASS |
28
+
29
+ The expanded CI configuration includes Ruby 3.2/3.3/3.4 and RubyLLM 2.0.0/2.1.1,
30
+ plus Rails 8.0. All seven compatibility jobs and the build/install/advisory job passed
31
+ for commit `0ec34d4` in [this CI run](https://github.com/yorzi/ruby_llm-browser/actions/runs/39086570934).
32
+ Do not treat that run as evidence for later code changes or live provider guarantees.
33
+ Rails 7 and MySQL are not supported targets. PostgreSQL must be tested separately.
34
+
35
+ ## Running checks
36
+
37
+ ```bash
38
+ bundle install
39
+ bundle exec rake
40
+ bundle exec bundle-audit check --update
41
+ bundle exec ruby examples/plain_ruby.rb
42
+ bundle exec ruby examples/rails_adapter.rb
43
+
44
+ BUNDLE_GEMFILE=gemfiles/ruby_llm_2_0.gemfile bundle install
45
+ BUNDLE_GEMFILE=gemfiles/ruby_llm_2_0.gemfile bundle exec rspec
46
+ BUNDLE_GEMFILE=gemfiles/rails_8_0.gemfile bundle install
47
+ BUNDLE_GEMFILE=gemfiles/rails_8_0.gemfile bundle exec rspec
48
+
49
+ bundle exec rake build
50
+ bundle exec ruby bin/verify-package --rails --audit
51
+ ```
52
+
53
+ The default suite blocks unmocked network calls. HTTP contract tests use the actual
54
+ Stagehand client/serializers with stubbed HTTP, not paid browser sessions. Generator
55
+ tests resolve the documented Rails namespace and migrate the generated schema up/down.
56
+ Documentation/package tests check local links, shipped guides/examples and executable
57
+ offline README examples. These are bounded engineering checks, not a penetration test.
58
+
59
+ ## Verifying the built gem
60
+
61
+ Use a fresh temporary directory for every install. Install the built gem there, then
62
+ activate it from outside the checkout. Confirm its loaded entrypoint lives under that
63
+ GEM_HOME, not under the source tree. Do not use `--ignore-dependencies` to claim that
64
+ dependency resolution succeeded. A clean network-resolved install is stronger evidence
65
+ than an install that borrows the developer's existing dependencies.
66
+
67
+ `bin/verify-package` installs without `--ignore-dependencies`, activates the artifact
68
+ outside the checkout, runs shipped examples, and optionally installs Rails and tests
69
+ the shipped generator/migration. `--audit` checks the actually activated core runtime
70
+ gems against the advisory database refreshed by `bundle-audit check --update`. This
71
+ catches default gems that a development lockfile may omit. Logs and a JSON version
72
+ inventory are retained in the temporary evidence directory printed by the command.
73
+
74
+ Verify all of the following:
75
+
76
+ - Core require loads no Rails, Active Record, Active Job, or Stagehand client.
77
+ - The optional Rails require loads on demand.
78
+ - The generated migration and all README-linked guides are shipped.
79
+ - Both examples run using the installed artifact.
80
+ - The artifact contains no keys, cookies, dumps, databases, lockfiles, or test fixtures.
81
+ - Dependency/license inventory and known-advisory check match the resolved versions.
82
+ - Record the artifact SHA-256, exact versions, commands and date in the WORK ledger.
83
+
84
+ ## Release boundaries
85
+
86
+ The first-release scope is offline read-only. `FakeBackend` is the only built-in backend
87
+ supported for operations. `StagehandBackend` refuses live calls, its egress capability
88
+ stays unverified, and cleanup requests are disabled. The full live Act
89
+ protocol is deferred to a later version. No provider calls were run or authorized.
90
+
91
+ The owner selected version `0.1.0` for this package and instructed its release. H1 and
92
+ H2 approved the product handoff against exact Specs candidate Q
93
+ `22d488987517b10dcd96d2713ed3d47e8818b36b`; the candidate's historical version label is
94
+ `0.3.0-draft`. The later release instruction is separate from H2. See [the audit findings]({% link RELEASE_AUDIT.md %}) for the live-provider limits.
95
+
96
+ Keep Stagehand unsupported and live Act disabled. Before enabling either in a later
97
+ version, qualify egress across every request path, final URL reporting, TTL, cleanup
98
+ status, unknown-start recovery, and provider billing controls. No live provider call was
99
+ made for `0.1.0`.
100
+
101
+ ## Releasing a version
102
+
103
+ For a later version, update the gem version in `lib/ruby_llm/browser/version.rb` and the
104
+ documentation version in `docs/_config.yml` to the same value. Keep `released: false` on
105
+ `main`; the documentation builder uses release tags for the stable site and builds the
106
+ working tree under `/next/`. Add a matching release entry to `CHANGELOG.md` and
107
+ `docs/_reference/release-history.md`.
108
+
109
+ Run the checks above, review the package contents and push the completed commit to
110
+ `main`. Then create and push a matching tag, for example `v0.2.0` for gem version
111
+ `0.2.0`:
112
+
113
+ ```bash
114
+ git tag -a v0.2.0 -m "Release 0.2.0"
115
+ git push origin v0.2.0
116
+ ```
117
+
118
+ The `Release Gem` workflow rejects a tag that does not match both the gem and docs
119
+ versions, or has no matching changelog section. It runs the suite, dependency audit and
120
+ isolated package verification before RubyGems publishing through OIDC trusted publishing.
121
+ On success it creates a GitHub Release with the gem, checksum and matching changelog
122
+ section. The Docs workflow then builds the tagged stable docs plus current `main` docs.
123
+ Configure the matching
124
+ pending trusted publisher on RubyGems with owner `yorzi`, repository `ruby_llm-browser`,
125
+ workflow `release.yml`, and environment `release` before the first tag.
126
+
127
+ ## Next steps
128
+
129
+ - Review the requirement evidence in the source repository's `docs/WORK.md` and the [remaining audit findings]({% link RELEASE_AUDIT.md %}).
130
+ - Follow the source repository's `docs/RELEASE_ACTION_PLAN.md` for the selected scope and
131
+ future version workflow.