openui-rails 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.
- checksums.yaml +7 -0
- data/AUDIT.md +102 -0
- data/CHANGELOG.md +71 -0
- data/CODE_OF_CONDUCT.md +37 -0
- data/CONFIGURATION.md +79 -0
- data/CONTRIBUTING.md +66 -0
- data/LICENSE +22 -0
- data/README.md +471 -0
- data/RELEASING.md +90 -0
- data/SECURITY.md +48 -0
- data/THIRD_PARTY_NOTICES.md +45 -0
- data/UPGRADING.md +26 -0
- data/app/controllers/openui/rails/actions_controller.rb +77 -0
- data/app/controllers/openui/rails/application_controller.rb +10 -0
- data/app/javascript/openui/rails/openui_controller.js +347 -0
- data/app/javascript/openui/rails/openui_envelope_controller.js +38 -0
- data/config/routes.rb +5 -0
- data/lib/generators/openui/install/install_generator.rb +62 -0
- data/lib/generators/openui/install/templates/config/initializers/openui.rb.tt +30 -0
- data/lib/generators/openui/install/templates/config/openui/README.md +24 -0
- data/lib/openui/rails/actions/dispatcher.rb +194 -0
- data/lib/openui/rails/actions/idempotency/memory_store.rb +64 -0
- data/lib/openui/rails/actions/invocation.rb +16 -0
- data/lib/openui/rails/actions/registry.rb +113 -0
- data/lib/openui/rails/actions/schema.rb +115 -0
- data/lib/openui/rails/actions/submission.rb +102 -0
- data/lib/openui/rails/actions.rb +18 -0
- data/lib/openui/rails/adapters/ruby_llm.rb +113 -0
- data/lib/openui/rails/configuration.rb +282 -0
- data/lib/openui/rails/engine.rb +46 -0
- data/lib/openui/rails/errors.rb +41 -0
- data/lib/openui/rails/json_value.rb +33 -0
- data/lib/openui/rails/library_manifest.rb +234 -0
- data/lib/openui/rails/message.rb +139 -0
- data/lib/openui/rails/prompt_builder.rb +115 -0
- data/lib/openui/rails/runtime_asset.rb +65 -0
- data/lib/openui/rails/runtime_package.rb +74 -0
- data/lib/openui/rails/stream/accumulator.rb +86 -0
- data/lib/openui/rails/stream/bridge.rb +193 -0
- data/lib/openui/rails/stream/broadcaster.rb +106 -0
- data/lib/openui/rails/stream/envelope.rb +159 -0
- data/lib/openui/rails/stream/transports/action_cable.rb +50 -0
- data/lib/openui/rails/stream/transports/memory.rb +33 -0
- data/lib/openui/rails/stream/transports/turbo_streams.rb +61 -0
- data/lib/openui/rails/stream/transports.rb +41 -0
- data/lib/openui/rails/surface.rb +150 -0
- data/lib/openui/rails/version.rb +5 -0
- data/lib/openui/rails/view_helper.rb +56 -0
- data/lib/openui/rails.rb +33 -0
- data/lib/tasks/openui.rake +16 -0
- metadata +125 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: f5365a84ab993b7d1d7efd392753a9699f28cbe17acd8482d517e0e529150a6c
|
|
4
|
+
data.tar.gz: d071e4fbd4f29fefd7a074f411b30cdd0b453bc5532410bbcb7af3333184f340
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: cd3efcb1e979ac3c63892e83a68fcd1b62db7d44297763e2fdc451251220c823efa2f27afc25d340902e5f56e0c8b7c53afd952df115eda9e78c95a143b8bb0f
|
|
7
|
+
data.tar.gz: e8a972cb1ef2663d0247263984d06e82e24cfcf1e4e972788c3dc917a111c5ea88e8cd782191f7baf19d2804660ed93bd6c8241a58074010f112b22686ce6a55
|
data/AUDIT.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Pre-publication audit: 2026-10-18
|
|
2
|
+
|
|
3
|
+
## Outcome
|
|
4
|
+
|
|
5
|
+
The audit found real defects despite the original green test suite. The fixes
|
|
6
|
+
below have regression coverage. The owner authorized publication of `0.1.0`
|
|
7
|
+
on 2026-10-18. Publication still requires the selected tag's complete CI to
|
|
8
|
+
pass and the protected publishing workflow to succeed. This audit establishes
|
|
9
|
+
the specific behaviors below, not the absence of all bugs.
|
|
10
|
+
|
|
11
|
+
## Confirmed defects corrected
|
|
12
|
+
|
|
13
|
+
| Area | Defect | Correction and evidence |
|
|
14
|
+
| --- | --- | --- |
|
|
15
|
+
| Identity isolation | A client idempotency key could replay another actor's result or a different action's result. | Actor/tenant-scoped keys; request digest binds action, surface, revision, args, and confirmation. Cross-actor/action/surface/tenant regression tests. |
|
|
16
|
+
| Canonical input | Symbol-keyed and nested arguments had incorrect digests; mutable input could change after validation. | Deep canonical JSON copies and immutable data. Mutation and nested-digest tests. |
|
|
17
|
+
| Claim state | A completed `nil` looked pending; an expired writer could overwrite a newer claim. | Explicit pending/replay states and per-claim tokens. Nil/false, expiry, and concurrent-claim tests. |
|
|
18
|
+
| Host authentication | Engine controllers bypassed the host ApplicationController; documented default actor resolution was absent. | Host controller inheritance, explicit CSRF protection, current-user fallback, and tenant-swap tests. |
|
|
19
|
+
| Action responses | Authentication redirects, malformed JSON, or unrelated result objects could appear successful. | Reject redirects and non-JSON/malformed/mismatched responses; browser regression tests. |
|
|
20
|
+
| Stream ordering | Completion skipped missing deltas; stale snapshots rewound state; a gap did not pause later deltas. | Sequence gates for terminal frames and snapshots; Ruby/browser regressions. |
|
|
21
|
+
| Terminal writers | Duplicate finishes and writes after errors emitted more frames. | One terminal state. Consumer/transport failures propagate instead of being relabelled provider failures. |
|
|
22
|
+
| Browser lifecycle | Pre-load frames could render without a runtime; reconnect/cache restoration lost source or sequence state. | Deferred rendering, connection tokens, state preservation, real Turbo visit/back tests. |
|
|
23
|
+
| OpenUI actions | The stub used `{action,args}`, but the real runtime emits `{type,params}`. | Official event normalization, verified with a real component and keyboard activation. |
|
|
24
|
+
| Browser tools | The proxy looked like an MCP client and returned the endpoint wrapper instead of the handler value. | Reserved-property handling, stable provider identity, raw tool values. Real Query and Mutation tests. |
|
|
25
|
+
| Renderer errors | The official renderer calls `onError([])` after successful parsing. | Empty error arrays are ignored; actual error arrays become structured renderer errors. |
|
|
26
|
+
| Library identity | A fingerprint existed, but the runtime library was not checked. | Server rejects persisted mismatches; browser hashes `toSpec()` before mounting. Real-bundle and negative tests. |
|
|
27
|
+
| Runtime supply | Fetch chose the largest JS file, ran without checksum verification, and CSS was not integrated. | Exact file allowlist, committed archive/file hashes, npm scripts disabled, no archive path extraction, stylesheet helper. Real fetch and SRI-backed browser tests. |
|
|
28
|
+
| RubyLLM | `Chat#complete(question)` does not exist in RubyLLM 2.1.1. | Add the user message, then call block-only complete; verify both strategies through real provider SSE parsing with mocked HTTP. |
|
|
29
|
+
| Message framing | CRLF, malformed markers, post-end text, and multi-line raw context differed from lang-core. | Align framing with pinned lang-core 1.0.1 and extend regression cases. No Ruby DSL parser added. |
|
|
30
|
+
| Packaging | Standalone require needed Rails preloaded; previous smoke checks only installed the gem. | Explicit framework loading; a fresh host uses the extracted built artifact, generator, eager load, and an HTTP render. |
|
|
31
|
+
| Configuration readiness | A configured filename could pass validation without an actual valid manifest or prompt file. | Explicit readiness validation reads the files and clears stale derived state. Missing-file regression tests. |
|
|
32
|
+
| Release workflow | A tag could publish without running tests; the supposed draft was public; environment protection was only a comment. | Reusable full CI gate, explicit manual publish, artifact handoff, real draft flag, and documented reviewer setup. |
|
|
33
|
+
|
|
34
|
+
## Verification evidence
|
|
35
|
+
|
|
36
|
+
Local environment: Ruby 3.3.5, Rails 8.1.4, Chrome through Selenium.
|
|
37
|
+
|
|
38
|
+
- Unit/integration: `209 runs, 542 assertions`, no failures or errors.
|
|
39
|
+
- Deterministic browser suite: `32 runs, 104 assertions`, no failures or errors.
|
|
40
|
+
- Real `@openuidev/browser-bundle` 0.2.1: `5 runs, 30 assertions`, no failures or errors.
|
|
41
|
+
Includes matching runtime/spec identity, rendering, keyboard action, Query,
|
|
42
|
+
click-triggered Mutation, and unknown-component rejection.
|
|
43
|
+
- Real Turbo Streams broadcast over Action Cable, plus Turbo visit/back restoration.
|
|
44
|
+
- RubyLLM 2.1.1 with real OpenAI chat-completions SSE parsing and mocked HTTP:
|
|
45
|
+
`1 run, 7 assertions`, no failures or errors. The deterministic test made no
|
|
46
|
+
paid provider calls.
|
|
47
|
+
- Live-provider smoke (2026-10-18): one synthetic prompt streamed through the
|
|
48
|
+
gem adapter with RubyLLM 2.1.1, OpenRouter, `openai/gpt-4.1-mini`, and the
|
|
49
|
+
`:complete` strategy. A 16-token output cap was set. The first sandboxed
|
|
50
|
+
attempt returned `stream_failed` with no delta; the network-enabled retry
|
|
51
|
+
emitted one 2-byte delta and a completed frame. The account's usage ledger
|
|
52
|
+
and exact charge were not checked. Evidence is limited to this OpenRouter
|
|
53
|
+
path/model; direct OpenAI and other provider/model paths remain unverified.
|
|
54
|
+
- Checksum-locked npm runtime fetch, including CSS and upstream license: passed.
|
|
55
|
+
- Built artifact in a fresh generated Rails host: generator, eager load, and HTTP render passed.
|
|
56
|
+
- RuboCop: clean. JS syntax checks: passed.
|
|
57
|
+
- GitHub workflows: actionlint 1.7.12 passed. External actions are pinned to
|
|
58
|
+
verified full commit SHAs, not mutable major-version tags.
|
|
59
|
+
- `bundler-audit check --update`: no known Ruby vulnerabilities at advisory DB
|
|
60
|
+
commit `fa2295251efb3b0d1a300e5ce8ad700ff1f1b485` (updated 2026-10-17).
|
|
61
|
+
- CI additionally checks Ruby 3.2/3.3/3.4 with Rails 8.0/8.1. Consult the
|
|
62
|
+
linked repository workflow for the result of the latest audit commit.
|
|
63
|
+
|
|
64
|
+
Tests establish these specific behaviors, not universal provider compatibility,
|
|
65
|
+
complete accessibility, legal clearance, or an absence of future vulnerabilities.
|
|
66
|
+
|
|
67
|
+
## Release setup on 2026-10-18
|
|
68
|
+
|
|
69
|
+
- The owner explicitly authorized publication, public repository visibility,
|
|
70
|
+
and RubyGems OIDC trust for `yorzi/openui-rails`, `release.yml`, environment
|
|
71
|
+
`release`.
|
|
72
|
+
- The repository is public. Security reports go to the address in `SECURITY.md`;
|
|
73
|
+
public support uses GitHub Issues.
|
|
74
|
+
- RubyGems has a pending Trusted Publisher for the exact workflow and
|
|
75
|
+
environment. The first successful publication activates the gem's publisher.
|
|
76
|
+
- The GitHub `release` environment requires approval by `yorzi`, allows the
|
|
77
|
+
`main` branch only, and disallows administrator bypass. Self-review remains
|
|
78
|
+
available for the single maintainer.
|
|
79
|
+
- Dated `0.1.0` notes are in `CHANGELOG.md`. Gem and documentation versions
|
|
80
|
+
are checked together before packaging.
|
|
81
|
+
- The built gem excludes the upstream browser bundle. A host that redistributes
|
|
82
|
+
the fetched bundle must review its transitive notices, including ISC-licensed
|
|
83
|
+
`lucide-react`. The gem's fetch task retains upstream legal comments and the
|
|
84
|
+
upstream license file. This is not a legal-clearance claim.
|
|
85
|
+
|
|
86
|
+
## Boundaries hosts must enforce
|
|
87
|
+
|
|
88
|
+
- Shared atomic idempotency storage for multiple processes. TTL expiry, crashes,
|
|
89
|
+
and irreversible effects still need database constraints or transactions.
|
|
90
|
+
- Trusted confirmation UI and, where needed, signed server approvals. A client
|
|
91
|
+
boolean is not proof of human intent.
|
|
92
|
+
- Authorized stream subscriptions, scoped surface lookup, and authoritative
|
|
93
|
+
snapshot persistence/recovery. The gem supplies no snapshot server.
|
|
94
|
+
- Request/body/rate limits and provider token/time budgets.
|
|
95
|
+
- A compatible prompt/spec/runtime library. The default bundle contains only
|
|
96
|
+
the chat library; custom libraries require a matching runtime.
|
|
97
|
+
- CSP validation for the upstream renderer's inline styles. No strict-style-CSP
|
|
98
|
+
support is claimed. No human screen-reader certification is claimed.
|
|
99
|
+
- API-only Rails authentication/CSRF integration is not covered by this release.
|
|
100
|
+
|
|
101
|
+
The owner's publication authorization is not an independent legal, security,
|
|
102
|
+
or accessibility certification. Follow [RELEASING.md](RELEASING.md) for each release.
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `openui-rails` are documented here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project
|
|
5
|
+
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.1.0] - 2026-10-18
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Scope idempotency to server actor/tenant identity and bind replay digests to
|
|
14
|
+
the full request. Distinguish pending claims from completed nil results and
|
|
15
|
+
fence expired writers with claim tokens.
|
|
16
|
+
- Preserve host authentication filters and default current-user resolution.
|
|
17
|
+
- Reject authentication redirects and malformed or mismatched action responses
|
|
18
|
+
instead of reporting a false success.
|
|
19
|
+
- Canonicalize and copy JSON input before validation/fingerprinting; prevent
|
|
20
|
+
mutable submissions, envelopes, surfaces, and manifests.
|
|
21
|
+
- Make explicit configuration readiness validation read the actual manifest
|
|
22
|
+
and prompt files instead of accepting filenames alone.
|
|
23
|
+
- Enforce stream terminal states, completion ordering, snapshot watermarks,
|
|
24
|
+
browser byte limits, and Turbo cache restoration.
|
|
25
|
+
- Use the real OpenUI `{type, params}` action contract, avoid false MCP-client
|
|
26
|
+
detection, return raw tool values, and ignore empty renderer-error arrays.
|
|
27
|
+
- Verify configured library fingerprints against the actual runtime `toSpec()`.
|
|
28
|
+
- Fetch exact checksum-locked JS/CSS/license files without npm lifecycle scripts.
|
|
29
|
+
Add `openui_stylesheet_tag` and default integrity pins.
|
|
30
|
+
- Correct RubyLLM's block-only `complete` call and verify 2.1.1 with real SSE parsing.
|
|
31
|
+
- Align message framing edge cases with lang-core 1.0.1.
|
|
32
|
+
- Require full CI and explicit manual approval before publication; create a
|
|
33
|
+
genuine draft GitHub release. See `UPGRADING.md` for pre-release changes.
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
|
|
37
|
+
- Mountable Rails engine under the `OpenUI::Rails` namespace with the
|
|
38
|
+
`openui/rails` require path.
|
|
39
|
+
- Configuration object with validated transport, runtime-asset strategy,
|
|
40
|
+
library/prompt paths, stream limits and action hooks.
|
|
41
|
+
- Versioned component-library manifest with a canonical SHA-256 fingerprint.
|
|
42
|
+
- Prompt builder tying the model system instruction to the exact library
|
|
43
|
+
revision and stamping a combined prompt fingerprint.
|
|
44
|
+
- Ruby implementation of the upstream OpenUI message protocol
|
|
45
|
+
(`build`/`parse`/`strip`).
|
|
46
|
+
- Ordered stream envelopes (`openui.delta/snapshot/completed/error`), a
|
|
47
|
+
sequence accumulator and a coalescing stream bridge/writer.
|
|
48
|
+
- Server-authoritative action loop: allowlist registry, arg schema,
|
|
49
|
+
confirmation, stale-surface check, required authorizer and idempotency.
|
|
50
|
+
- `openui_surface` view helper and the `OpenUI::Rails::ActionsController`
|
|
51
|
+
endpoint.
|
|
52
|
+
- Optional RubyLLM adapter (no hard dependency).
|
|
53
|
+
- Stimulus mount controller and pinned runtime-asset resolution.
|
|
54
|
+
- `openui:install` generator and the `openui:runtime:fetch` task.
|
|
55
|
+
- Transport broadcasters (`ActionCable`, `TurboStreams`, `Memory`) behind
|
|
56
|
+
`Stream::Broadcaster`, and the `openui-envelope` Stimulus controller that
|
|
57
|
+
turns a Turbo Stream append into an `openui:envelope` event without inline
|
|
58
|
+
script.
|
|
59
|
+
- Browser (system) test suite: lifecycle, stream ordering/duplicate/gap,
|
|
60
|
+
snapshot recovery, completion/error, action/tool round-trips, destructive
|
|
61
|
+
confirmation, and mount accessibility (`aria-busy` lifecycle, named region).
|
|
62
|
+
- `aria-busy` on the mount element tracks the streaming state, and an optional
|
|
63
|
+
`label:` exposes the mount as a named `region` landmark.
|
|
64
|
+
- Tag-driven release workflow and `RELEASING.md`.
|
|
65
|
+
- Versioned guides and an RDoc API reference at
|
|
66
|
+
`https://openui-rails.wangyaodi.com`, deployed with GitHub Pages.
|
|
67
|
+
- Gem-scoped RubyGems Trusted Publishing through GitHub OIDC, with a protected
|
|
68
|
+
release environment and verified artifact handoff.
|
|
69
|
+
|
|
70
|
+
[Unreleased]: https://github.com/yorzi/openui-rails/compare/v0.1.0...main
|
|
71
|
+
[0.1.0]: https://github.com/yorzi/openui-rails/releases/tag/v0.1.0
|
data/CODE_OF_CONDUCT.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
|
2
|
+
|
|
3
|
+
## Our Pledge
|
|
4
|
+
|
|
5
|
+
We as members, contributors, and leaders pledge to make participation in our
|
|
6
|
+
community a harassment-free experience for everyone, regardless of age, body
|
|
7
|
+
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
|
8
|
+
identity and expression, level of experience, education, socio-economic
|
|
9
|
+
status, nationality, personal appearance, race, religion, or sexual identity
|
|
10
|
+
and orientation.
|
|
11
|
+
|
|
12
|
+
We pledge to act and interact in ways that contribute to an open, welcoming,
|
|
13
|
+
diverse, inclusive, and healthy community.
|
|
14
|
+
|
|
15
|
+
## Our Standards
|
|
16
|
+
|
|
17
|
+
Examples of behavior that contributes to a positive environment include
|
|
18
|
+
showing empathy and kindness, respecting differing opinions, giving and
|
|
19
|
+
gracefully accepting constructive feedback, and focusing on what is best for
|
|
20
|
+
the community.
|
|
21
|
+
|
|
22
|
+
Examples of unacceptable behavior include sexualized language or imagery,
|
|
23
|
+
trolling or insulting comments, public or private harassment, and publishing
|
|
24
|
+
others' private information without permission.
|
|
25
|
+
|
|
26
|
+
## Enforcement
|
|
27
|
+
|
|
28
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
29
|
+
reported to the maintainers at **wangyaodi@gmail.com**. All complaints will be
|
|
30
|
+
reviewed and investigated promptly and fairly.
|
|
31
|
+
|
|
32
|
+
## Attribution
|
|
33
|
+
|
|
34
|
+
This Code of Conduct is adapted from the
|
|
35
|
+
[Contributor Covenant](https://www.contributor-covenant.org), version 2.1,
|
|
36
|
+
available at
|
|
37
|
+
https://www.contributor-covenant.org/version/2/1/code_of_conduct/.
|
data/CONFIGURATION.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
Configure the bridge in `config/initializers/openui.rb`. Rails owns identity,
|
|
4
|
+
authorization, stream subscriptions, persistence, and snapshot recovery.
|
|
5
|
+
|
|
6
|
+
| Setting | Default | Contract |
|
|
7
|
+
| --- | --- | --- |
|
|
8
|
+
| `transport` | `:turbo` | Factory preference: `:turbo`, `:action_cable`, or `:none`. Pass it explicitly to `Transports.build`; it does not create a subscription. |
|
|
9
|
+
| `library_spec_path` | `nil` | JSON manifest from the exact runtime library. Enables server and browser fingerprint checks. |
|
|
10
|
+
| `prompt_path` | `nil` | Prompt generated from that same library. |
|
|
11
|
+
| `runtime_assets` | `:self_hosted` | Use `:external` only with explicit exact-version URLs. |
|
|
12
|
+
| `local_runtime_path` | `"/openui"` | Public URL directory for self-hosted assets. |
|
|
13
|
+
| `runtime_version` | pinned `0.2.1` | The fetch task accepts only the checksum-locked version. Other versions are unverified. |
|
|
14
|
+
| `runtime_url` | derived self-hosted JS URL | Explicit override for a host-built or external runtime. |
|
|
15
|
+
| `runtime_integrity` | pinned hash for the default runtime | Override for another JS asset. No default hash is assumed for custom URLs. |
|
|
16
|
+
| `runtime_stylesheet_url` | derived self-hosted CSS URL | Required when using `:external` and `openui_stylesheet_tag`. |
|
|
17
|
+
| `runtime_stylesheet_integrity` | pinned hash for the default runtime | Override for another CSS asset. |
|
|
18
|
+
| `actions_endpoint` | `"/openui-rails/actions"` | Must match the host engine mount. |
|
|
19
|
+
| `stream_byte_limit` | `1_048_576` | Positive integer; enforced by server writer and browser. |
|
|
20
|
+
| `pending_byte_limit` | `32_768` | Positive byte-based flush threshold, at most the stream limit. Lower it for smaller, more frequent updates. There is no timer-based flush. |
|
|
21
|
+
| `idempotency_ttl` | `900` seconds | Finite positive number. Replays are protected only within the store's lifetime and TTL. |
|
|
22
|
+
| `idempotency_store` | process-local `MemoryStore` | Use a shared atomic store for multiple processes. See the contract below. |
|
|
23
|
+
| `idempotency_scope` | derived actor/tenant identity | Optional callable receiving `Invocation`; return a stable, JSON-serializable namespace for custom identity types. |
|
|
24
|
+
| `actor_resolver` | host `current_user`, if available | Callable receiving the engine controller. Host authentication filters are inherited. |
|
|
25
|
+
| `surface_resolver` | `nil` | Callable `(surface_id, actor:, context:)`; return the scoped surface, or `nil` to deny. Without it, the authorizer owns resource/version checks. |
|
|
26
|
+
| `authorize_action` | `nil` | Global authorizer receiving `Invocation`. Missing authorization denies the action. Per-action `authorize:` takes precedence. |
|
|
27
|
+
| `on_error` | `nil` | Server stream-error hook `(error, message_id:)`. Do not log credentials or unredacted provider requests. |
|
|
28
|
+
| `action_registry` | lazy `Actions::Registry` | Allowlisted action definitions. |
|
|
29
|
+
|
|
30
|
+
`library_name` and `surface_id_param` are legacy metadata settings. They do not
|
|
31
|
+
select a runtime library or change the endpoint's `surface_id` wire key.
|
|
32
|
+
|
|
33
|
+
After you create the library files, call
|
|
34
|
+
`OpenUI::Rails.configuration.validate!` to check required configuration and
|
|
35
|
+
read the actual manifest and configured prompt files.
|
|
36
|
+
Boot does not call it automatically, so setup tasks can run before files exist.
|
|
37
|
+
|
|
38
|
+
## Shared idempotency store
|
|
39
|
+
|
|
40
|
+
The dispatcher supplies a SHA-256 key scoped to server actor and tenant identity.
|
|
41
|
+
It supplies a digest of the entire validated request separately.
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
claim(key, ttl:, digest:)
|
|
45
|
+
# => [:claimed, nil, digest, claim_token] # this request owns execution
|
|
46
|
+
# => [:pending, nil, stored_digest, token] # execution is in progress
|
|
47
|
+
# => [:replay, result, stored_digest, token] # completed, including nil/false
|
|
48
|
+
|
|
49
|
+
record(key, result, claim_token: token)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Implement `claim` atomically. Make `claim_token` unique for every new claim.
|
|
53
|
+
Ignore a `record` call whose token no longer owns the key. Preserve completed
|
|
54
|
+
`nil` and `false` results instead of treating them as pending.
|
|
55
|
+
|
|
56
|
+
The default identity is `[class, id]` for records and `[class, value]` for scalar
|
|
57
|
+
identities. Unsupported identity objects need `idempotency_scope`. Scope must
|
|
58
|
+
come from trusted server state, not a client-supplied user or tenant ID.
|
|
59
|
+
|
|
60
|
+
A failed handler leaves a pending claim until expiry because its side effects
|
|
61
|
+
may already have happened. A handler that outlives the TTL can overlap a later
|
|
62
|
+
claim. Choose a suitable TTL and enforce business-level uniqueness or
|
|
63
|
+
transactions for irreversible operations. Process-local memory does not survive
|
|
64
|
+
restarts and cannot coordinate separate workers.
|
|
65
|
+
|
|
66
|
+
## Browser errors and recovery
|
|
67
|
+
|
|
68
|
+
Listen for `openui:error` to show a host-owned error or fallback. Listen for
|
|
69
|
+
`openui:snapshot-required` to fetch an authorized, authoritative snapshot.
|
|
70
|
+
The bridge emits these events; it does not implement the host's fallback UI,
|
|
71
|
+
snapshot endpoint, reconnection policy, or persistence.
|
|
72
|
+
|
|
73
|
+
The action bridge rejects authentication redirects and non-JSON, malformed, or
|
|
74
|
+
mismatched results. An expired-session redirect emits `unauthorized`; a broken
|
|
75
|
+
response contract emits `invalid_response`. Host callbacks may still redirect
|
|
76
|
+
HTTP requests, but a redirect is never a successful JSON action result.
|
|
77
|
+
|
|
78
|
+
Configure a matching manifest in production. Without one, the bridge cannot
|
|
79
|
+
verify that the model prompt, stored output, and runtime library agree.
|
data/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Contributing to openui-rails
|
|
2
|
+
|
|
3
|
+
Thanks for helping. This is an unofficial Rails integration for Thesys OpenUI
|
|
4
|
+
Lang; it does not reimplement the OpenUI parser or renderer.
|
|
5
|
+
|
|
6
|
+
## Getting set up
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
git clone https://github.com/yorzi/openui-rails
|
|
10
|
+
cd openui-rails
|
|
11
|
+
bundle install
|
|
12
|
+
bundle exec bin/rails db:test:prepare
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Before you open a pull request
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
bundle exec bin/rails test # unit + integration tests
|
|
19
|
+
bundle exec bin/rails test:system # browser (system) tests, needs Chrome
|
|
20
|
+
bundle exec rubocop # style (rubocop-rails-omakase)
|
|
21
|
+
bundle exec gem build openui-rails.gemspec
|
|
22
|
+
bundle exec ruby bin/smoke-package openui-rails-0.1.0.gem
|
|
23
|
+
bundle exec bundler-audit check --update
|
|
24
|
+
bundle exec ruby -Itest test/compatibility/ruby_llm_smoke.rb
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The browser tests run against `test/dummy` with headless Chrome and a
|
|
28
|
+
deterministic stub of the OpenUI runtime
|
|
29
|
+
(`test/dummy/public/openui/openui-browser-bundle-0.2.1.js`), so they need no
|
|
30
|
+
network or real bundle.
|
|
31
|
+
|
|
32
|
+
CI also runs the real checksum-locked browser bundle. To run that probe locally:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
bundle exec rake "app:openui:runtime:fetch[$PWD/tmp/real-runtime]"
|
|
36
|
+
OPENUI_REAL_RUNTIME_DIR="$PWD/tmp/real-runtime" \
|
|
37
|
+
bundle exec ruby -Itest test/compatibility/runtime_smoke.rb
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The first command needs npm and network access. The browser probe uses local
|
|
41
|
+
assets afterward. The RubyLLM probe uses the real 2.1.1 client and SSE parser,
|
|
42
|
+
with WebMock and a dummy API key. It never calls a paid provider.
|
|
43
|
+
|
|
44
|
+
Keep the test suite green and add negative/security tests alongside any new
|
|
45
|
+
behavior. Changes to the action path or the stream protocol are expected to
|
|
46
|
+
include unauthorized, wrong-tenant, replay, stale-version and malformed-payload
|
|
47
|
+
cases.
|
|
48
|
+
|
|
49
|
+
## Guidelines
|
|
50
|
+
|
|
51
|
+
- Prefer the official OpenUI runtime over any hand-rolled parser. Model output
|
|
52
|
+
is untrusted UI DSL, never executable code.
|
|
53
|
+
- Keep `ruby_llm` optional and isolated under `OpenUI::Rails::Adapters`; do not
|
|
54
|
+
reference a `RubyLLM` constant outside a guarded adapter.
|
|
55
|
+
- Inside `module OpenUI`, write `::Rails` for the framework.
|
|
56
|
+
- Do not introduce floating dependency ranges for the OpenUI JS runtime or the
|
|
57
|
+
prompt CLI; pin exact versions.
|
|
58
|
+
- Write commit messages that explain the *why*.
|
|
59
|
+
|
|
60
|
+
## Reporting security issues
|
|
61
|
+
|
|
62
|
+
Please do not open a public issue for a security problem. See `SECURITY.md`.
|
|
63
|
+
|
|
64
|
+
## Code of Conduct
|
|
65
|
+
|
|
66
|
+
By participating you agree to `CODE_OF_CONDUCT.md`.
|
data/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Andy Wang and openui-rails contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|