agentadmit 1.13.0 → 1.14.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 +4 -4
- data/README.md +25 -0
- data/lib/agentadmit/introspection_client.rb +82 -2
- data/lib/agentadmit/tokens_client.rb +10 -1
- data/lib/agentadmit/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1af95260c413c1ce419cfdd29dbb64abffa228c2c252d729f62d8d8514713d43
|
|
4
|
+
data.tar.gz: c9a638031a64b6d5a634687f0d8bd150b928ff10e76af2b6203215c908aae7c6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e12e4ede6e53c77634ef65d249a5e8363ba9a2a7bba650fe9a3b5696410dbe30a90c170bdd44ac1a44dbe8b7c0fa5145b8fa4d1ae5c8d47084d5f2e2e6d4f0e4
|
|
7
|
+
data.tar.gz: d6c0bfb352a0af3044b8153846969b0e9500e13d7a82489d06ad80383bc0b171156bbe5a95e84913c43b21f11127523342359f6f9939a0ba5e3b1ea8f004e6f1
|
data/README.md
CHANGED
|
@@ -398,6 +398,31 @@ result.user_intent # => "Book my flights to the Austin offsite in October" or ni
|
|
|
398
398
|
|
|
399
399
|
`user_intent` is nullable -- connections issued without one (or by servers that predate the field) read as `nil`. Months later, a review screen can answer "is this still appropriate?" with the user's own stated boundary, not just the app's. Like purpose, it is a review-time record and never an enforcement input; authorization decisions ride scopes, connection status, and consent.
|
|
400
400
|
|
|
401
|
+
## Agent Designation
|
|
402
|
+
|
|
403
|
+
On AgentAdmit's consent page the user says which agent app a connection is for and, optionally, which AI model family it mainly uses. That is the user's statement, recorded with the grant; it is not a verified identity of the agent. Separately, an agent may report its own platform and model when it redeems the connection token; that is unverified too.
|
|
404
|
+
|
|
405
|
+
The verify response reports both on every introspection, and the SDK surfaces them on the result as `designated_agent` and `agent_reported` (`nil` when the connection carries none):
|
|
406
|
+
|
|
407
|
+
```ruby
|
|
408
|
+
result = AgentAdmit::IntrospectionClient.new.verify(token)
|
|
409
|
+
|
|
410
|
+
# {"agent_id" => ..., "name" => ..., "source" => "user_declared",
|
|
411
|
+
# "model" => {"model_id" => ..., "name" => ...} or nil}
|
|
412
|
+
named = result.designated_agent
|
|
413
|
+
# {"platform" => ..., "model" => {"id" => ..., "version" => ...} or nil}
|
|
414
|
+
reported = result.agent_reported
|
|
415
|
+
|
|
416
|
+
puts "Named by the user: #{named ? named['name'] : '(not named)'}"
|
|
417
|
+
puts "Reported by the agent: #{reported&.dig('model', 'id') || '(nothing)'}"
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
Use `designated_agent["name"]` when you tell a user which of their connections did something: it is the name they chose. `agent_id` and `model["model_id"]` are the ids of your list entries, or `nil` when the user typed a name that is not on your list.
|
|
421
|
+
|
|
422
|
+
If your code redeems connection tokens with `TokensClient#exchange`, pass `agent_platform:` and `agent_model:` to send along what the agent says it runs on (since 1.14.1).
|
|
423
|
+
|
|
424
|
+
Do not branch authorization on either field. They are records for people reviewing a connection, not a gate. Scope checks, connection status, and consent verdicts remain the only decision inputs. To act on a designation, use the hosted revoke API (`designated_agent_id`, `designated_model_id`) or your list's block setting in the dashboard.
|
|
425
|
+
|
|
401
426
|
## App-Attested Presence
|
|
402
427
|
|
|
403
428
|
If your app gates token minting behind its own embedded passkey/WebAuthn ceremony, AgentAdmit never witnesses that ceremony (it is origin-bound), so by default the hosted service reports `presence.verified: false` for those connections. Attest the ceremony fact at issuance to close that gap -- AFTER verifying and consuming your own fresh, purpose-bound attestation:
|
|
@@ -34,7 +34,8 @@ module AgentAdmit
|
|
|
34
34
|
:sub, :role, :app_id, :jti, :exp, :consent,
|
|
35
35
|
:presence, :purpose, :user_intent,
|
|
36
36
|
:action_confirmation, :audit_row_id,
|
|
37
|
-
:consumed_receipt,
|
|
37
|
+
:consumed_receipt, :designated_agent,
|
|
38
|
+
:agent_reported, keyword_init: true) do
|
|
38
39
|
def has_scope?(scope)
|
|
39
40
|
scopes.include?(scope)
|
|
40
41
|
end
|
|
@@ -94,6 +95,24 @@ module AgentAdmit
|
|
|
94
95
|
# `consumed_receipt` (Hash or nil) is a replay diagnostic for an
|
|
95
96
|
# already-consumed confirm-each-time attestation. It is not a fresh
|
|
96
97
|
# authorization and must not be treated as permission to run.
|
|
98
|
+
|
|
99
|
+
# `designated_agent` (Hash or nil) is what the USER said on AgentAdmit's
|
|
100
|
+
# consent page: which agent app the connection is for and, optionally,
|
|
101
|
+
# which AI model family it mainly uses:
|
|
102
|
+
# {"agent_id" => String|nil, "name" => String,
|
|
103
|
+
# "source" => "user_declared",
|
|
104
|
+
# "model" => {"model_id" => String|nil, "name" => String} | nil}.
|
|
105
|
+
# It is the user's statement, recorded with the grant; it is not a
|
|
106
|
+
# verified identity of the agent. `agent_id` / `model_id` are nil when
|
|
107
|
+
# the user typed a name that is not on the app's list.
|
|
108
|
+
|
|
109
|
+
# `agent_reported` (Hash or nil) is what the AGENT reported about itself
|
|
110
|
+
# when it redeemed the connection token:
|
|
111
|
+
# {"platform" => String|nil,
|
|
112
|
+
# "model" => {"id" => String, "version" => String|nil} | nil}.
|
|
113
|
+
# Unverified. Both fields are review-time records only, never an
|
|
114
|
+
# enforcement input; authorization decisions ride scopes, connection
|
|
115
|
+
# status, and consent.
|
|
97
116
|
end
|
|
98
117
|
|
|
99
118
|
class << self
|
|
@@ -192,6 +211,58 @@ module AgentAdmit
|
|
|
192
211
|
"row_hash" => raw["row_hash"].is_a?(String) ? raw["row_hash"] : nil }
|
|
193
212
|
end
|
|
194
213
|
|
|
214
|
+
##
|
|
215
|
+
# The user's agent designation from a verify response, or nil when the
|
|
216
|
+
# block is absent, null, or not a user-declared designation. Strict: a
|
|
217
|
+
# non-empty String name and source "user_declared", or the whole block
|
|
218
|
+
# is dropped. A malformed model is dropped and the app is kept. This is
|
|
219
|
+
# what the user said on the consent page, not a verified identity.
|
|
220
|
+
#
|
|
221
|
+
# @param raw [Object] the `designated_agent` value from the verify body
|
|
222
|
+
# @return [Hash, nil]
|
|
223
|
+
#
|
|
224
|
+
def parse_designated_agent(raw)
|
|
225
|
+
return nil unless raw.is_a?(Hash)
|
|
226
|
+
name = raw["name"]
|
|
227
|
+
return nil unless name.is_a?(String) && !name.empty?
|
|
228
|
+
return nil unless raw["source"] == "user_declared"
|
|
229
|
+
|
|
230
|
+
m = raw["model"]
|
|
231
|
+
model =
|
|
232
|
+
if m.is_a?(Hash) && m["name"].is_a?(String) && !m["name"].empty?
|
|
233
|
+
{ "model_id" => m["model_id"].is_a?(String) ? m["model_id"] : nil,
|
|
234
|
+
"name" => m["name"] }
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
{ "agent_id" => raw["agent_id"].is_a?(String) ? raw["agent_id"] : nil,
|
|
238
|
+
"name" => name,
|
|
239
|
+
"source" => "user_declared",
|
|
240
|
+
"model" => model }
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
##
|
|
244
|
+
# What the agent reported about itself at token exchange, or nil when
|
|
245
|
+
# the block carries neither a platform nor a model id. Unverified.
|
|
246
|
+
#
|
|
247
|
+
# @param raw [Object] the `agent_reported` value from the verify body
|
|
248
|
+
# @return [Hash, nil]
|
|
249
|
+
#
|
|
250
|
+
def parse_agent_reported(raw)
|
|
251
|
+
return nil unless raw.is_a?(Hash)
|
|
252
|
+
|
|
253
|
+
platform = raw["platform"]
|
|
254
|
+
platform = nil unless platform.is_a?(String) && !platform.empty?
|
|
255
|
+
m = raw["model"]
|
|
256
|
+
model =
|
|
257
|
+
if m.is_a?(Hash) && m["id"].is_a?(String) && !m["id"].empty?
|
|
258
|
+
{ "id" => m["id"],
|
|
259
|
+
"version" => m["version"].is_a?(String) ? m["version"] : nil }
|
|
260
|
+
end
|
|
261
|
+
return nil if platform.nil? && model.nil?
|
|
262
|
+
|
|
263
|
+
{ "platform" => platform, "model" => model }
|
|
264
|
+
end
|
|
265
|
+
|
|
195
266
|
##
|
|
196
267
|
# The agent's X-AgentAdmit-Action-Attestation header from a Rack env:
|
|
197
268
|
# first value only, trimmed, capped at 120 characters. nil when absent
|
|
@@ -408,6 +479,13 @@ module AgentAdmit
|
|
|
408
479
|
audit_row_id = nil unless audit_row_id.is_a?(String)
|
|
409
480
|
consumed_receipt = self.class.parse_consumed_receipt(data["consumed_receipt"])
|
|
410
481
|
|
|
482
|
+
# Agent designation (1.14.0): what the user said the connection is
|
|
483
|
+
# for, and what the agent reported about itself. Review-time records,
|
|
484
|
+
# never an enforcement input, so a malformed block is dropped rather
|
|
485
|
+
# than failing the verify.
|
|
486
|
+
designated_agent = self.class.parse_designated_agent(data["designated_agent"])
|
|
487
|
+
agent_reported = self.class.parse_agent_reported(data["agent_reported"])
|
|
488
|
+
|
|
411
489
|
return IntrospectionResult.new(
|
|
412
490
|
user_id: data["user_id"],
|
|
413
491
|
connection_id: data["connection_id"],
|
|
@@ -424,7 +502,9 @@ module AgentAdmit
|
|
|
424
502
|
user_intent: user_intent,
|
|
425
503
|
action_confirmation: action_confirmation,
|
|
426
504
|
audit_row_id: audit_row_id,
|
|
427
|
-
consumed_receipt: consumed_receipt
|
|
505
|
+
consumed_receipt: consumed_receipt,
|
|
506
|
+
designated_agent: designated_agent,
|
|
507
|
+
agent_reported: agent_reported
|
|
428
508
|
)
|
|
429
509
|
end
|
|
430
510
|
|
|
@@ -111,13 +111,22 @@ module AgentAdmit
|
|
|
111
111
|
# @param connection_token [String] the ag_ct_… connection token
|
|
112
112
|
# @param agent_label [String, nil] human-readable agent name
|
|
113
113
|
# @param agent_id [String, nil] agent identifier
|
|
114
|
+
# @param agent_platform [String, nil] the agent app or runtime the agent
|
|
115
|
+
# says it runs in (reported by the agent, unverified)
|
|
116
|
+
# @param agent_model [Hash, nil] the AI model the agent says it runs on:
|
|
117
|
+
# {"id" => String, "version" => String (optional)} (reported by the
|
|
118
|
+
# agent, unverified). The hosted service records both and never lets
|
|
119
|
+
# them decide the outcome of the exchange.
|
|
114
120
|
# @return [Hash] the exchange response — "access_token" is the ag_at_… token
|
|
115
121
|
# @raise [IntrospectionError] if the exchange fails
|
|
116
122
|
#
|
|
117
|
-
def exchange(connection_token, agent_label: nil, agent_id: nil
|
|
123
|
+
def exchange(connection_token, agent_label: nil, agent_id: nil,
|
|
124
|
+
agent_platform: nil, agent_model: nil)
|
|
118
125
|
body = { "token" => connection_token }
|
|
119
126
|
body["agent_label"] = agent_label if agent_label
|
|
120
127
|
body["agent_id"] = agent_id if agent_id
|
|
128
|
+
body["agent_platform"] = agent_platform if agent_platform
|
|
129
|
+
body["agent_model"] = agent_model if agent_model
|
|
121
130
|
|
|
122
131
|
post("/api/v1/exchange", body, authenticated: false, op: "exchange")
|
|
123
132
|
end
|
data/lib/agentadmit/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: agentadmit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.14.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Christopher Emerson
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-11-06 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: json
|