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
@@ -0,0 +1,117 @@
1
+ ---
2
+ layout: default
3
+ title: Security Model
4
+ description: Understand what the gem enforces and what a deployment must prove independently.
5
+ ---
6
+
7
+ # Security model
8
+
9
+ Understand what the gem enforces and what a deployment must prove independently.
10
+
11
+ After reading this guide, you will know:
12
+
13
+ - Which inputs are trusted.
14
+ - Why local URL validation does not secure a remote browser.
15
+ - How sessions, cancellation, unknown outcomes, and secrets are handled.
16
+
17
+ ## Trust boundaries
18
+
19
+ The host owns authentication, actor/run authorization, consent, provider configuration,
20
+ source grants, budgets and approval issuance. The model, browser page and provider
21
+ payload are untrusted. A `Context` expresses a host decision; it does not authenticate
22
+ that decision. Unscoped store APIs are for trusted workers, not public controllers.
23
+
24
+ ## Destinations and egress
25
+
26
+ URL validation rejects unsupported schemes/ports, userinfo, ambiguous numeric IP forms,
27
+ unapproved hosts, and local/private/link-local/metadata/reserved IP answers, including
28
+ IPv6 and IPv4-mapped addresses. Real DNS validation is enabled by default.
29
+
30
+ These checks run in Ruby. They do **not** enforce a remote browser's DNS view, redirects,
31
+ iframes, popups, WebSockets, subresources, scripts or connection-time rebinding.
32
+ `Policy#authorize_egress!` refuses an unverified backend before starting a session. The
33
+ built-in `StagehandBackend` always reports unverified egress and is unsupported for live
34
+ operations in the first release. No constructor option can change that. A custom backend
35
+ must enforce and evidence its own browser-side egress controls.
36
+
37
+ Before a host makes that assertion, it must test every request path against a controlled
38
+ public test origin and prove private/metadata addresses remain unreachable at connection
39
+ time. Asset origins must be explicitly approved by the network layer. Also qualify
40
+ local-mode browsers; a local browser is not a sandbox by default.
41
+
42
+ Stagehand navigation results may be null. The adapter refuses to invent a final URL in
43
+ that case. A live deployment must establish how its actual server reports a verified
44
+ current URL; the SDK schema alone is not evidence. The current release has no supported
45
+ Stagehand deployment. Returned page data remains untrusted.
46
+
47
+ ## Operation identity and recovery
48
+
49
+ - Reserve operation identity and actor quota before start. Claim reserved-to-starting
50
+ atomically, checking the exact fence. A duplicate job cannot start a second session.
51
+ - Rails idempotency keys bind the exact payload and budget. Per-actor write-lock rows
52
+ serialize even the first two reservations when there are no open operation rows.
53
+ - Persist returned session identity before further calls, including a return that races
54
+ with cancellation. A persistence exception still triggers a cleanup attempt.
55
+ - Disable SDK retries at client and request levels. The SDK's idempotency option is not
56
+ treated as a provider guarantee: its client has no configured idempotency header.
57
+ - Cancellation cannot undo a dispatched request. It fences stale workers and holds quota
58
+ in `cleanup_pending` until provider-confirmed closure. A never-started reservation can
59
+ become terminal immediately.
60
+ - A reaper fences before reconciliation/cleanup. It attempts to close known session IDs
61
+ even if reconciliation is unavailable. Database/provider failures never count as
62
+ successful cleanup. A local deadline or idle TTL is not proof of provider expiry.
63
+ - A start whose session ID is unknown cannot be automatically reconciled by the current
64
+ Stagehand adapter. Hold quota and escalate to host/provider review. Never start a new
65
+ operation merely to conceal or retry an uncertain outcome.
66
+
67
+ SQLite concurrency is tested with independent connections. PostgreSQL remains untested
68
+ in this audit. A global reaper must use the same backend configuration as the operations
69
+ it sees; multi-backend routing is not represented in the schema.
70
+
71
+ ## Actions and approvals
72
+
73
+ **Live Act is disabled at the Engine boundary**, including when a backend advertises
74
+ action support. The first release is read-only; the full live Act protocol is deferred to
75
+ a later version. Offline simulation checks RubyLLM approval integration, read-only grant
76
+ resolution, atomic single-use consumption, actor/run/action/URL and supplied revision
77
+ bindings, expiration, revocation, replay, and unknown dispatch outcomes.
78
+
79
+ That is not the complete production protocol: operation/session/page bindings are
80
+ optional on the current Approval object; a candidate-action hash is not a DOM/target
81
+ revision; and each Engine call starts a new session. Implement and verify same-session
82
+ host approval and fresh target/policy checks before enabling live Act.
83
+
84
+ Keyword filters on selectors/descriptions cannot prove semantic safety. An innocuously
85
+ named button can purchase or publish. They are only defense in depth. Purchases,
86
+ posting, account changes, credentials and access-control bypass remain unsupported.
87
+
88
+ ## Output, schema, and secret handling
89
+
90
+ Extraction data is validated against the host schema before redaction/truncation. Model
91
+ byte requests cannot increase policy ceilings. The retained serialized JSON fits both
92
+ the policy ceiling and remaining operation bytes, including UTF-8 and escaped text.
93
+ These limits do not cap remote browser bandwidth or provider response allocation.
94
+
95
+ The redactor removes credential-like fields, known credential literals and WebSocket
96
+ DevTools endpoints. URL provenance removes userinfo, fragments and credential-like
97
+ query values. Error messages do not include raw SDK bodies or parser input. Progress
98
+ messages contain no raw URL/selector. Active Job argument logging is disabled.
99
+
100
+ This is **not a general secret or PII detector**. Arbitrary page secrets, screenshots,
101
+ credentials under innocuous names and secrets not supplied to the redactor cannot be
102
+ reliably identified. Do not browse authenticated/private content; do not put credentials
103
+ in URLs or instructions; prevent host/RubyLLM/HTTP debug logging from recording payloads.
104
+ Queue data and approval action arguments may persist in the host database; encrypt,
105
+ scope and retain them appropriately. No browser cookies/profiles are supplied for reuse.
106
+
107
+ The adapter passes explicit SDK credentials/base URL and rejects unapproved custom
108
+ headers. Changes to relevant process environment values invalidate qualification.
109
+ Environment injection is for diagnostics/tests; the real SDK reads actual process ENV.
110
+ Do not mutate ENV during concurrent browser work. Session requests disable self-healing,
111
+ CAPTCHA solving and provider recording/logging; actual provider behavior still needs
112
+ qualification. Cleanup and TTL are best-effort, and cost values are advisory.
113
+
114
+ ## Next steps
115
+
116
+ - Follow [Rails recovery]({% link RAILS.md %}) to schedule cleanup and protect status access.
117
+ - Review [the release audit]({% link RELEASE_AUDIT.md %}) for evidence and remaining blockers.
@@ -0,0 +1,107 @@
1
+ ---
2
+ layout: default
3
+ title: Specification Baseline and Provenance
4
+ description: Record product intent, evidence sources, and project ownership boundaries.
5
+ llms: false
6
+ ---
7
+
8
+ # Specification baseline and provenance
9
+
10
+ This repository is the independent implementation of the `ruby_llm-browser` Gem. It is
11
+ the source of truth for code, tests and the WORK ledger. The product intent lives in a
12
+ separate specifications repository; this file records exactly which baseline was used.
13
+
14
+ ## Baseline
15
+
16
+ | Field | Value |
17
+ | --- | --- |
18
+ | Intent source repository | `yorzi/oddlyniche`, folder `ruby-llm-browser` |
19
+ | Reviewed candidate Q | `22d488987517b10dcd96d2713ed3d47e8818b36b` |
20
+ | H2 approval/content commit C | `678f9faed6dfdd8e55a7c54566f94cfa1ad1bc0a` |
21
+ | Registration commit R | Pending; not registered |
22
+ | Source commit date | 2026-10-18 |
23
+ | Approved Specs candidate version at Q | `0.3.0-draft` (historical label; unchanged) |
24
+ | Implementation and gem release version | `0.1.0` (owner-selected 2026-10-18) |
25
+ | Spec lifecycle | `HANDOFF_READY`; independent H1 complete and owner H2 approved for exact Q |
26
+ | Implementation authorized | 2026-10-17; H2 handoff approved 2026-10-18 |
27
+ | RubyGems publication | Owner instructed release of `0.1.0` on 2026-10-18; this later instruction is separate from H2 |
28
+ | Gem | `ruby_llm-browser` |
29
+ | Namespace | `RubyLLM::Browser` |
30
+ | Require path | `ruby_llm/browser` |
31
+ | Working repository | `yorzi/ruby_llm-browser` |
32
+
33
+ ## Pinned upstream facts used (source-read at the spec baseline)
34
+
35
+ | Dependency | Pin | Notes |
36
+ | --- | --- | --- |
37
+ | Stagehand Ruby SDK | `stagehand` 3.21.0, tag `v3.21.0` (`b389c6e`) | Ruby `>= 3.2`; REST client, not a sandbox |
38
+ | RubyLLM | 2.0.0 and 2.1.1 | `Chat#with_tools` accepts Tool instances; `requires_approval` since 2.0; `progress`/`defer` since 2.1 |
39
+
40
+ ## S0 decisions recorded here
41
+
42
+ 1. **Hooks:** `Tool#progress` and `Tool.defer` exist only in RubyLLM 2.1+. This Gem
43
+ **feature-detects** them instead of raising the gemspec floor to `>= 2.1`, so the
44
+ supported floor stays `>= 2.0, < 3` (matching the 2.0.0 / 2.1.x matrix).
45
+ 2. **Stagehand dependency:** declared as a runtime dependency (`~> 3.21`) for the
46
+ experimental adapter and cleanup path, but **required lazily** so requiring the Gem
47
+ never reads `STAGEHAND_*`/`BROWSERBASE_*`/`MODEL_API_KEY` environment variables or
48
+ triggers the SDK's local-server binary download. The adapter is unsupported for live
49
+ operations in the first release.
50
+ 3. **Rails:** optional, loaded only via `require "ruby_llm/browser/rails"`.
51
+ 4. **Frame and lib style:** explicit `require_relative` chain (no Zeitwerk loader) so the
52
+ load graph is fully predictable and the no-Rails guarantee is easy to prove.
53
+
54
+ ## Audit corrections (2026-10-18)
55
+
56
+ - Restrict Stagehand to `~> 3.21.0`, not all future 3.x minors; add JSONSchemer for
57
+ schema/instance validation.
58
+ - The Engine now refuses live Act. The full same-session/page/target protocol in this
59
+ baseline is not implemented; earlier fake-only PASS claims did not establish it.
60
+ - Final URLs are no longer inferred when navigation results omit them. The pinned SDK
61
+ permits null results, so actual server behavior must be qualified.
62
+ - SQLite's ignored SELECT FOR UPDATE needs an actual write lock; actor-lock rows and
63
+ atomic start claims now enforce reservations and duplicate-delivery safety.
64
+ - Cancellation holds quota after dispatch until cleanup is confirmed. Prior terminal
65
+ cancellation and swallowed reaper-write failures were not evidence of safe cleanup.
66
+ - No cost ledger or hard currency cap is implemented. Retained-result limits do not
67
+ cap provider response allocations or browser bandwidth.
68
+
69
+ These corrections harden engineering behavior. The owner separately approved exact
70
+ Specs candidate Q `22d488987517b10dcd96d2713ed3d47e8818b36b` at H2 on 2026-10-18;
71
+ that approval does not authorize live use. The owner later authorized release of gem
72
+ version `0.1.0`. The historical candidate label remains `0.3.0-draft` so this record
73
+ does not rewrite the approved Q. See
74
+ [the audit]({% link RELEASE_AUDIT.md %}) for unresolved protocol/product decisions.
75
+
76
+ ## Outstanding blockers (carried from the spec baseline)
77
+
78
+ These are **not** satisfied by this repository yet. BR-02 blocks live support. The owner
79
+ has authorized the offline-only `0.1.0` release despite the unresolved live blocker; the
80
+ release does not claim to resolve BR-02.
81
+
82
+ - **BR-02 / live blocker:** a real browser provider must prove browser-side egress
83
+ isolation across navigation, redirects, iframes, popups, subresources, WebSockets and
84
+ metadata routes before live mode is enabled. The first release keeps Stagehand
85
+ unsupported and makes no live calls. Ruby-side URL/DNS checks cannot prove egress.
86
+ - **BR-03 / distribution identity:** the package identifies itself as an unofficial
87
+ community extension and disclaims endorsement. Recheck name availability at publish
88
+ time. The dependency license inventory is technical evidence, not legal clearance.
89
+ - **BR-04:** independent H1 and owner H2 are complete for the exact Q above. Baseline
90
+ registration R remains pending. H2 approved handoff; the owner's later `0.1.0` release
91
+ instruction authorizes publication separately.
92
+
93
+ ## Owner scope decision (2026-10-18)
94
+
95
+ The owner selected read-only gem version `0.1.0`, deferred the full live Act protocol to a
96
+ later version, and declined live provider calls. This narrows the first release to the
97
+ offline `FakeBackend` and does not close BR-02 for later live support. Stagehand always
98
+ reports unverified egress, refuses live operations, and sends no provider call including
99
+ cleanup. Unresolved sessions remain `cleanup_pending`. H1/H2 are approved for exact Q;
100
+ registration R remains pending. The later release instruction is recorded separately
101
+ from that historical Specs approval.
102
+
103
+ ## Evidence rule
104
+
105
+ Every functional requirement case records test ID, exact versions, run command, fixture,
106
+ result (`PASS` / `FAIL` / `NOT RUN`) and date in `docs/WORK.md`. Mocked tests never
107
+ prove remote browser egress. `NOT RUN` is recorded as `NOT RUN`, never as `PASS`.
data/docs/WORK.md ADDED
@@ -0,0 +1,260 @@
1
+ ---
2
+ layout: default
3
+ title: WORK Ledger
4
+ description: Track requirement evidence with exact fixtures, commands, dependency versions, and limits.
5
+ llms: false
6
+ ---
7
+
8
+ # WORK ledger
9
+
10
+ The implementation repository owns this ledger. Each functional requirement records the
11
+ test ID, exact dependency versions, run command, fixture, result and date. The rule is
12
+ simple: `NOT RUN` is written as `NOT RUN`, never as `PASS`.
13
+
14
+ ## Environment used for local runs
15
+
16
+ | Field | Value |
17
+ | --- | --- |
18
+ | Ruby | 3.3.5 (arm64-darwin) |
19
+ | RubyLLM | 2.1.1 |
20
+ | Stagehand | 3.21.0 (development only; never called live) |
21
+ | Rails / Active Job | active_record 8.1.4 + active_job 8.1.4, in-memory SQLite (adapter now exercised) |
22
+ | Command | `bundle exec rspec` and `bundle exec rubocop` |
23
+
24
+ ## Progress slices (S0 → S4)
25
+
26
+ | Slice | Deliverable | State |
27
+ | --- | --- | --- |
28
+ | S0 | Bootstrap: gemspec, version, Ruby-only fixture, CI, OSS docs, baseline record | DONE |
29
+ | S1 | Read-only `Inspect`/`Extract` core with fake backend, policy, bounded results | DONE |
30
+ | S2 | Stagehand adapter, durable operation store, quotas, TTL/reaper, reconciliation | PARTIAL: Stagehand disabled for first release; live egress/final-URL/TTL/cleanup and unknown-start correlation unqualified |
31
+ | S3 | Opt-in `Act` with one-use approvals, Rails 8 jobs, cancellation, negative tests | PARTIAL: read-only Rails jobs tested; offline Act simulation only; full live protocol deferred |
32
+ | S4 | Full matrix, clean install, security/compat/license docs, human-approved release | PARTIAL (Gem builds and clean-installs; compatibility matrix + public release pending) |
33
+
34
+ ## Functional-requirement evidence
35
+
36
+ All rows start as `NOT RUN` and are filled in as tests land. Columns: test file(s), run
37
+ command, result, date. Every `PASS` below is a **fake-backend / offline** result; no row
38
+ proves remote browser egress and no live Stagehand call has been run.
39
+
40
+ | FR | Positive | Denial / fault | Result | Date |
41
+ | --- | --- | --- | --- | --- |
42
+ | FR-01 Tool + configuration | `spec/ruby_llm/browser/inspect_spec.rb`, `spec/ruby_llm/browser/extract_spec.rb`, `spec/ruby_llm/browser/context_spec.rb` | `spec/ruby_llm/browser/inspect_spec.rb`, `spec/ruby_llm/browser/extract_spec.rb`, `spec/integration/require_spec.rb` | PASS (fake, no Rails) | 2026-10-17 |
43
+ | FR-02 session/operation | `spec/ruby_llm/browser/engine_spec.rb`, `spec/ruby_llm/browser/fake_backend_spec.rb` | `spec/ruby_llm/browser/engine_spec.rb`, `spec/ruby_llm/browser/in_memory_operation_store_spec.rb` | PASS (fake lifecycle); live Stagehand NOT RUN | 2026-10-17 |
44
+ | FR-03 read-only inspect | `spec/ruby_llm/browser/engine_spec.rb`, `spec/security/url_guard_spec.rb` | `spec/ruby_llm/browser/engine_spec.rb`, `spec/security/url_guard_spec.rb` | PASS (policy/engine); remote-browser egress NOT RUN | 2026-10-17 |
45
+ | FR-04 structured extract | `spec/ruby_llm/browser/extract_spec.rb`, `spec/ruby_llm/browser/engine_spec.rb` | same | PASS (fake) | 2026-10-17 |
46
+ | FR-05 approved Act | `spec/ruby_llm/browser/act_spec.rb`, `spec/ruby_llm/browser/engine_spec.rb` | same, `spec/security/release_audit_spec.rb` | PASS for offline simulation only; live Act deferred, same-session protocol NOT IMPLEMENTED | 2026-10-18 |
47
+ | FR-06 Rails jobs | `spec/rails/operation_job_spec.rb`, `spec/rails/reaper_spec.rb`, `spec/rails/active_record_operation_store_spec.rb`, `spec/rails/active_record_approval_store_spec.rb` | same (wrong actor, fenced-out worker, cancel-mid-session, cleanup_pending visible) | PASS (fake backend + in-memory SQLite) | 2026-10-17 |
48
+ | FR-07 failure/budget/audit | `spec/ruby_llm/browser/engine_spec.rb` (budget, redaction), `spec/ruby_llm/browser/stagehand_backend_spec.rb` | `spec/integration/stagehand_http_spec.rb` (stubbed HTTP; default live calls refused; cleanup ACK not closure), `spec/ruby_llm/browser/doctor_spec.rb` | Offline contract tests only; live Stagehand calls NOT RUN and unsupported | 2026-10-18 |
49
+ | FR-08 full delivery | Build/install evidence below, `spec/integration/documentation_spec.rb` | Core/Rails require, generator/migration and compatibility suites | Package checks PASS; H1/H2 approved for exact Q; owner later authorized the offline-only `0.1.0` release; release outcome is recorded separately | 2026-10-18 |
50
+
51
+ Run command for every row: `bundle exec rspec` (and `bundle exec rubocop`). Environment is
52
+ the pinned local table above.
53
+
54
+ ### FR-08 delivery evidence (2026-10-17)
55
+
56
+ ```text
57
+ $ gem build ruby_llm-browser.gemspec
58
+ Successfully built RubyGem
59
+ Name: ruby_llm-browser
60
+ Version: 0.1.0
61
+ File: ruby_llm-browser-0.1.0.gem
62
+
63
+ # Clean install into an isolated GEM_HOME, then require from the built gem:
64
+ $ gem install --local ./ruby_llm-browser-0.1.0.gem --install-dir "$PKG" --ignore-dependencies
65
+ $ GEM_HOME="$PKG" GEM_PATH="$PKG:$(gem env gempath)" ruby -e 'require "ruby_llm/browser"'
66
+ framework_loaded=[] # no railties / active_record / active_job on the core path
67
+ core=0.1.0
68
+ $ ... ruby -e 'require "ruby_llm/browser/rails"'
69
+ rails=RubyLLM::Browser::Rails::OperationJob
70
+ ```
71
+
72
+ The packaged gem contains the plain-Ruby core, the opt-in Rails adapter and the install
73
+ generator. At the time of this initial package check, RubyGems publication was blocked on
74
+ BR-03 (name/license clearance).
75
+
76
+ The original install above used `--ignore-dependencies` and borrowed the development
77
+ GEM_PATH. It proved loading the artifact, **not** clean dependency resolution. The later
78
+ audit's fresh-GEM_HOME installation is the stronger check.
79
+
80
+ ## Release-hardening audit (2026-10-18)
81
+
82
+ The previous 270-example green suite did not cover several release-critical negative
83
+ paths. The initial new audit regressions failed on output ceilings, malformed schemas,
84
+ missing result validation, stale starts and early quota release. Those are now fixed.
85
+ See the [release audit]({% link RELEASE_AUDIT.md %}) for findings, corrections and remaining gates.
86
+
87
+ Evidence commands (all provider HTTP is stubbed or disabled):
88
+
89
+ ```bash
90
+ bundle exec rake
91
+ BUNDLE_GEMFILE=gemfiles/ruby_llm_2_0.gemfile bundle exec rspec
92
+ BUNDLE_GEMFILE=gemfiles/rails_8_0.gemfile bundle exec rspec
93
+ bundle exec bundle-audit check --update
94
+ bundle exec ruby examples/plain_ruby.rb
95
+ bundle exec ruby examples/rails_adapter.rb
96
+ bundle exec rake build
97
+ ```
98
+
99
+ Additional fixtures exercise independent SQLite connections, duplicate start claims,
100
+ atomic grant consumption, actual SIGKILL of a fake-backend worker followed by recovery
101
+ in a fresh process, post-start persistence errors, real-clock timestamp precision,
102
+ Stagehand HTTP retries/serialization, the Rails generator's reversible migration, local
103
+ Markdown links, shipped files and executable offline README examples.
104
+
105
+ | Check | Versions / fixture | Result |
106
+ | --- | --- | --- |
107
+ | Full default suite | Ruby 3.3.5, RubyLLM 2.1.1, AR/AJ 8.1.4, Stagehand 3.21.0, SQLite 2.10.0 | PASS: 302 examples, 0 failures |
108
+ | Minimum RubyLLM | Same, RubyLLM 2.0.0 | PASS: 302 examples, 0 failures |
109
+ | Rails 8.0 | Ruby 3.3.5, RubyLLM 2.0.0, AR/AJ 8.0.5.1 | PASS: 302 examples, 0 failures |
110
+ | RuboCop | `bundle exec rubocop . bin/verify-package` | PASS: 89 files, 0 offenses |
111
+ | Runtime dependency advisories | ruby-advisory-db `fa2295251efb3b0d1a300e5ce8ad700ff1f1b485` | No known vulnerabilities found |
112
+ | Live provider egress / TTL / billing / final URL | No live session authorized or run | NOT RUN |
113
+ | PostgreSQL concurrency | No PostgreSQL fixture run | NOT RUN |
114
+ | Expanded remote CI | Linux Ruby 3.2/3.3/3.4, RubyLLM 2.0.0/2.1.1, Rails 8.1 plus Rails 8.0 row | PASS for `0ec34d4`: seven test jobs plus build/install/advisory job |
115
+ | Public release at initial hardening audit | H1/H2 for exact Q, BR-03 naming/license, exact artifact | HOLD at that time; H1/H2 passed, distribution clearance and artifact approval remained open |
116
+
117
+ ### Stronger package-install evidence
118
+
119
+ ```bash
120
+ bundle exec rake build
121
+ bundle exec bundle-audit check --update
122
+ bundle exec ruby bin/verify-package --rails --audit
123
+ ```
124
+
125
+ The verifier installs the actual package and resolves dependencies in a new GEM_HOME
126
+ with no borrowed GEM_PATH and no `--ignore-dependencies`. It runs outside the checkout,
127
+ checks the activated entrypoint, loads shipped guides/examples, proves core loading has
128
+ no eager framework/client, then installs optional Rails dependencies and runs the
129
+ installed generator's migration up/down. Output markers:
130
+
131
+ ```text
132
+ CORE_PACKAGE_PASS
133
+ CLEAN_RUNTIME_ADVISORIES_PASS
134
+ RAILS_PACKAGE_PASS
135
+ ```
136
+
137
+ The initial fresh install exposed vulnerable default CGI/Resolv/URI/zlib versions that
138
+ the project's lockfile scan omitted. After adding patched runtime floors, both the
139
+ lockfile scan and a check of the actually activated clean core runtime report no known
140
+ advisories. Local resolved security libraries: CGI 0.5.2, Resolv 0.8.1, URI 1.1.2 and
141
+ zlib 3.2.4. Native Ruby/system-library security is a separate host responsibility.
142
+
143
+ Full installer/smoke logs and `activated-versions.json` remain in the temporary evidence
144
+ directory printed by the verifier. The verifier prints the artifact SHA-256; a local
145
+ `pkg/SHA256SUMS` accompanies the final artifact. Build/install is not publication.
146
+
147
+ ### Submission evidence (2026-10-18)
148
+
149
+ The hardening is committed and pushed as `0ec34d4b48e0e3ab88bb893c4c731777d2aa24af`.
150
+ [CI run 39086570934](https://github.com/yorzi/ruby_llm-browser/actions/runs/39086570934)
151
+ completed successfully: all seven test jobs report `302 examples, 0 failures`, and
152
+ the package job reports no known dependency advisories plus all three clean-install
153
+ success markers. No RubyGems release or paid provider call was performed. Publication
154
+ remains NO-GO for the other recorded gates.
155
+
156
+ ### No-provider-dispatch cleanup follow-up (2026-10-18)
157
+
158
+ The owner selected an offline read-only first release, deferred the full live Act
159
+ protocol, and declined live provider calls. The Stagehand adapter refuses every live
160
+ operation. Its end_session method returns false without invoking the SDK; stubbed HTTP
161
+ tests assert that neither normal cleanup nor partial-start cleanup sends an end request.
162
+ This follow-up is local evidence; it is not covered by the CI run above.
163
+
164
+ | Check | Result |
165
+ | --- | --- |
166
+ | `RUBOCOP_CACHE_ROOT=/private/tmp/ruby_llm_browser_rubocop_cache bundle exec rake` (Ruby 3.3.5, RubyLLM 2.1.1, AR/AJ 8.1.4, Stagehand 3.21.0, SQLite 2.10.0) | PASS: 305 examples, 0 failures; RuboCop inspected 94 files, 0 offenses |
167
+ | `BUNDLE_GEMFILE=gemfiles/ruby_llm_2_0.gemfile bundle exec rspec` | PASS: 305 examples, 0 failures; RubyLLM 2.0.0 |
168
+ | `BUNDLE_GEMFILE=gemfiles/rails_8_0.gemfile bundle exec rspec` | PASS: 305 examples, 0 failures; RubyLLM 2.0.0 and Rails/Active Job 8.0.5.1 |
169
+ | `./docs/bin/build.sh` | PASS: Jekyll built the working tree |
170
+ | `bundle exec rake build` | PASS: built `ruby_llm-browser-0.1.0.gem` |
171
+ | `bundle exec ruby bin/verify-package --rails --audit` | PASS: `CORE_PACKAGE_PASS`, `CLEAN_RUNTIME_ADVISORIES_PASS`, `RAILS_PACKAGE_PASS`; final artifact SHA-256 is in ignored `pkg/SHA256SUMS` |
172
+ | Live provider egress, TTL, billing, and cleanup | NOT RUN; no live calls authorized |
173
+ | Current working-tree remote CI | NOT RUN; the passing CI above is for `0ec34d4` |
174
+ | RubyGems publication | NOT ATTEMPTED; H1/H2 passed for exact Q, but name/account/MFA, owner clearance and exact-artifact approval remain open |
175
+
176
+ The clean package verifier needed network access to resolve gems from RubyGems. It ran
177
+ from an isolated temporary GEM_HOME outside the checkout. Its network use was limited to
178
+ gem installation; all example execution used the fake backend. The sandbox-only first
179
+ attempt could not resolve `rubygems.org` and made no install; the permitted retry
180
+ completed successfully. The verifier's SHA-256 output is captured in the local, ignored
181
+ `pkg/SHA256SUMS` sidecar after the final build so this document does not contain a
182
+ self-referential artifact checksum.
183
+
184
+ ### Offline Act single-use race and docs (2026-10-18)
185
+
186
+ The first release supports Act only as an offline FakeBackend simulation. It consumes a
187
+ host-issued grant once; competing calls yield one recorded action and stale denials.
188
+ Stagehand remains unsupported, no provider cleanup request is sent, and the full live Act
189
+ protocol remains a later-version feature. The new feature guide explains grant issuance,
190
+ revocation, binding, concurrency, denial results, and this boundary.
191
+
192
+ | Check | Result |
193
+ | --- | --- |
194
+ | `RUBOCOP_CACHE_ROOT=/private/tmp/ruby_llm_browser_rubocop_cache bundle exec rake` | PASS: 306 examples, 0 failures; RuboCop inspected 94 files, 0 offenses |
195
+ | Offline Act guide example via `bundle exec ruby -Ilib -` | PASS: returned `ok` and the issued approval ID; FakeBackend only, no model or provider call |
196
+ | `python3 /Users/andy/.codex/skills/project-docs/scripts/docs.py check --dir docs` | PASS: 12 guides, 0 errors, 0 warnings |
197
+ | `./docs/bin/build.sh` | PASS: Jekyll built the working tree |
198
+ | `bundle exec rake build` | PASS: built `ruby_llm-browser-0.1.0.gem` |
199
+ | `bundle exec ruby bin/verify-package --rails --audit` | PASS: `CORE_PACKAGE_PASS`, `CLEAN_RUNTIME_ADVISORIES_PASS`, and `RAILS_PACKAGE_PASS`; isolated clean install |
200
+ | Live provider egress, TTL, billing, and terminal cleanup | NOT RUN; no live calls authorized |
201
+ | RubyGems publication | NOT ATTEMPTED at this audit snapshot, before the later owner instruction to release version `0.1.0` |
202
+
203
+ These local checks do not qualify browser-side egress, provider TTL, billing, or remote
204
+ cleanup. They validate a local unpublished candidate, not a release. Rebuild the exact
205
+ artifact after any release metadata changes and only after the H1/H2 gates.
206
+
207
+ ### H2 handoff and distribution inventory (2026-10-18)
208
+
209
+ The owner approved the offline read-only v1 scope and KPI proposal against exact Specs
210
+ candidate Q `22d488987517b10dcd96d2713ed3d47e8818b36b`. The approval is recorded in
211
+ OddlyNiche content commit C `678f9faed6dfdd8e55a7c54566f94cfa1ad1bc0a`. H1/H2 passed;
212
+ registration R and this repository's final baseline receipt remain pending. H2 does not
213
+ authorize RubyGems publication.
214
+
215
+ | Check | Result |
216
+ | --- | --- |
217
+ | Resolved runtime dependency license inventory | PASS: 26 dependencies' exact lockfile versions and installed gemspec license metadata match NOTICE; Stagehand's missing metadata is covered by its pinned upstream MIT text |
218
+ | RubyGems exact-name query | HTTP 404 on 2026-10-18; unregistered at check time, not reserved |
219
+ | Runtime dependency license inventory | PASS: 26 exact lockfile versions and installed gemspec licenses match NOTICE; Stagehand source MIT text is pinned |
220
+ | Project docs checker | PASS: 12 guide pages, 0 errors, 0 warnings |
221
+ | Jekyll build | PASS: `./docs/bin/build.sh` built the working tree |
222
+ | Local package review | PASS: version 0.1.0, 83 files, 7 runtime dependencies, MFA metadata present, no blocked paths |
223
+ | RubyGems MFA requirement | Gem metadata requires publisher MFA; enable MFA before the first push |
224
+ | RubyGems publication / live provider call | NOT ATTEMPTED; publication awaits exact-artifact approval and distribution clearance; no provider calls were made |
225
+
226
+ The license inventory is a point-in-time metadata check, not legal clearance. Recheck
227
+ name availability and the final built artifact immediately before any release.
228
+
229
+ ### 0.1.0 release decision and automation (2026-10-18)
230
+
231
+ After the initial publication hold above, the owner selected gem version `0.1.0`,
232
+ instructed the matching `v0.1.0` tag and RubyGems release, and requested automation for
233
+ future version tags. This instruction authorizes the offline-only package release; it is
234
+ separate from H2 handoff approval. The approved Specs Q keeps its historical candidate
235
+ label `0.3.0-draft`. Stagehand remains unsupported, live Act remains deferred, and no
236
+ provider calls were authorized or made.
237
+
238
+ The repository release workflow runs tests, the dependency advisory scan and isolated
239
+ package verification before RubyGems trusted publishing. It then creates a GitHub Release
240
+ with the generated gem, checksum and matching changelog section. The Docs workflow builds
241
+ the tag's stable documentation only after that release workflow succeeds. The first tag
242
+ is pushed only after the trusted publisher is registered for this repository and the
243
+ `release.yml` workflow under the `release` GitHub environment.
244
+
245
+ ## Commit log
246
+
247
+ Small, meaningful commits land one piece at a time. See `git log` for the authoritative
248
+ history; notable slices:
249
+
250
+ 1. Scaffold: gemspec, version, Ruby-only fixture, CI, OSS docs and the baseline record.
251
+ 2. Core: typed errors, redaction, budgets, results and configuration.
252
+ 3. Policy: capabilities, URL guard and source/schema/action policy.
253
+ 4. Lifecycle: operation, approval and in-memory stores.
254
+ 5. Runtime: backend contract, FakeBackend, Context, Engine and the Inspect/Extract/Act
255
+ tools, with FR-01/03/04/05 evidence (fake backend).
256
+ 6. Backend: the Stagehand adapter and preflight Doctor (FR-02/03/07 offline evidence).
257
+ 7. Engine seam: a caller-reserved operation for the Rails job and cleanup bookkeeping
258
+ that tolerates a fenced or cancelled writer.
259
+ 8. Rails adapter: durable ActiveRecord operation/approval stores, schema and migration.
260
+ 9. Rails jobs: scoped Active Job, cleanup reaper and the install generator (FR-06).
@@ -0,0 +1,74 @@
1
+ ---
2
+ layout: default
3
+ title: Qualifying a live backend
4
+ nav_order: 2
5
+ description: Review the host and provider guarantees required before a live browser session.
6
+ ---
7
+
8
+ # {{ page.title }}
9
+
10
+ {{ page.description }}
11
+ {: .fs-6 .fw-300 }
12
+
13
+ After reading this guide, you will know:
14
+
15
+ * Why `StagehandBackend` is unsupported in the first release.
16
+ * Which egress and cleanup controls need qualification before live use.
17
+ * Why the full live Act protocol is deferred to a later version.
18
+
19
+ ## Run a Local Diagnostic
20
+
21
+ `Doctor` checks local configuration without a network request or browser session. This
22
+ fake-backend example reports its exact boundary:
23
+
24
+ ```ruby
25
+ require "ruby_llm/browser"
26
+
27
+ backend = RubyLLM::Browser::FakeBackend.new
28
+ puts RubyLLM::Browser::Doctor.new(backend: backend).report[:summary]
29
+ # => "configuration checks passed; live provider guarantees are not verified"
30
+ ```
31
+
32
+ The diagnostic is local and makes no provider request. In this release it reports the
33
+ built-in Stagehand backend as unsupported: `egress_isolation` remains `:unverified`, and
34
+ the Engine refuses to start a session. The host cannot override this status with a
35
+ constructor option. A custom backend must enforce its own browser-side network boundary.
36
+
37
+ Browserbase's documented [Allowed Domains feature](https://docs.browserbase.com/platform/browser/security/allowed-domains)
38
+ applies to top-frame navigations. It does not block iframe or subresource requests such
39
+ as scripts, images, and XHR. That setting alone cannot qualify provider egress. Any future
40
+ deployment must separately verify DNS at connection time, redirects, frames, popups,
41
+ WebSockets, scripts, and all other request paths.
42
+
43
+ ## Verify Provider Behavior Separately
44
+
45
+ Before considering live use, independently establish provider-side egress restrictions,
46
+ final URL reporting, session TTL, unknown-start recovery, billing controls, and confirmed
47
+ cleanup. The gem does not provide a browser firewall or a hard cost cap. The Doctor does
48
+ not call the provider and cannot establish these properties.
49
+
50
+ Cleanup needs a status check. Stagehand 3.21's `SessionEndResponse.success` reports that
51
+ the end request succeeded; it does not report the session's final status. Browserbase's
52
+ [`REQUEST_RELEASE` endpoint](https://docs.browserbase.com/reference/api/update-a-session)
53
+ requests completion and may return a session still marked `PENDING`. The
54
+ [`Get a Session` endpoint](https://docs.browserbase.com/reference/api/get-a-session)
55
+ exposes status and `endedAt`, but the Stagehand Ruby SDK does not expose that lookup.
56
+ The adapter cannot start a live session in this release. Its `end_session` method returns
57
+ `false` without calling the SDK. An operation whose cleanup is not confirmed stays
58
+ `cleanup_pending`; the Gem does not claim terminal cleanup.
59
+
60
+ ## Current Project Status
61
+
62
+ The repository's [release audit]({% link RELEASE_AUDIT.md %}) records no live provider
63
+ qualification and marks the built-in Stagehand backend unsupported. The first release
64
+ uses read-only tools; the full live Act protocol is deferred to a later version. The
65
+ Engine refuses non-fake Act calls until same-session host approval and target binding are
66
+ implemented.
67
+
68
+ Read-only tools do not dispatch browser actions. A page can still run scripts and initiate
69
+ requests, and returned page text remains untrusted data. Review the [security model]({% link SECURITY_MODEL.md %}) before designing a host deployment.
70
+
71
+ ## Next Steps
72
+
73
+ * [Controlling source access]({% link _core_features/source-policy.md %}) - set host URL policy.
74
+ * [Running work in Rails]({% link _advanced/rails-integration.md %}) - protect durable jobs and recovery.
@@ -0,0 +1,56 @@
1
+ ---
2
+ layout: default
3
+ title: Running work in Rails
4
+ nav_order: 1
5
+ description: Run durable inspect and extract operations with the Rails 8 adapter.
6
+ ---
7
+
8
+ # {{ page.title }}
9
+
10
+ {{ page.description }}
11
+ {: .fs-6 .fw-300 }
12
+
13
+ After reading this guide, you will know:
14
+
15
+ * Which framework versions and components the adapter expects.
16
+ * How to install its tables.
17
+ * What the host must own in background jobs and cleanup.
18
+
19
+ ## Install the Adapter
20
+
21
+ The adapter is optional. Add Rails 8 Active Record and Active Job to the host application,
22
+ then require the Rails entry point and install its migration:
23
+
24
+ ```ruby
25
+ require "ruby_llm/browser/rails"
26
+ ```
27
+
28
+ ```sh
29
+ bin/rails generate ruby_llm:browser:install
30
+ bin/rails db:migrate
31
+ ```
32
+
33
+ The generator creates durable operation and approval tables plus per-actor reservation
34
+ locks. The jobs accept `inspect` and `extract`; Act is not available in background jobs.
35
+
36
+ ## Keep Authorization in the Host
37
+
38
+ The context factory runs in the worker and must reload trusted records, recheck actor and
39
+ run authorization, and use the same operation store and budget limits as the reservation.
40
+ Never put provider credentials or authorization decisions in model arguments or job
41
+ payloads. Protect queue storage because URLs, instructions, and schemas are persisted.
42
+
43
+ ## Schedule Recovery
44
+
45
+ Run `RubyLLM::Browser::Rails::Reaper` on a separate schedule. Worker cleanup alone cannot
46
+ recover sessions left by a killed process. Do not free quota for an unknown or pending
47
+ session based only on elapsed time. The job does not durably store result bodies, so the
48
+ host must implement authorized result storage and delivery if it needs them.
49
+
50
+ SQLite concurrency is exercised locally. PostgreSQL still needs separate qualification,
51
+ and MySQL is not supported by the adapter's locking contract. See the [full Rails guide]({% link RAILS.md %}) for a context factory, enqueueing, status access, migration recovery, and reaper details.
52
+
53
+ ## Next Steps
54
+
55
+ * [Bounding operation work]({% link _core_features/operation-lifecycle.md %}) - understand budgets and state transitions.
56
+ * [Qualifying a live backend]({% link _advanced/live-backend-qualification.md %}) - review provider requirements.