arbiter-gem 0.1.0 → 0.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c1390ac17a355433c6f9e2f9f7a47e6b9b8a8ce166c8c37b1389e1950eb1696a
4
- data.tar.gz: 7ef97bb093b05948eeed2746e1d30fc3df630b6bd13eea53d2f163898003d4eb
3
+ metadata.gz: c96f3c93f0987c1c51c5a7d9cd4b11606e41669e34bd2b9c35cc14511ddbc394
4
+ data.tar.gz: cec5e2049ddcec4bb643742e013cdd8652b49c4f74d053a56c4e06641720462e
5
5
  SHA512:
6
- metadata.gz: a0a0a9382852eb361ca410cfe5a8aa28db293b2a052d490a7e64da5c34338ff30b33bafa0d3efa8b0d30e2389c0be0ab545d7034f40e6cb55269f9032aa0f108
7
- data.tar.gz: a52e0d340a0a267cb354c18dccf7a91c59e6b1eaefc861632317d48a7f24676db0a1cdee576d755cf2dae0a58398f91eeb3c0385fb77c5defbf7a8e8dd22eb95
6
+ metadata.gz: da81138d05eea4fee1fee34b9c30aff833d3083b9d63bd25bd69ce71152ca201056abc8ca2f15d7869de4fd4fac9c029f797f2fa804763b79d003eba30ce87aa
7
+ data.tar.gz: 220b705b197ea2521b37d572facff43e95938c3de8d1ad2480be5c29e07dea4191fb8fe1815fba14d64493afbb892687fc315f719266a9fed2136df18a5415c0
data/CHANGELOG.md CHANGED
@@ -1,6 +1,12 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.0 — 2026-10-11
4
- - Initial `arbiter-gem` Ruby client for compare and embed.
5
- - Finite candidate ranking, decision, and opt-in callable dispatch.
6
- - Explicit 402 error handling; never spends funds automatically.
3
+ ## 0.1.1 — 2026-10-12
4
+
5
+ - Route calls without a key to the public ARBITER endpoint, with its published daily free allowance.
6
+ - Send configured API keys using `X-API-Key`, matching the authenticated GRIP gateway.
7
+ - Document all five methods, authorization at execution, and HTTP 402 handling.
8
+ - Add RubyGems homepage, source, documentation, changelog and issues metadata.
9
+
10
+ ## 0.1.0 — 2026-10-12
11
+
12
+ - Initial Ruby compare, embed, rank, decide and dispatch APIs.
data/README.md CHANGED
@@ -1,109 +1,118 @@
1
- # ARBITER for Ruby
1
+ # ARBITER GEM
2
2
 
3
- **Gem:** `arbiter-gem`
4
- **Ruby module:** `Arbiter`
5
- **Concept:** The Last Conditional — measure a finite permitted action field instead of extending contextual branching logic.
6
-
7
- Deterministic remote ARBITER measurement in ordinary Ruby applications. No native engine, CLI installation, model weights, or Regolith required.
8
-
9
- ## Install
3
+ Deterministic ARBITER measurement in Ruby applications, using the remote ARBITER engine. No local ARBITER binary or model downloads.
10
4
 
11
5
  ```bash
12
6
  gem install arbiter-gem
13
7
  ```
14
8
 
15
- Gemfile:
16
-
17
- ```ruby
18
- gem "arbiter-gem"
19
- ```
9
+ ## Quickstart — 10 free comparisons per day
20
10
 
21
- ## Use
11
+ The public ARBITER endpoint advertises **10 successful measurements per day without a key**. That allowance is enforced by the service, not the gem. Free requests use `https://arbiter.grip.fyi/v1/compare`.
22
12
 
23
13
  ```ruby
24
14
  require "arbiter"
25
15
 
26
- result = Arbiter.compare(
16
+ Arbiter.configure(api_key: nil, base_url: "https://arbiter.grip.fyi")
17
+ results = Arbiter.rank(
27
18
  query: "Resolve a failed payment",
28
- candidates: ["Retry", "Escalate", "Notify"],
29
- top_k: 3
19
+ candidates: ["Retry the payment", "Escalate to billing", "Notify the customer"]
30
20
  )
31
- puts result # raw ARBITER JSON object
21
+ results.each { |row| puts "#{row[:candidate]}: #{row[:score]}" }
32
22
  ```
33
23
 
34
- The `compare` call returns the raw decoded API JSON object. To obtain a normalized ranking for documented formats, use `Arbiter.rank(...)`. Unknown response formats raise `Arbiter::InvalidResponse` rather than invent a winner.
24
+ For a direct HTTP trial (no Ruby required):
35
25
 
36
- Embeddings:
37
-
38
- ```ruby
39
- response = Arbiter.embed(texts: ["one", "two"])
26
+ ```bash
27
+ curl -sS https://arbiter.grip.fyi/v1/compare \
28
+ -H 'Content-Type: application/json' \
29
+ -d '{"query":"Resolve a failed payment","candidates":["Retry the payment","Escalate to billing","Notify the customer"]}'
40
30
  ```
41
31
 
42
- ### The Last Conditional
32
+ **Free-allowance verification:** This route and policy originate from ARBITER's published CLI offering; if the live gateway returns 401, 403 or 402, check its current status before advertising the free tier as operational. No API key is needed for the published free tier.
43
33
 
44
- ```ruby
45
- permitted = ->(name, _action) { name != :retry || payment.retry_allowed? }
34
+ ## Funded API-key credits
46
35
 
47
- actions = {
48
- retry: { description: "Retry the payment", run: -> { retry_payment } },
49
- escalate: { description: "Escalate to billing", run: -> { escalate_payment } },
50
- notify: { description: "Notify customer", run: -> { notify_customer } }
51
- }
36
+ For paid usage, start at https://grip.fyi and request a GRIP-issued funded API key. **A self-service key issuance or credit-pack checkout URL has not been independently verified.** Do not assume purchasing has completed until credits and a key are issued.
52
37
 
53
- # Measurement only — never runs Ruby callables:
54
- choice = Arbiter.decide(intent: "Resolve the payment", actions: actions, permitted: permitted)
55
- puts choice[:action]
38
+ The working GRIP owner-access transport uses this header:
56
39
 
57
- # Opt-in execution with permission revalidated immediately before invocation:
58
- # Arbiter.dispatch(intent: "Resolve the payment", actions: actions, permitted: permitted)
40
+ ```http
41
+ X-API-Key: YOUR_GRIP_API_KEY
59
42
  ```
60
43
 
61
- **Authority is external to measurement.** Filter prohibited actions first. Hard policy rules, authorization, cryptographic checks and transactional safety still belong to the application. `dispatch` never executes unknown actions returned by the API. Both the input field and the selected action are checked.
62
-
63
- ## Configuration
64
-
65
44
  ```bash
66
- export ARBITER_API_KEY="<credit-api-key>"
45
+ export GRIP_API_KEY="<the-key-issued-for-GRIP-credits>"
67
46
  ```
68
47
 
69
- `GRIP_API_KEY` is also recognized. If the GRIP API uses a different header for credits, adjust the `Client` configuration before publishing; the currently implemented key header is `Authorization: Bearer` and has not been live-verified against the credit-pack gateway.
48
+ ```ruby
49
+ require "arbiter"
50
+ Arbiter.configure(api_key: ENV.fetch("GRIP_API_KEY"), base_url: "https://api.grip.fyi")
51
+ puts Arbiter.compare(query: "Route incident", candidates: ["Escalate", "Restart service"])
52
+ ```
53
+
54
+ **Rate:** advertised metered x402 charge of **$0.001 USDC per compare/embed call**. Credit-pack denominations and checkout are not confirmed in public documentation. RubyGems credentials are unrelated to API keys. The gem never signs wallet payments or authorizes spending automatically.
55
+
56
+ ## API
57
+
58
+ All methods belong to the `Arbiter` module. `Arbiter.compare` and `Arbiter.embed` return decoded JSON; normalized rankings are obtained using `rank`.
70
59
 
71
60
  ```ruby
72
- Arbiter.configure(
73
- api_key: ENV.fetch("ARBITER_API_KEY"),
74
- base_url: "https://api.grip.fyi"
75
- )
61
+ Arbiter.compare(query: "Current intent", candidates: ["A", "B"], top_k: 2)
62
+ Arbiter.embed(texts: ["A", "B"])
63
+ Arbiter.rank(query: "Current intent", candidates: ["A", "B"], top_k: 2)
76
64
  ```
77
65
 
78
- Endpoints: `POST /v1/compare` with `query`, `candidates`, optional `top_k`; `POST /v1/embed` with `texts`.
66
+ `embed` is typically used with funded API access; verify public availability before relying on it without a key.
67
+
68
+ Actions require a `description` and callable `run`:
69
+
70
+ ```ruby
71
+ actions = {
72
+ retry: { description: "Retry the payment", run: -> { :retried } },
73
+ escalate: { description: "Escalate to billing", run: -> { :escalated } },
74
+ notify: { description: "Notify the customer", run: -> { :notified } }
75
+ }
76
+
77
+ allowed = ->(name, _action) { name != :retry }
79
78
 
80
- ### HTTP 402
79
+ # Measures only; no action is executed:
80
+ choice = Arbiter.decide(intent: "Resolve failed payment", actions: actions, permitted: allowed)
81
+ puts choice[:action]
81
82
 
82
- The ARBITER service may request x402 payment. This client never signs wallet transactions or spends automatically:
83
+ # Explicitly executes a permitted callable, checking permission again before execution:
84
+ executed = Arbiter.dispatch(intent: "Resolve failed payment", actions: actions, permitted: allowed)
85
+ puts executed[:value]
86
+ ```
87
+
88
+ `decide` and `dispatch` require **at least two permitted actions**. The application owns authorization; ranking cannot override it. `dispatch` sends another comparison request; it does not execute the earlier decision object.
89
+
90
+ ## Error handling
83
91
 
84
92
  ```ruby
85
93
  begin
86
- Arbiter.compare(query: "...", candidates: ["A", "B"])
94
+ Arbiter.compare(query: "Resolve failed payment", candidates: ["Retry", "Escalate"])
87
95
  rescue Arbiter::PaymentRequired => e
88
- puts "HTTP #{e.status} — payment required"
89
- # e.challenge contains payment-related response headers, if supplied
96
+ warn "HTTP #{e.status} — obtain credits or handle the x402 challenge explicitly"
90
97
  end
91
98
  ```
92
99
 
93
- Use funded API-key credits if supported by the deployment, or integrate a separately authorized wallet payment mechanism. RubyGems publication credentials **are unrelated** to API credits.
100
+ ### Environment variables
94
101
 
95
- ## Testing
102
+ - `ARBITER_API_KEY` or `GRIP_API_KEY`: optional API key, sent as `X-API-Key`.
103
+ - `ARBITER_BASE_URL`: optional override. Otherwise keyless calls use the public endpoint and authenticated calls use the GRIP API.
96
104
 
97
- ```bash
98
- ruby -Ilib:test test/test_arbiter.rb
99
- ```
105
+ ## Links
100
106
 
101
- ## Publishing
107
+ - Docs: https://arbiter-gem.grip.fyi
108
+ - RubyGems: https://rubygems.org/gems/arbiter-gem
109
+ - Source: https://github.com/ziolndr/arbiter-gem (requires public repository publication)
110
+ - GRIP: https://grip.fyi
102
111
 
103
- Run `./PUBLISH.command` on a machine already authenticated to RubyGems. It performs tests, builds the gem, then calls `gem push` using the existing RubyGems credential store. It never copies, prints, or packages credentials. Two-factor authentication may still require an OTP.
112
+ ## Tests
104
113
 
105
- ## Scope
106
-
107
- Version 0.1.0 is a release candidate tested against stubbed HTTP responses. Before public release, validate the live API response schema, credit-key header, and Gem name availability. No x402 wallet signer is bundled.
114
+ ```bash
115
+ ruby -Ilib:test test/test_arbiter.rb
116
+ ```
108
117
 
109
118
  MIT © 2026 Actual General Intelligence, Inc.
@@ -2,20 +2,30 @@
2
2
  require "json"
3
3
  require "net/http"
4
4
  require "uri"
5
+ require "open3"
6
+ require "tempfile"
5
7
 
6
8
  module Arbiter
7
9
  class Client
8
- DEFAULT_BASE_URL = "https://api.grip.fyi".freeze
10
+ PAID_BASE_URL = "https://api.grip.fyi".freeze
11
+ PUBLIC_BASE_URL = "https://arbiter.grip.fyi".freeze
12
+ DEFAULT_BASE_URL = PAID_BASE_URL
9
13
  attr_reader :base_url
10
14
 
11
15
  def initialize(api_key: ENV["ARBITER_API_KEY"] || ENV["GRIP_API_KEY"],
12
- base_url: ENV.fetch("ARBITER_BASE_URL", DEFAULT_BASE_URL),
16
+ base_url: ENV["ARBITER_BASE_URL"],
13
17
  open_timeout: 5, read_timeout: 30, transport: nil)
14
18
  @api_key = api_key
19
+ # Keyless compare uses the public ARBITER free-allowance route.
20
+ # Funded/owner API keys use the GRIP gateway.
21
+ base_url ||= (@api_key && !@api_key.empty? ? PAID_BASE_URL : PUBLIC_BASE_URL)
15
22
  @base_url = base_url.to_s.sub(%r{/+$}, "")
16
23
  @open_timeout = open_timeout
17
24
  @read_timeout = read_timeout
18
- @transport = transport
25
+ unless transport.nil? || transport == :auto || transport == :net_http || transport == :curl || transport.respond_to?(:call)
26
+ raise InvalidInput, "transport must be :auto, :curl, :net_http, or a callable"
27
+ end
28
+ @transport = transport || :auto
19
29
  validate_url!
20
30
  end
21
31
 
@@ -62,10 +72,12 @@ module Arbiter
62
72
  def request(path, payload)
63
73
  uri = URI("#{@base_url}#{path}")
64
74
  headers = { "content-type" => "application/json", "accept" => "application/json" }
65
- headers["authorization"] = "Bearer #{@api_key}" if @api_key && !@api_key.empty?
75
+ headers["X-API-Key"] = @api_key if @api_key && !@api_key.empty?
66
76
  body = JSON.generate(payload)
67
- response = if @transport
77
+ response = if @transport.respond_to?(:call)
68
78
  @transport.call(uri, headers, body)
79
+ elsif @transport == :curl || (@transport == :auto && curl_available? && known_grip_host?(uri))
80
+ curl_post(uri, headers, body)
69
81
  else
70
82
  http_post(uri, headers, body)
71
83
  end
@@ -90,6 +102,67 @@ module Arbiter
90
102
  raise HTTPError.new(0, "ARBITER transport error (#{e.class})")
91
103
  end
92
104
 
105
+
106
+ # The hosted GRIP gateway has rejected some default language HTTP clients
107
+ # at Cloudflare's browser-signature layer. Prefer curl for GRIP hosts when
108
+ # installed; never expose the API key in process argv, files, or exception
109
+ # text. Caller-supplied endpoints continue to use Net::HTTP by default.
110
+ def known_grip_host?(uri)
111
+ uri.scheme == "https" && ["api.grip.fyi", "arbiter.grip.fyi"].include?(uri.host)
112
+ end
113
+
114
+ def curl_available?
115
+ ENV.fetch("PATH", "").split(File::PATH_SEPARATOR).any? do |part|
116
+ File.executable?(File.join(part, "curl"))
117
+ end
118
+ end
119
+
120
+ CurlResponse = Struct.new(:code, :body, :headers) do
121
+ def each_header(&block)
122
+ headers.each(&block)
123
+ end
124
+ end
125
+
126
+ def curl_post(uri, headers, body)
127
+ raise HTTPError.new(0, "curl not available") unless curl_available?
128
+ Tempfile.create("arbiter-curl-payload") do |payload_file|
129
+ Tempfile.create("arbiter-curl-headers") do |header_file|
130
+ Tempfile.create("arbiter-curl-output") do |output_file|
131
+ payload_file.write(body)
132
+ payload_file.flush
133
+ # X-API-Key is passed via STDIN, not command line arguments.
134
+ headers.each_value do |value|
135
+ raise InvalidInput, "header contains a line break" if value.match?(/[\r\n]/)
136
+ end
137
+ config = headers.map { |key, value| %(header = "#{curl_escape(key + ': ' + value)}") }.join("\n") + "\n"
138
+ args = ["curl", "--config", "-", "--silent", "--show-error",
139
+ "--connect-timeout", @open_timeout.to_s,
140
+ "--max-time", (@open_timeout + @read_timeout).to_s,
141
+ "--request", "POST", "--url", uri.to_s,
142
+ "--data-binary", "@#{payload_file.path}",
143
+ "--dump-header", header_file.path,
144
+ "--output", output_file.path,
145
+ "--write-out", "%{http_code}"]
146
+ stdout, _stderr, status = Open3.capture3(*args, stdin_data: config)
147
+ raise HTTPError.new(0, "ARBITER transport error (curl)") unless status.success?
148
+ code = stdout.strip
149
+ raise InvalidResponse, "curl returned no HTTP status" unless code.match?(/\A[1-5][0-9]{2}\z/)
150
+ lines = header_file.read.split(/\r?\n/)
151
+ response_headers = lines.filter_map do |line|
152
+ next if line.start_with?("HTTP/") || !line.include?(":")
153
+ key, value = line.split(":", 2)
154
+ [key.downcase.strip, value.to_s.strip]
155
+ end.to_h
156
+ CurlResponse.new(code, output_file.read, response_headers)
157
+ end
158
+ end
159
+ end
160
+ end
161
+
162
+ def curl_escape(value)
163
+ value.to_s.gsub("\\", "\\\\").gsub('"', '\\"')
164
+ end
165
+
93
166
  def http_post(uri, headers, body)
94
167
  req = Net::HTTP::Post.new(uri)
95
168
  headers.each { |key, value| req[key] = value }
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Arbiter
3
- VERSION = "0.1.0"
3
+ VERSION = "0.1.1"
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: arbiter-gem
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Actual General Intelligence, Inc.
@@ -9,8 +9,9 @@ bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies: []
12
- description: Ruby client for ARBITER compare and embed, plus explicit finite-action
13
- dispatch. Remote measurement, no local engine or binary.
12
+ description: Measure and rank candidate actions with the ARBITER API from Ruby. Includes
13
+ compare, embed, rank, decide and dispatch; MCP interoperability and x402 payment
14
+ challenges. No native engine.
14
15
  executables: []
15
16
  extensions: []
16
17
  extra_rdoc_files: []
@@ -24,11 +25,15 @@ files:
24
25
  - lib/arbiter/flow.rb
25
26
  - lib/arbiter/ranking.rb
26
27
  - lib/arbiter/version.rb
27
- homepage: https://arbiter.grip.fyi
28
+ homepage: https://arbiter-gem.grip.fyi
28
29
  licenses:
29
30
  - MIT
30
31
  metadata:
31
- homepage_uri: https://arbiter.grip.fyi
32
+ homepage_uri: https://arbiter-gem.grip.fyi
33
+ source_code_uri: https://github.com/ziolndr/arbiter-gem
34
+ documentation_uri: https://arbiter-gem.grip.fyi/#methods
35
+ changelog_uri: https://github.com/ziolndr/arbiter-gem/blob/main/CHANGELOG.md
36
+ bug_tracker_uri: https://github.com/ziolndr/arbiter-gem/issues
32
37
  rdoc_options: []
33
38
  require_paths:
34
39
  - lib
@@ -45,5 +50,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
45
50
  requirements: []
46
51
  rubygems_version: 4.0.3
47
52
  specification_version: 4
48
- summary: ARBITER deterministic measurement for Ruby decisions and control flow
53
+ summary: Deterministic decision, ranking and action selection in Ruby; ARBITER API,
54
+ MCP and x402
49
55
  test_files: []