Ctrl K
ComunidadGuías…

specs/agent-identity.feature

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
Feature: Agent identity and delegated API authorization
  As an Agent controller
  I want Agents to have durable identities and request constrained access to connected API resources
  So that Agents can act independently or by delegation without receiving refresh tokens

  Background:
    Given a first admin exists
    And Agent identity is enabled for the tenant

  Rule: Public APIs expose product resources rather than security implementation records

    @entrypoint:agent-protocol @journey:agent-public-resource-model @proof:unit
    Scenario: API clients manage stable product aggregates
      Given Agent identity uses protocol registrations, host credentials, and identity bindings internally
      And external authorization uses discovery metadata, OAuth clients, connection state, and token leases internally
      When an Agent, controller, or administrator reads the Realmroot API contract
      Then the public resources are Agents, Agent installations, Agent installation enrollments, API resources, account connections, Agent access requests, Permissions, and audit events
      And the authenticated Agent addresses its access requests below `/agent/access-requests`
      And Agent registrations, hosts, identity bindings, connection intents, OAuth integration records, and token leases remain private implementation records
      And each public resource has one canonical URI in its caller boundary
      And Agent installation representations never expose internal Host identifiers

  Rule: Agent identities remain stable across hosts and credentials

    @entrypoint:agent-protocol @journey:agent-identity-enrollment @proof:unit
    Scenario: A new Agent explicitly establishes a stable identity
      Given a new Agent connects Restish to the Realmroot OpenAPI contract
		When the Agent invokes enrollment with an explicit username, optional nickname, and its detected runtime
      Then the Restish authentication adapter registers locally generated host and Agent keys
      And Realmroot preserves the requested username without deriving it from another field
      And an omitted nickname defaults to the detected runtime while the Host is named after its local device
      And enrollment waits while an authorized controller approves the Agent once from the hosted enrollment page
      And the adapter creates a personal stable identity through the approved Agent session
      Then Realmroot creates an Agent with a stable issuer and subject
      And Realmroot assigns the requested immutable username independently of the Agent's nickname
      And the Agent belongs to exactly one concrete User
      And users govern the Agent through explicit Permissions in that space
      And the host registration is bound to that Agent identity
      And the enrollment operation returns the stable Agent enrollment
      And before the enrollment command returns the adapter resolves and durably stores the stable Agent issuer and subject
      And losing that successful response and retrying enrollment returns the same enrollment without creating another identity
      And the hosted approval page replaces the request with a clear completion state that says it can be closed
      And later OpenAPI operations reuse the Agent identity without another login command
      And whoami only reads the already established identity and never completes enrollment as a side effect
      And the adapter requests only the exact bootstrap scopes required by each operation
      And a configured Realmroot Resource credential source delegates published bootstrap scopes to that same Agent identity
      And enrollment alone grants no management or external API resource access
      And an unbound protocol registration cannot exercise Agent identity capabilities

    @entrypoint:restish @journey:application-agent-creation @proof:unit
    Scenario: An authorized Application creates a User-owned Agent directly
      Given an Application acts on behalf of a User and has agents:write authority
      When the Application creates an Agent with its public installation credential
      Then Realmroot creates one active stable Agent and its initial installation
      And the represented User owns and controls the stable Agent and installation
      And Realmroot returns the stable issuer and a UUID version 7 subject
      And every new identity, binding, and audit identifier is an unprefixed UUID version 7
      And no account approval or enrollment decision resource is created
      And retrying the same request with the same idempotency key returns the same Agent
      And a retry first completed before the UUID version 7 rollout returns that historical Agent and records its durable idempotency reservation
      And reusing the idempotency key with different Agent data is rejected
      And historical prefixed Agent identities remain readable without being generated again
      And the original self-enrollment interface and approval behavior are unchanged

    @entrypoint:restish @journey:agent-whoami-requires-enrollment @proof:unit
    Scenario: Agent identity inspection never creates an identity implicitly
      Given Restish has no local Realmroot Agent registration
      When the Agent invokes the generated whoami command
      Then the command fails with guidance to invoke the generated Agent enrollment command
      And Restish does not create keys, open a browser, or create an Agent identity

    @entrypoint:restish @journey:agent-single-cli-principal @proof:unit
    Scenario: Command-line operations always use the Agent principal
      Given an Agent has established a stable identity in Restish
      When the Agent invokes any operation discovered from the Realmroot OpenAPI contract
      Then Realmroot authenticates the request as that Agent identity
      And the Realmroot adapter is the explicit default for Realmroot oauth2 operations unless the caller selects a configured credential override
      And Realmroot never substitutes the approving user's identity for the Agent
      And the controller's browser session is used only to approve enrollment or authority
      And the CLI requests bootstrap authority with the reserved client_id realmroot-cli
      And Realmroot issues an RFC 9068 DPoP-bound bootstrap token with the stable Agent as subject
      And the bootstrap and Resource access tokens carry the runtime's raw session ID in the private Agent binding claim
      And the bootstrap token contains no actor claim or public Host claim
      And previously issued legacy Agent tokens remain accepted only until their existing expiry

    @entrypoint:product-ui @journey:agent-enrollment-denial @proof:unit
    Scenario: A controller can deny Agent enrollment
      Given an Agent enrollment request is pending
      When the authorized controller denies the request
      Then Realmroot resolves the enrollment as denied
      And the hosted approval page clearly says the request was denied and can be closed
      And the waiting Realmroot command exits without receiving an Agent identity

    @entrypoint:product-ui @journey:agent-management-authority @proof:unit
    Scenario: Agent control is established by one authoritative approval
      Given an unowned Agent requests enrollment for the authenticated User
      When that User approves the enrollment
      Then that concrete User becomes the Agent owner
      And only that owner can establish the stable Agent identity
      And later management requests authenticate as the Agent without substituting the controller
      And Realmroot records one authoritative approval outcome

    @entrypoint:agent-protocol @journey:agent-multi-host-continuity @proof:integration
    Scenario: One Agent identity can be used from independently secured hosts
      Given an Agent identity has an active host registration
      When the Agent client requests another enrollment for that stable Agent from a host with a different public key and an idempotency key
      Then Realmroot creates a pending Agent installation enrollment and returns its hosted approval URL
      And the Agent client can poll that installation enrollment through its canonical Agent protocol URI
      And retrying with the same idempotency key returns that same installation enrollment
      When an authorized controller approves the hosted enrollment
      Then both host registrations resolve to the same Agent issuer and subject
      And neither host receives the other host's private key

    @entrypoint:agent-protocol @journey:agent-host-revocation @proof:integration
    Scenario: Revoking one host does not revoke the Agent identity
      Given an Agent identity has two active host registrations
      When a controller revokes one host
      Then that host can no longer authenticate as the Agent
      And the other host and the Agent identity remain active

    @entrypoint:product-ui @journey:agent-identity-recovery @proof:integration
    Scenario: A controller replaces compromised Agent credentials without changing its subject
      Given an Agent's host credentials may be compromised
      When an authorized controller recovers the Agent
      Then every existing host credential and session is revoked
      And external API resource entitlements are frozen
      And the Agent becomes inactive without entering a recovering state
      And the Agent keeps the same issuer and subject

    @entrypoint:product-ui @journey:agent-identity-deletion @proof:integration
    Scenario: A soft-deleted Agent subject is never reassigned
      Given an Agent has a stable issuer and subject
      When an authorized controller deletes the Agent
      Then the Agent can no longer authenticate, receive grants, or be queried through an interface
      And its subject remains reserved for historical audit records
      And its username and installation identifiers remain reserved and return conflict when reused
      And no interface can restore it

    @entrypoint:agent-protocol @journey:agent-stable-issuer @proof:unit
    Scenario: Agent identity uses the deployment's existing OIDC issuer
      Given Realmroot is reached through a non-canonical request origin
      When a controller approves an Agent enrollment
      Then the Agent issuer is the Better Auth OIDC issuer
      And preview or request origins do not change the Agent issuer and subject
      And DPoP request binding and Agent links use only an origin allowed by TRUSTED_ORIGINS
      And hosted Agent approval URLs use the configured deployment origin
      And Realmroot does not publish a second Agent-only OIDC issuer

    @entrypoint:product-ui @journey:public-agent-profile @proof:unit
    Scenario: External visitors resolve a stable public Agent profile
      Given a non-deleted Agent has a stable issuer and subject
      When an external visitor requests the Agent by its stable subject or immutable username
      Then Realmroot returns the Agent's public identity
      And the public identity includes its immutable human-readable username
      And picture resolves to the Realmroot static file "/agent-picture-v1.svg" until the Agent has a custom picture
      And the default summary omits owner and activity
      And the full view includes the public owner, activity overview, annual heatmap, and sanitized recent activity
      And Agent configuration and OAuth authorization-server discovery publish the public Agent Profile URI template keyed by subject
      And browser clients can read issuer-path OAuth and OpenID discovery across origins
      And permits each view to be cached and revalidated independently
      But the public profile never returns Host, role, scope, grant, Resource, or authorization state
      And the public profile is never authoritative for authentication or authorization

  Rule: Resource Servers expose authorization details through one Agent access workflow

    @entrypoint:agent-protocol @journey:realmroot-built-in-resource-server @proof:unit
    Scenario: Realmroot exposes its own API as a system-managed Resource Server
      Given a Realmroot deployment has completed onboarding
      And its persisted system-managed scope registry predates the current Realmroot scope catalog
      When an Agent lists Resource Servers
      Then exactly one enabled native Resource Server represents that deployment's Realmroot API
      And its service URL and OAuth resource indicator use the deployment's canonical API URL
      And Realmroot reconciles its persisted scope registry to the current system-managed catalog
      And agents:write is granted automatically so authorized Applications may create a User's Agent without a direct User permission
      And refreshing that registry returns the same current catalog without an external network dependency
      And its account connection status is not-required
      And it cannot be disabled, soft-deleted, or reassigned through tenant management
      When the Agent lists that Resource Server's authorization details
      Then the built-in platform Organization, ordinary Organization, and personal User details reflect the controller tenant boundaries available for approval
      And the platform Organization detail supplies platform-wide management scopes
      And each Organization or User detail supplies only scopes valid for that tenant boundary
      And every controller can approve scopes only within the selected authorization detail boundary
      And a platform Organization credential retains only the Agent's automatic protocol scopes plus approved management scopes
      And a token for one authorization detail cannot authorize another authorization detail
      When the Agent reads Resource Servers with either bootstrap or resource-bound authority
      Then Realmroot returns the same canonical Resource Server representation
      And the credential authority changes only which Resource Servers the Agent may read or mutate

    @entrypoint:agent-protocol @journey:agent-resource-server-model @proof:unit
    Scenario: An Agent discovers Resource Servers before provider authorization details
      Given Realmroot has registered native and external Resource Servers
      When the Agent lists Resource Servers
      Then each item identifies one protected API service, its service URL, OAuth resource indicator, availability, and account connection status
      And the Agent-facing contract does not call a Resource Server an API Resource
      When the Agent lists one Resource Server's authorization details
      Then each item exposes one RFC 9396 authorization detail, flat safe display metadata, and a stable Context ID when its source defines one
      And each item separately reports account authorization status, Agent-authorized scopes, and requestable scopes
      And an Organization owner may approve current assigned scopes of a Resource Server owned by that Organization
      And native User and Organization Context IDs are their tenant IDs while external Context IDs come from the Resource Server catalog
      And an access request copies the selected authorization detail directly while the Context ID remains only its selector

    @entrypoint:agent-protocol @journey:external-resource-authorization-detail-catalog @proof:unit
    Scenario: An Agent discovers display-safe authorization details from an external Resource Server
      Given an external Resource Server's authorization server advertises an authorization-detail catalog endpoint
      And the Agent's controller has an active external Resource authorization
      When the Agent lists that Resource Server's authorization details
      Then Realmroot authenticates the catalog read with a refreshed external subject token
      And each connected detail uses the Resource Server supplied display name, description, and safe attributes
      And Realmroot reports the Agent's authorized and requestable scopes for that exact detail
      But the external provider credential is never returned to the Agent

    @entrypoint:agent-protocol @journey:agent-private-resource-server-visibility @proof:unit
    Scenario: A private Resource Server stays inside its owner Organization boundary
      Given a private Resource Server is owned by an Organization and available to Agents
      And a personal Agent's controller is an active member of that Organization
      When the Agent lists Resource Servers or that Resource Server's authorization details
      Then the private Resource Server is visible to that Agent
      But it remains hidden from Agents outside the owner Organization
      And discovery grants no Resource scope or credential

    @entrypoint:product-ui @journey:native-api-resource-registration @proof:integration
    Scenario: An administrator registers a native API that trusts Realmroot
      Given a product uses Realmroot as its OIDC provider and OAuth authorization server
      When an administrator creates an API resource with native authorization mode
      Then the administrator configures one protected resource URL
      And Realmroot uses that URL as the OAuth resource identifier and access-token audience
      And without a Provider Connector no external authorization server, OAuth client, or account connection is configured
      And the product API validates Realmroot access tokens with the published issuer and JWKS
      And the protected resource publishes its requestable scopes through RFC 9728 metadata
      And the protected resource advertises its OpenAPI contract with a standard service-desc link
      And Realmroot derives its local scope registry from that protected-resource metadata
      And scope registry refresh first refreshes dynamic connector metadata before validating provider compatibility
      And OpenAPI may add descriptions and maps operations only to advertised scopes
      And advertised scopes remain valid even when no public operation references them
      And Realmroot stores only discovered scope metadata and local grant modes, never either source document

    @entrypoint:product-ui @journey:api-resource-contract-validation @proof:unit
    Scenario: API resources require a discoverable OpenAPI contract
      Given an API resource URL cannot be reached or does not return a successful service-desc response
      When an administrator creates or enables the API resource, including a disabled registration
      Then Realmroot rejects the request without enabling the resource
      And a network failure identifies whether the resource or its OpenAPI document was unreachable
      When the administrator enables an existing draft or changes an enabled resource URL
      Then Realmroot validates the exact resource URL before saving the change

    @entrypoint:agent-protocol @journey:native-api-resource-access-request @proof:unit
    Scenario: An Agent requests access to a native API
      Given an enabled native API resource belongs to the Agent's home space
      When the Agent lists available resources
      Then Realmroot returns that resource and its protected resource URL without requiring an account connection
      When Restish reads the target OpenAPI operation and the Agent requests its Resource Server and exact scope set
      Then Realmroot validates that scope set against the local target scope registry
      And the request selects exactly one realmroot_authority Context for a User or Organization visible to the Agent
      And every visible native Resource lists a personal Agent's User Context and every active Organization Context
      And private Resource visibility is checked before that Context catalog is exposed
      And missing, multiple, malformed, or unavailable Contexts fail closed
      And before creating or resuming a pending request Realmroot verifies the controller currently holds every requested scope inside only the selected Context
      And a scope outside that boundary is rejected without creating an approval request or notification
      And the server returns HTTP 400 with the existing "bad_request" error code, a message, and the selected Context and offending scopes in error details
      And User Context scopes include only automatic scopes and direct Permissions with no Organization
      And Organization Context scopes include only automatic scopes, matching Organization direct Permissions, and matching membership Roles
      And NULL direct Permissions never fall back to the Resource owner Organization
      And direct and Role scopes from another Organization do not cross that Context boundary
      And Realmroot creates the same pending access-request resource used for external APIs
      And it does not require a user-created authority grant or grant identifier
      When an authorized controller approves the request
      Then the approval preserves the exact requested authorization details without an Account Connection
      Then Realmroot creates the same per-scope Permissions used for external APIs

    @entrypoint:agent-protocol @journey:native-api-automatic-agent-permission @proof:unit
    Scenario: A personal Agent requests only automatic scopes from a native API
      Given an enabled native API resource marks every requested scope as automatic
      And the personal Agent selects a current Realmroot authority Context controlled by its owner User
      When the Agent requests that exact scope set
      Then Realmroot approves the access request without controller interaction
      And Realmroot creates persistent per-scope Agent Permissions through the normal approval path
      And any assigned scope keeps the access request pending for controller approval
      But an unavailable Context is rejected without creating a request or Permission

    @entrypoint:agent-protocol @journey:agent-resource-discovery-isolation @proof:unit
    Scenario: An unavailable API resource does not block resource discovery
      Given multiple enabled API resources are visible to an Agent
      And one resource cannot publish its current OpenAPI contract
      When the Agent lists available resources
      Then Realmroot returns the resource with unavailable status and no requestable scopes
      And returns every available resource with available status and its current requestable scopes

  @entrypoint:agent-protocol @journey:agent-resource-access-without-role @proof:unit
    Scenario: An Agent requests resource access without a Role model
      Given an enabled API resource publishes the requested assigned scope in its local registry
      When the Agent requests that exact scope
      Then Realmroot allows the access request to proceed to controller approval
      And the controller may approve only scopes within the controller's effective scope set
      And the approved request stores the exact Permission snapshot without a roles claim

    @entrypoint:agent-protocol @journey:native-api-resource-token @proof:unit
    Scenario: An Agent calls a native API directly
      Given a controller approved an exact native API resource request
      When Restish accepts the approved access request's credential offer
      Then the Realmroot plugin creates and retains a separate DPoP key
      And the plugin sends the DPoP proof in the standard DPoP header
      Then Realmroot issues a short-lived audience-bound JWT access token
      And the token uses the Better Auth issuer and signing keys
      And the token identifies the controller as subject and the stable Agent as the RFC 8693 actor
      And the Agent actor carries only its issuer and stable subject
      And the Realmroot-issued token identifies the presenting CLI with client_id realmroot-cli
      And the Host remains internal credential, binding, revocation, and audit context
      And the approved access request remains an audit record of the scopes approved in that interaction
      And its credential offer exposes the Context's one current cumulative scope set
      And the token carries every currently active Permission for the same Agent, Resource Server, Account Connection, and authorization details
      And a later expansion replaces the client's Context credential instead of creating a selectable historical offer
      And revoking one Permission removes only that scope from subsequently issued tokens
      And an Organization Context token carries only that selected Organization claim and groups
      And the same personal Agent receives different Organization claims when it selects different Organization Contexts
      And a User Context token has no Organization claim, regardless of the Agent home Organization or Resource owner
      And the token does not contain Agent roles
      And the token is bound to the Agent's DPoP key
      And Restish stores but does not print the raw access token
      When the Agent connects Restish to the discovered protected resource URL
      Then Restish discovers the target OpenAPI contract from its standard service-desc link
      When the Agent invokes a generated target operation
      Then the plugin sends the access token and a fresh DPoP proof directly to the product API
      And the product API validates the token type, signature, issuer, audience, expiry, scopes, and DPoP binding

    @entrypoint:agent-protocol @journey:agent-resource-entitlement-policy @proof:unit
    Scenario: Both API authorization modes enforce the same Permissions
      Given an Agent requests a target token for an API resource
      When no active Permissions permit the Agent, resource, scopes, and lifetime
      Then Realmroot denies the request
      And the Agent cannot substitute another account connection or resource
      When active Permissions permit the request
      Then the token issuer is selected only from the API resource authorization mode
      And one-time, limited, persistent, revocation, and audit behavior is consistent across both modes

  Rule: Workload token exchange preserves authorization boundaries

    @entrypoint:agent-protocol @journey:agent-oidc-id-token-exchange @proof:unit
    Scenario: A native Resource Server exchanges an Agent token for an OIDC ID token
      Given a confidential Application has an explicit source Resource Server to target OIDC Application policy
      And the target is an active private OIDC Application in the same Organization
      When the Application exchanges an active Agent access token issued for the configured source Resource Server
      Then Realmroot issues a short-lived ID token for the target Application client ID
      And the token preserves the controller User in sub and the stable Agent in act
      And current Organization Team names are emitted as groups without source Resource scopes or a refresh token
      But an inactive source token, Agent, controller, membership, source, target, or missing policy is rejected and audited

    @entrypoint:agent-protocol @journey:user-resource-token-delegation @proof:unit
    Scenario: A Resource Server exchanges an inbound User token for a narrower downstream token
      Given a confidential Application has an explicit source-to-target Resource Server scope mapping
      And the Application is entitled to the requested scopes on a downstream Resource Server
      And the User is entitled to the mapped scopes on that downstream Resource Server
      When the Application exchanges a valid Realmroot access token whose audience is the configured source Resource Server
      Then Realmroot issues a short-lived access token for the downstream Resource Server
      And its scopes are the intersection of the request, current source mapping, Application Permissions, and User Context Permissions
      And the downstream token preserves the User subject and identifies the authenticated Application as client_id
      And the downstream token contains no act claim and no refresh token is issued
      But an unmapped source or target, an empty scope intersection, or an unavailable target is rejected

    @entrypoint:agent-protocol @journey:agent-resource-token-delegation @proof:unit
    Scenario: A Resource Server exchanges an inbound Agent token without losing its actor chain
      Given a confidential Application has an explicit source-to-native-target Resource Server scope mapping
      And an active Agent token, identity binding, and controller authorize the source Resource Server in a personal or Organization Context
      And the Application and controller are entitled to the requested downstream scopes
      When the Application exchanges that Agent token for the configured downstream Resource Server
      Then Realmroot applies the same request, source mapping, Application Permission, and controller Context intersection
      And the downstream token preserves the controller User in sub and the stable Agent in act
      And the authenticated Application is identified as client_id without issuing a refresh token
      And a short-lived delegated Agent token may continue through another explicitly mapped Application and Resource Server hop
      But an inactive token lease, Agent, binding, controller, Organization membership when present, source, target, external target, or policy is rejected

    @entrypoint:product-ui @journey:connector-backed-connection-revocation @proof:unit
    Scenario: Revoking a connector-backed account connection revokes provider authority first
      Given an active connector-backed account connection has provider access and refresh tokens
      When its controller revokes the connection
      Then Realmroot revokes the provider refresh token and access token before ending Permissions, Token Leases, and local connection state
      But a temporary provider failure leaves the local connection active and returns an explicit retryable failure

    @entrypoint:agent-protocol @journey:workload-token-exchange-claims @proof:unit
    Scenario: Introspection reports only authorization-server controlled security claims
      Given a trusted workload assertion contains untrusted private claims
      When Realmroot exchanges and introspects its RFC 9068 JWT access token
      Then the JWT identifies the trusted external workload as the RFC 8693 subject and the authenticated Application as client_id
      And Realmroot's workload profile accepts no actor_token and the JWT contains no act claim
      And issuer, audience, client, scope, activity, token type, and lifetime come only from Realmroot
      And subject assertion claims cannot override introspection security fields
      And only the confidential client that owns the exchange can introspect the token
      And previously issued opaque exchange access tokens remain introspectable until they expire

    @entrypoint:agent-protocol @journey:workload-refresh-security @proof:unit
    Scenario: Token-exchange refresh tokens are confidential, rotating, and revocable
      Given a confidential client received a token-exchange refresh token
      When it refreshes with valid client authentication and an enabled federated credential
      Then Realmroot rotates the refresh token and invalidates the previous value
      When the old token is replayed or the federated credential is disabled or deleted
      Then Realmroot rejects the refresh with invalid_grant
      And disabling the client or rotating its secret also prevents refresh

  Rule: External API resources use target-issued authorization

    @entrypoint:product-ui @journey:resource-account-connection @proof:unit
    Scenario: A controller connects an external resource account securely
      Given an external Resource Server requires a connected account
      When the controller starts and completes its authorization
      Then Realmroot uses authorization code flow with PKCE and explicit consent
      And the authorization request identifies the exact target Resource Server
      And Realmroot stores the resulting credentials only in encrypted custody
      And an unavailable or mismatched authorization server fails without creating a connection

    @entrypoint:product-ui @journey:linear-managed-workspace-connections @proof:unit
    Scenario: One Linear Connector independently supports sign-in and external resource authorization
      Given the Linear Connector uses Better Auth for authentication and the Linear Adapter as its external authorization issuer
      When a user signs in with Linear and authorizes Linear resource access
      Then sign-in creates only its Better Auth account link
      And resource authorization creates one external connection to the Linear Adapter
      And the Linear Adapter keeps provider credentials outside Realmroot
      And available workspaces are selected through authorization details instead of additional Realmroot connections
      And Realmroot exchanges the connected subject and Agent actor at the Linear Adapter
      And the Linear Adapter issues the final DPoP token used for its Resource Server

    @entrypoint:product-ui @journey:adapter-external-resource-authorization @proof:unit
    Scenario: An Adapter presents a non-standard provider as a standard External Resource Server
      Given the provider cannot issue the Agent token required by Realmroot
      And its Adapter publishes a standard authorization server and protected Resource Server
      When the controller connects the provider through the Connector's resource-authorization facet
      Then the Adapter completes provider authorization without exposing provider credentials to Realmroot
      And Realmroot stores one standard external connection issued by the Adapter
      And Realmroot requests an advertised OIDC profile scope so the connection keeps a readable Provider label
      When an Agent receives access through that connection
      Then Realmroot sends the connected subject, Agent actor, scopes, and authorization details through standard token exchange
      And the Adapter issues a DPoP token bound to the Agent and selected provider authority
      And reconnecting updates the external connection instead of creating another Realmroot connection

    @entrypoint:product-ui @journey:external-api-resource-registration @proof:unit
    Scenario: An administrator creates an external API resource with an OIDC connector
      Given a target resource publishes protected-resource and authorization-server metadata
      And a platform-managed standard OIDC connector exists for its authorization server
      And Realmroot can discover that connector through OIDC or RFC 8414 authorization-server metadata
      When a member with the required platform Organization scopes creates the API resource and selects that connector
      Then Realmroot validates the resource issuer, token exchange, DPoP, and revocation against the connector
      And the external Resource Server is owned by the built-in platform Organization
      And ordinary Organizations cannot register or take ownership of it
      And the resource URL advertises its OpenAPI contract with a standard service-desc link
      And Realmroot derives every requestable scope only from scopes_supported in that protected-resource metadata
      And the OpenAPI contract may add descriptions for advertised scopes
      And Realmroot publishes only operation security alternatives fully supported by those advertised scopes
      And unrelated scoped operations do not prevent resource synchronization
      And authorization-server scopes_supported is not a scope catalog
      And the resource stores only its connector association rather than another OAuth client
      And the resource cannot be enabled for Agents when a required capability is absent
      And the same connector can independently be enabled for Realmroot login

    @entrypoint:restish @journey:external-api-resource-reconfiguration @proof:integration
    Scenario: Changing an external API resource URL revalidates its connector boundary
      Given an external API resource is associated with an active OIDC connector
      When an administrator changes its resource URL or selects another OIDC connector
      Then Realmroot rediscovers the target metadata
      And the resource remains enabled only when its authorization server matches the associated connector
      And the resource cannot remove its connector or become natively authorized

    @entrypoint:restish @journey:external-api-resource-canonical-callback @proof:unit
    Scenario: OIDC connector registration uses the deployment's canonical callbacks
      Given Realmroot is reached through a non-canonical request origin
      When an administrator dynamically registers an OIDC connector
      Then its login and resource-account redirect URIs and JWKS URI use the configured deployment origin
      And a later Account Center authorization request uses that same redirect URI
      And a successful resource-account callback shows completion even when origin-scoped session storage is unavailable

    @entrypoint:agent-protocol @journey:external-resource-dynamic-client-scope-upgrade @proof:unit
    Scenario: A dynamic OIDC connector upgrades its registered scope authority
      Given an authorization server advertises scopes that were not registered by an existing dynamic connector
      When a controller expands an external resource account for one of those scopes
      Then Realmroot updates the existing client through its registration management endpoint when available
      And otherwise Realmroot registers a new client generation without invalidating connections pinned to the previous generation
      And the connection intent is pinned to the new client generation
      And same-subject reauthorization preserves the selected account connection identity and switches only that connection to the new generation

    @entrypoint:product-ui @journey:external-resource-rich-authorization-connection @proof:unit
    Scenario: A controller connects one external subject to multiple target contexts
      Given an authorization server advertises RFC 9396 authorization detail types
      And an external API resource configures opaque connection authorization detail templates using supported types
      When Realmroot dynamically registers its reusable OIDC connector
      Then the registration declares the authorization detail types that the connector can use
      When the controller authorizes the resource account
      Then Realmroot sends the complete authorization request including the configured authorization details
      And uses RFC 9126 pushed authorization requests when the authorization server advertises that optional endpoint
      When the target consent enriches one template into multiple granted contexts
      Then Realmroot requires and stores every returned authorization detail under the single account connection
      And refresh-token rotation preserves the granted authorization details
      And unknown types or malformed authorization details fail with invalid_authorization_details

    @entrypoint:agent-protocol @journey:external-resource-rar-without-catalog @proof:unit
    Scenario: Rich authorization does not require an enumerable resource catalog
      Given an authorization server supports RFC 9396 authorization details
      And it does not advertise Realmroot's optional authorization detail catalog extension
      When an administrator registers an external API resource with supported authorization detail templates
      Then Realmroot accepts the resource without inventing a catalog requirement
      And Agent approval can select the concrete authorization details already returned by that account connection
      And Agents can request exact details already exposed by their connected account

    @entrypoint:agent-protocol @journey:external-resource-contextual-delegation @proof:unit
    Scenario: An Agent delegates an exact external-resource context alongside scopes
      Given one external account connection grants multiple opaque authorization detail entries
      And the authorization server advertises Realmroot authorization detail catalog version 1 with an account-scoped endpoint and required scope
      When the Agent discovers that catalog through Realmroot
      Then Realmroot forwards pagination and returns each available detail with safe display metadata and connection authorization
      And each detail reports only its Agent-authorized and requestable scope sets
      And provider-reported scope reductions immediately remove stale account and Agent authority from that detail
      And Realmroot does not expose account connection identifiers, grant identifiers, grants, or tokens
      When the Agent requests an exact scope subset and one or more concrete connected authorization details
      Then Realmroot preserves that exact authorization boundary through hosted approval
      And rejects missing, generic, duplicate, or unconnected authorization details
      And the pending request and controller approval preserve both authority dimensions
      And an ungranted entry or browser-tampered approval fails with invalid_authorization_details
      When the controller approves the request and the Agent exchanges a token
      Then Realmroot sends the approved scopes and authorization details to the target authorization server
      And requires the target token response to return the exact assigned scopes and authorization details
      And stores both dimensions with the token lease
      And audit events expose only safe authorization detail type and identifier projections

    @entrypoint:product-ui @journey:external-resource-rich-authorization-reauthorization @proof:unit
    Scenario: Reauthorization removes stale contextual authority without changing non-RAR resources
      Given an existing external resource account has active contextual Agent grants
      When reauthorization no longer returns one previously granted authorization detail entry
      Then Realmroot prevents future issuance from every grant containing the removed entry
      And existing connections must be explicitly reauthorized when their resource becomes RAR-required
      And resources without configured authorization details preserve their existing connection, refresh, grant, token exchange, revocation, and audit behavior

    @entrypoint:agent-protocol @journey:external-resource-first-access @proof:unit
    Scenario: An Agent requests first access to an external API resource
      Given an enabled external API resource has active authorization configuration
      And the Agent's home space has no account connection for that resource
      When the Agent discovers every target operation required by the current task
      And requests their combined exact advertised scope set
      Then Realmroot creates one pending Agent access request and one hosted approval URL
      When the controller opens the access approval page
      Then Realmroot requires the controller to connect that resource account in the same approval flow
      And the new account authorization requests the access request's exact scope set
      When OAuth returns after connecting the account
      Then Realmroot returns to the same access approval with that account displayed
      When the authorization server instead returns an OAuth error
      Then Realmroot consumes the failed attempt and returns to the same access approval with the provider error and a retry action
      Then Realmroot records a resource account connection owned by the Agent's home space
      And stores its refresh credential encrypted
      And never exposes the refresh credential through an API, audit event, or error
      And does not create a grant or token before approval
      When the controller selects one connected authorization context when the resource requires one
      And approves the exact Agent scopes and grant lifetime
      Then Realmroot binds the account connection to the exact request and grant
      And the Agent can obtain a DPoP-bound target access token

    @entrypoint:product-ui @journey:resource-account-reauthorization @proof:unit
    Scenario: A controller reauthorizes a connected external resource account
      Given the controller's home space already has an account connection for an external API resource
      And a pending Agent access request requires scopes that its selected authorization detail does not yet cover
      When the controller opens the approval page
      Then Realmroot presents account permission update as the only available action
      And hides Agent approval controls until the account covers every requested scope
      But a failed authorization-detail catalog lookup reports that failure without presenting account permission update
      When the controller reauthorizes that account for the union of its existing scopes and the pending Agent request's exact scope set
      And OAuth returns the same external subject with replacement credentials and scopes
      Then Realmroot preserves the account connection identity
      And replaces its encrypted credentials, scopes, display name, and expiry
      And treats the callback authorization details as authoritative so removed details invalidate uncovered Agent grants
      And restores the connection when it was previously revoked
      And returns to the pending Agent approval so the controller can finish the continuous flow

    @entrypoint:agent-protocol @journey:resource-account-connection-expansion @proof:unit
    Scenario: An Agent requests additional authority from an existing resource account
      Given the Agent's home space has an active resource account connection with covered persistent grants
      When the Agent requests an additional scope for one selected authorization detail
      Then Realmroot leaves the account connection revision, authorization details, and grants unchanged while approval is pending or interrupted
      When the controller starts account reauthorization
      Then Realmroot requests the union of the account's still-advertised resource scopes and the Agent's additional scope
      And sends only the selected authorization detail to the external authorization server
      And Realmroot adds provider protocol scopes only after validating that resource scope union
      And the external authorization server returns the connection's complete current authorization-detail snapshot
      And Realmroot accepts the selected authorization detail as a subset of that snapshot
      And only a successful OAuth callback may replace the account authorization and invalidate grants the complete snapshot no longer covers

    @entrypoint:agent-protocol @journey:agent-resource-discovery @proof:unit
    Scenario: An Agent discovers resource connection and scope status before requesting exact authority
      Given enabled native and externally authorized API resources exist
      When the Agent lists available resources
      Then Realmroot returns enabled resources even when an external resource has no connected account
      And a temporarily unreachable external authorization server does not fail the Resource Server collection or revoke its account connection
      And returns each resource server with its protected URL, available scopes, and one connected, not-connected, or not-required account status
      And a connected account reports only its safe display label and still-advertised connection-authorized scopes
      And Realmroot does not expose Connector, account connection, grant, or token identifiers
      When Restish connects directly to a candidate resource and reads the target OpenAPI operation
      And the Agent requests an account and its exact scope set without an applicable grant
      Then Realmroot validates that scope set against the local target scope registry
      And automatic scopes do not apply to Agents
      And the connected account permits every requested scope
      Then Realmroot creates one pending access request and returns a hosted approval URL
      And it does not require a pre-existing Agent resource grant

    @entrypoint:agent-protocol @journey:agent-resource-access-ensure @proof:unit
    Scenario: An Agent ensures exact resource access without selecting grants or tokens
      Given an Agent names an API resource, exact authorization details, and least-privilege scopes
      When the Agent requests that exact access
      Then Realmroot resolves the unique account connection from the Agent's home space and API resource
      Then Realmroot reuses an exact active grant without controller approval when one exists
      And otherwise creates or resumes one pending controller approval
      And the public access-request contract never exposes grant identifiers or token operations
      And the Restish plugin resolves approved access through a hidden adapter boundary
      And the plugin obtains, protects, and activates a short-lived DPoP target token
      And the Agent never selects a grant or invokes a token operation
      And the plugin returns a safe ready receipt without grant identifiers or token material

    @entrypoint:product-ui @journey:agent-resource-approval @proof:unit
    Scenario: A controller decides an Agent resource request in one step
      Given an Agent resource access request is pending
      When an authorized controller approves it
      Then the controller confirms the named Agent, named resource, displayed resource account, exact scopes, and one-time, limited, or persistent mode
      And the Account Center request queue identifies the named Agent and named resource before the controller opens the decision
      And expired requests do not appear in the Account Center request queue
      And stable Agent and resource identifiers remain visible as supporting information
      And limited access accepts an exact future local date and time while rejecting empty or past values
      And no account-selection control is displayed
      And scope expansion, another account, or another resource requires a new approval
      And a denied request cannot issue a target token
      And an incomplete approval URL shows only a recovery state without inactive authorization controls

    @entrypoint:product-ui @journey:agent-resource-approval-sign-in @proof:unit
    Scenario: A signed-out controller signs in without losing the Agent approval
      Given an Agent resource access request is pending
      And the controller is signed out
      When the Realmroot CLI opens the hosted approval URL
      And the controller signs in
      Then Realmroot returns to the same approval without exposing its token to the server callback URL
      And the controller can approve or deny the request

    @entrypoint:agent-protocol @journey:agent-direct-resource-access @proof:unit
    Scenario: An Agent calls an external API directly with a target-issued token
      Given a controller approved an exact external API resource request
      When the Realmroot CLI completes the Agent's exact access request
      Then the Realmroot CLI creates and retains a separate DPoP key
      And the CLI sends a standard DPoP header bound to the target token endpoint
      Then Realmroot submits a signed Agent assertion with the RFC 7523 JWT bearer grant
      And the target platform issues an Agent access token
      And Realmroot exchanges the connected user's subject token and the target-issued Agent access token with RFC 8693
      And the target platform issues a short-lived DPoP-bound access token
      And the token identifies the target user as subject and the Agent in the RFC 8693 actor claim
      And the target preserves the Agent issuer, subject, and ai_agent subject profile
      And no Realmroot-specific metadata, grant type, token type, or claim is required
      And Realmroot returns no refresh token
      And Restish stores but does not print the raw access token
      When the Agent connects Restish to the discovered protected resource URL
      Then Restish discovers the target OpenAPI contract from its standard service-desc link
      When the Agent invokes a generated target operation
      Then the plugin sends the access token and a fresh DPoP proof directly to the target platform
      And no Realmroot egress or credential injection endpoint exists

    @entrypoint:agent-protocol @journey:agent-resource-revocation @proof:unit
    Scenario: Revocation stops direct external API access
      Given an Agent has an active target token
      When a controller revokes its grant, account connection, credential, or Agent
      Then Realmroot calls the target revocation endpoint for active target tokens
      And subsequent token requests are rejected
      And unrelated Agents, accounts, and grants remain active

  Rule: Controllers and administrators can govern Agent activity

    @entrypoint:product-ui @journey:agent-governance-surfaces @proof:unit
    Scenario: Agent management follows ownership and platform boundaries
      Given User-owned Agents exist
      When an authorized controller opens Agent management
      Then Account Center presents the current User's Agents
      And Organization membership does not grant Agent ownership or control
      And Console presents User-owned inventory, audit, and emergency revocation

    @entrypoint:product-ui @journey:agent-audit-chain @proof:unit
    Scenario: Audit records reconstruct an Agent authorization decision
      Given an Agent host attempts to request external API resource authority
      When Realmroot allows or denies the request
      Then the audit record identifies the controller authority, resource account, Agent, host, grant, scopes, and result
      And it excludes credentials, authorization headers, and complete request or response bodies

    @entrypoint:product-ui @journey:agent-governance-audit @proof:unit
    Scenario: Agent identity and management authority changes remain auditable
      Given an Agent identity is governed by a controller or administrator
      When the Agent is enrolled, recovered, retired, or receives a capability decision
      Then Realmroot records the action, result, controller, stable Agent identity, host, and affected capabilities
      And the audit event contains no host credential, session token, or approval code

  @journey:direct-agent-permission @entrypoint:restish @proof:unit
  Scenario: A controller grants an Agent permission before its first request
    Given an active Agent and a Resource within its controller's authority
    When the controller posts the resource URL, scopes, and a persistent lifetime
    Then Realmroot resolves the controller's connection and authorization Contexts internally
    And Realmroot stores the permissions without creating an access request
    And repeated grants reuse the existing permission
    And a later Agent request reuses that permission without human approval
    And scopes outside the controller or connected account boundary are rejected

  @journey:direct-agent-permission-http @entrypoint:restish @proof:integration
  Scenario: Direct permissions preserve ownership and persistence through HTTP
    Given a user controls an active Agent
    When the user creates the same native permission twice through HTTP
    Then both responses identify the same persistent permission without an access request
    And anonymous and other-user writes are rejected
    And invalid permission bodies are rejected
    And equivalent Contexts reuse existing permissions regardless of JSON object key order