Class: AgentAdmit::CallerConsent
- Inherits:
-
Object
- Object
- AgentAdmit::CallerConsent
- Defined in:
- lib/agentadmit/caller_consent.rb
Overview
Caller-Identity Consent middleware: the "classify caller, then gate the right independent path" recipe as one Rack middleware, so an app owner does not have to hand-roll it.
One endpoint serves every caller class. On each request the middleware:
- classifies the caller from the STRUCTURE of the credential (a class the caller cannot self-select), before any consent check;
- routes to that class's ISOLATED consent path; no path reads or inherits another class's preference;
- permits or denies, and sets env variables (agentadmit.caller_class, agentadmit.consent, plus the standard agent env variables on the external-agent path).
- external_agent: an ag_at_ access token -> hosted introspection, which returns the external-agent consent verdict inline plus the granted scopes. Enforced here directly.
- in_app_ai: your application's own server-side AI code path -> the Consent Ledger /consent/check for the in-app-AI class.
- human_session: your application's own permission model (sharing, roles, grants). Deferred to your existing authorization by default; opt in to a stored human-session switch with gate_human: true.
The three decisions are independent: granting one never grants another.
SECURITY: this is a consent gate, not an authenticator. It classifies the caller and enforces the per-class CONSENT decision; it does not by itself authenticate a human session. Mount it AFTER your own authentication. On the human_session path it defers to your application's permission model and calls the app without re-authenticating. The external_agent path is always authenticated (hosted introspection); the in_app_ai path always evaluates the ledger.
Usage:
use AgentAdmit::CallerConsent,
# derive the class from your own credential structure, never caller input
classify_non_agent: ->(env) {
env["HTTP_X_INTERNAL_AI"] == ENV["INTERNAL_AI_SECRET"] ? "in_app_ai" : "human_session"
},
resolve_data_owner_id: ->(env) { Rack::Request.new(env).params["owner_id"] },
required_scope: "read:records"
Constant Summary collapse
- BEARER_AGENT_RE =
RFC 7235: the auth-scheme token is case-insensitive.
/\Abearer ag_at_/i- HUMAN_SESSION =
"human_session"- IN_APP_AI =
"in_app_ai"- EXTERNAL_AGENT =
"external_agent"
Instance Method Summary collapse
- #call(env) ⇒ Object
-
#classify_caller(env) ⇒ Object
Classify the caller from credential structure, before any consent check.
-
#initialize(app, resolve_data_owner_id: nil, classify_non_agent: nil, required_scope: nil, scope_group: nil, gate_human: false) ⇒ CallerConsent
constructor
A new instance of CallerConsent.
Constructor Details
#initialize(app, resolve_data_owner_id: nil, classify_non_agent: nil, required_scope: nil, scope_group: nil, gate_human: false) ⇒ CallerConsent
Returns a new instance of CallerConsent.
73 74 75 76 77 78 79 80 81 82 |
# File 'lib/agentadmit/caller_consent.rb', line 73 def initialize(app, resolve_data_owner_id: nil, classify_non_agent: nil, required_scope: nil, scope_group: nil, gate_human: false) @app = app @client = IntrospectionClient.new @resolve_data_owner_id = resolve_data_owner_id @classify_non_agent = classify_non_agent @required_scope = required_scope @scope_group = scope_group @gate_human = gate_human end |
Instance Method Details
#call(env) ⇒ Object
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/agentadmit/caller_consent.rb', line 98 def call(env) caller_class = classify_caller(env) env["agentadmit.caller_class"] = caller_class case caller_class when EXTERNAL_AGENT call_external_agent(env) when IN_APP_AI call_ledger_gated(env, IN_APP_AI, "in_app_ai", "The data owner has not enabled in-app AI analysis.") else if @gate_human call_ledger_gated(env, HUMAN_SESSION, "user", "The data owner has not enabled this access.") else # Defer the human path to the app's existing authorization. env["agentadmit.auth_type"] = "user" @app.call(env) end end end |
#classify_caller(env) ⇒ Object
Classify the caller from credential structure, before any consent check. An ag_at_ bearer token is an external agent; anything else is resolved by classify_non_agent (default: human_session). The class is derived, never self-selected by the caller.
90 91 92 93 94 95 96 |
# File 'lib/agentadmit/caller_consent.rb', line 90 def classify_caller(env) auth = env["HTTP_AUTHORIZATION"] || "" return EXTERNAL_AGENT if BEARER_AGENT_RE.match?(auth) return IN_APP_AI if @classify_non_agent && @classify_non_agent.call(env) == IN_APP_AI HUMAN_SESSION end |