agentadmit 1.5.1 → 1.7.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 +4 -4
- data/README.md +23 -0
- data/lib/agentadmit/introspection_client.rb +14 -2
- data/lib/agentadmit/tokens_client.rb +15 -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: 53205cacbbf7942a6cb2600c9cea4c0c739d991be95bd7c627df405287e5f52c
|
|
4
|
+
data.tar.gz: b9849673334811698875c9733abc1b69c2938111cf4b928cde741cb0e6ba6d0f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ed8c4e40cc18a7749257674d2ba0fba6d8fb79946aeb868cf84fa5f5fa67075eff3cc80456e67e34886157f63cbaaceb1156abc3ce92f87a3400096920354f69
|
|
7
|
+
data.tar.gz: e184a592c70ac59b82f906f00a7cae3c83ecd3b9681e5a35421bad55c3979a11bbfbfe45f85f8135fe4a909ed1f1c394108b03d86b199f91bf999ac8cf40e1bc
|
data/README.md
CHANGED
|
@@ -302,3 +302,26 @@ granted = tokens.exchange(connection_token, agent_label: "MyAssistant")
|
|
|
302
302
|
# Revoke when the user disconnects the agent.
|
|
303
303
|
tokens.revoke(granted["connection_id"], reason: "user_requested")
|
|
304
304
|
```
|
|
305
|
+
|
|
306
|
+
### Declared purpose
|
|
307
|
+
|
|
308
|
+
Declared purpose: the user-facing reason recorded on the grant at the consent moment. Review-time record only, never an enforcement input; authorization decisions ride scopes, connection status, and consent.
|
|
309
|
+
|
|
310
|
+
Pass it when issuing a connection token (optional, max 300 characters; omitted from the request when `nil`):
|
|
311
|
+
|
|
312
|
+
```ruby
|
|
313
|
+
issued = tokens.issue_token(
|
|
314
|
+
user_id: "user_42",
|
|
315
|
+
scopes: ["read:orders"],
|
|
316
|
+
purpose: "Book quarterly travel for the sales team"
|
|
317
|
+
)
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
The verify result carries it back for display in dashboards, review screens, and audit views:
|
|
321
|
+
|
|
322
|
+
```ruby
|
|
323
|
+
result = AgentAdmit::IntrospectionClient.new.verify(token)
|
|
324
|
+
result.purpose # => "Book quarterly travel for the sales team" or nil
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
`purpose` is nullable -- connections issued without one (or by servers that predate the field) read as `nil`. Do not branch authorization on it; keep enforcement on scopes, connection status, and consent.
|
|
@@ -17,7 +17,7 @@ module AgentAdmit
|
|
|
17
17
|
|
|
18
18
|
IntrospectionResult = Struct.new(:user_id, :connection_id, :scopes, :agent_label,
|
|
19
19
|
:sub, :role, :app_id, :jti, :exp, :consent,
|
|
20
|
-
:presence, keyword_init: true) do
|
|
20
|
+
:presence, :purpose, keyword_init: true) do
|
|
21
21
|
def has_scope?(scope)
|
|
22
22
|
scopes.include?(scope)
|
|
23
23
|
end
|
|
@@ -47,6 +47,11 @@ module AgentAdmit
|
|
|
47
47
|
def presence_verified?
|
|
48
48
|
presence.is_a?(Hash) && presence["verified"] == true
|
|
49
49
|
end
|
|
50
|
+
|
|
51
|
+
# `purpose` (String or nil) is the declared purpose: the user-facing
|
|
52
|
+
# reason recorded on the grant at the consent moment. Review-time record
|
|
53
|
+
# only, never an enforcement input; authorization decisions ride scopes,
|
|
54
|
+
# connection status, and consent.
|
|
50
55
|
end
|
|
51
56
|
|
|
52
57
|
def initialize(config = nil)
|
|
@@ -177,6 +182,12 @@ module AgentAdmit
|
|
|
177
182
|
presence = data["presence"]
|
|
178
183
|
presence = nil unless presence.is_a?(Hash) && [true, false].include?(presence["verified"])
|
|
179
184
|
|
|
185
|
+
# Declared purpose passes through as-is when it is a String (the
|
|
186
|
+
# hosted /verify returns it nullable). It is a review-time record,
|
|
187
|
+
# never an enforcement input, so a malformed value is simply dropped.
|
|
188
|
+
purpose = data["purpose"]
|
|
189
|
+
purpose = nil unless purpose.is_a?(String)
|
|
190
|
+
|
|
180
191
|
return IntrospectionResult.new(
|
|
181
192
|
user_id: data["user_id"],
|
|
182
193
|
connection_id: data["connection_id"],
|
|
@@ -188,7 +199,8 @@ module AgentAdmit
|
|
|
188
199
|
jti: data["jti"],
|
|
189
200
|
exp: data["exp"],
|
|
190
201
|
consent: consent,
|
|
191
|
-
presence: presence
|
|
202
|
+
presence: presence,
|
|
203
|
+
purpose: purpose
|
|
192
204
|
)
|
|
193
205
|
end
|
|
194
206
|
|
|
@@ -15,6 +15,9 @@ module AgentAdmit
|
|
|
15
15
|
# instead for an until-revoked connection (explicit JSON null).
|
|
16
16
|
UNSET = Object.new.freeze
|
|
17
17
|
|
|
18
|
+
# Maximum length of a declared purpose (matches the hosted API contract).
|
|
19
|
+
PURPOSE_MAX_LENGTH = 300
|
|
20
|
+
|
|
18
21
|
def initialize(config = nil)
|
|
19
22
|
@config = config || AgentAdmit.configuration || Config.new
|
|
20
23
|
@config.validate_api_key!
|
|
@@ -34,13 +37,24 @@ module AgentAdmit
|
|
|
34
37
|
# @param scopes [Array<String>] scopes the connection grants
|
|
35
38
|
# @param role [String, nil] the user's role on the connection
|
|
36
39
|
# @param duration_seconds [Integer, nil, UNSET] see above
|
|
40
|
+
# @param purpose [String, nil] declared purpose: the user-facing reason
|
|
41
|
+
# recorded on the grant at the consent moment. Review-time record only,
|
|
42
|
+
# never an enforcement input; authorization decisions ride scopes,
|
|
43
|
+
# connection status, and consent. Max 300 characters; omitted from the
|
|
44
|
+
# request when nil.
|
|
37
45
|
# @return [Hash] the issue response — "token" is the self-describing
|
|
38
46
|
# ag_ct_… connection token to hand to the user's agent
|
|
47
|
+
# @raise [ArgumentError] if purpose exceeds 300 characters
|
|
39
48
|
# @raise [IntrospectionError] if issuance fails
|
|
40
49
|
#
|
|
41
|
-
def issue_token(user_id:, scopes:, role: nil, duration_seconds: UNSET)
|
|
50
|
+
def issue_token(user_id:, scopes:, role: nil, duration_seconds: UNSET, purpose: nil)
|
|
51
|
+
if purpose && purpose.length > PURPOSE_MAX_LENGTH
|
|
52
|
+
raise ArgumentError, "purpose must be at most #{PURPOSE_MAX_LENGTH} characters"
|
|
53
|
+
end
|
|
54
|
+
|
|
42
55
|
body = { "user_id" => user_id, "scopes" => scopes }
|
|
43
56
|
body["role"] = role if role
|
|
57
|
+
body["purpose"] = purpose if purpose
|
|
44
58
|
# Tri-state: the UNSET sentinel omits the key entirely; nil survives
|
|
45
59
|
# JSON.generate as explicit JSON null (no compact, no nil-guard).
|
|
46
60
|
body["duration_seconds"] = duration_seconds unless duration_seconds.equal?(UNSET)
|
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.7.0
|
|
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-08-05 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: json
|