fix(vv): resolve all 6 V&V issues — field trial unblocked
All findings from the inaugural LeadValidator audit resolved and confirmed. Release gate: PASS. VV_ISSUE_002 (BLOCKER): 15 OpenAPI specs verified present covering all 20 route groups (46 endpoints documented in docs/openapi/) VV_ISSUE_003 (MAJOR): Remove any types from src/db/pool.ts — replaced pool.query shim with unknown[] + Object.defineProperty, zero any types, eslint-disable suppressions removed VV_ISSUE_004 (MAJOR): Remove raw Pool from ScaffoldController and HealthDetailedController — injected AgentRepository/CredentialRepository and DbProbe interface respectively; added CredentialRepository.findActiveClientId() VV_ISSUE_005 (MAJOR): Add unit tests for 5 untested services — ComplianceStatusStore, EventPublisher, MarketplaceService, OIDCTrustPolicyService, UsageService VV_ISSUE_006 (MAJOR): Add integration tests for 7 missing route groups — analytics, billing, tiers, webhooks, marketplace, oidc-trust-policies, oidc-token-exchange VV_ISSUE_001 (MINOR): Create missing design.md and tasks.md in 4 OpenSpec archives — all archives now complete Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
480
docs/openapi/delegation.yaml
Normal file
480
docs/openapi/delegation.yaml
Normal file
@@ -0,0 +1,480 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
title: SentryAgent.ai — A2A Delegation (Agent-to-Agent)
|
||||
version: 1.0.0
|
||||
description: |
|
||||
Agent-to-Agent (A2A) delegation endpoints for the SentryAgent.ai AgentIdP platform.
|
||||
|
||||
The delegation subsystem enables an authenticated agent (the *delegator*) to grant
|
||||
a subset of its own scopes to another agent (the *delegatee*) for a limited time.
|
||||
This creates a cryptographically-signed delegation chain, suitable for multi-agent
|
||||
orchestration patterns.
|
||||
|
||||
**All endpoints require a valid Bearer JWT.**
|
||||
|
||||
**Feature flag:** When `A2A_ENABLED=false` these routes are not registered (return 404).
|
||||
|
||||
**Delegation rules:**
|
||||
- The delegatee must be in the same tenant as the delegator.
|
||||
- Delegated scopes must be a strict subset of the delegator's own scopes.
|
||||
- TTL minimum: 60 seconds; maximum: 86400 seconds (24 hours).
|
||||
- Each delegation chain has a unique `chainId` (UUID).
|
||||
- Revoking a chain is idempotent — revoking an already-revoked chain succeeds.
|
||||
|
||||
servers:
|
||||
- url: http://localhost:3000/api/v1
|
||||
description: Local development server
|
||||
- url: https://api.sentryagent.ai/v1
|
||||
description: Production server
|
||||
|
||||
tags:
|
||||
- name: A2A Delegation
|
||||
description: Agent-to-Agent delegation chain management
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
BearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
description: |
|
||||
JWT access token obtained via `POST /token`.
|
||||
Include as `Authorization: Bearer <token>`.
|
||||
|
||||
schemas:
|
||||
DelegationChain:
|
||||
type: object
|
||||
description: A delegation chain record as returned by the API.
|
||||
required:
|
||||
- id
|
||||
- tenantId
|
||||
- delegatorAgentId
|
||||
- delegateeAgentId
|
||||
- scopes
|
||||
- delegationToken
|
||||
- ttlSeconds
|
||||
- issuedAt
|
||||
- expiresAt
|
||||
- createdAt
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Unique identifier of the delegation chain.
|
||||
readOnly: true
|
||||
example: "chain-abcd-1234-5678-ef01"
|
||||
tenantId:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Organization (tenant) that owns this delegation.
|
||||
readOnly: true
|
||||
example: "org-1234-5678-abcd-ef01"
|
||||
delegatorAgentId:
|
||||
type: string
|
||||
format: uuid
|
||||
description: UUID of the agent granting authority.
|
||||
example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
delegateeAgentId:
|
||||
type: string
|
||||
format: uuid
|
||||
description: UUID of the agent receiving delegated authority.
|
||||
example: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
|
||||
scopes:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: OAuth 2.0 scopes granted by this delegation chain.
|
||||
example:
|
||||
- "agents:read"
|
||||
delegationToken:
|
||||
type: string
|
||||
description: |
|
||||
Opaque delegation token string that the delegatee presents to verify authority.
|
||||
This token encodes the chain metadata and is HMAC-signed.
|
||||
example: "chain-abcd-1234-5678-ef01.1743151200.1743237600"
|
||||
signature:
|
||||
type: string
|
||||
description: HMAC-SHA256 signature of the delegation token payload.
|
||||
example: "3a7f2b9c..."
|
||||
ttlSeconds:
|
||||
type: integer
|
||||
description: Delegation lifetime in seconds.
|
||||
minimum: 60
|
||||
maximum: 86400
|
||||
example: 3600
|
||||
issuedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
readOnly: true
|
||||
example: "2026-04-07T09:00:00.000Z"
|
||||
expiresAt:
|
||||
type: string
|
||||
format: date-time
|
||||
readOnly: true
|
||||
example: "2026-04-07T10:00:00.000Z"
|
||||
revokedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
description: Timestamp when this chain was revoked. Null if still active or expired naturally.
|
||||
readOnly: true
|
||||
example: null
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
readOnly: true
|
||||
example: "2026-04-07T09:00:00.000Z"
|
||||
|
||||
CreateDelegationRequest:
|
||||
type: object
|
||||
description: Request body for creating a new agent-to-agent delegation chain.
|
||||
required:
|
||||
- delegateeAgentId
|
||||
- scopes
|
||||
- ttlSeconds
|
||||
properties:
|
||||
delegateeAgentId:
|
||||
type: string
|
||||
format: uuid
|
||||
description: |
|
||||
UUID of the agent to receive delegated authority.
|
||||
Must be in the same tenant as the delegator (caller).
|
||||
example: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
|
||||
scopes:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: |
|
||||
Scopes to delegate. Must be a strict subset of the delegator's current token scopes.
|
||||
At least one scope must be specified.
|
||||
minItems: 1
|
||||
example:
|
||||
- "agents:read"
|
||||
ttlSeconds:
|
||||
type: integer
|
||||
description: Delegation lifetime in seconds. Minimum: 60; Maximum: 86400 (24 hours).
|
||||
minimum: 60
|
||||
maximum: 86400
|
||||
example: 3600
|
||||
|
||||
VerifyDelegationRequest:
|
||||
type: object
|
||||
description: Request body for verifying a delegation token.
|
||||
required:
|
||||
- delegationToken
|
||||
properties:
|
||||
delegationToken:
|
||||
type: string
|
||||
description: The delegation token string to verify.
|
||||
example: "chain-abcd-1234-5678-ef01.1743151200.1743237600"
|
||||
|
||||
DelegationVerificationResult:
|
||||
type: object
|
||||
description: |
|
||||
Result of verifying a delegation token.
|
||||
Returns `valid: false` for expired or revoked tokens without throwing.
|
||||
required:
|
||||
- valid
|
||||
- chainId
|
||||
- delegatorAgentId
|
||||
- delegateeAgentId
|
||||
- scopes
|
||||
- issuedAt
|
||||
- expiresAt
|
||||
properties:
|
||||
valid:
|
||||
type: boolean
|
||||
description: Whether the delegation token is currently valid (active, not expired, not revoked).
|
||||
example: true
|
||||
chainId:
|
||||
type: string
|
||||
format: uuid
|
||||
description: UUID of the delegation chain.
|
||||
example: "chain-abcd-1234-5678-ef01"
|
||||
delegatorAgentId:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
delegateeAgentId:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
|
||||
scopes:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example:
|
||||
- "agents:read"
|
||||
issuedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
example: "2026-04-07T09:00:00.000Z"
|
||||
expiresAt:
|
||||
type: string
|
||||
format: date-time
|
||||
example: "2026-04-07T10:00:00.000Z"
|
||||
revokedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
example: null
|
||||
|
||||
ErrorResponse:
|
||||
type: object
|
||||
description: Standard error response envelope.
|
||||
required:
|
||||
- code
|
||||
- message
|
||||
properties:
|
||||
code:
|
||||
type: string
|
||||
example: "DELEGATION_NOT_FOUND"
|
||||
message:
|
||||
type: string
|
||||
example: "Delegation chain with the specified ID was not found."
|
||||
details:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
|
||||
responses:
|
||||
Unauthorized:
|
||||
description: Missing or invalid Bearer token.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "UNAUTHORIZED"
|
||||
message: "A valid Bearer token is required to access this resource."
|
||||
|
||||
Forbidden:
|
||||
description: Valid token but insufficient permissions.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "FORBIDDEN"
|
||||
message: "You do not have permission to perform this action."
|
||||
|
||||
NotFound:
|
||||
description: Delegation chain not found.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "DELEGATION_NOT_FOUND"
|
||||
message: "Delegation chain with the specified ID was not found."
|
||||
|
||||
InternalServerError:
|
||||
description: Unexpected server error.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "INTERNAL_SERVER_ERROR"
|
||||
message: "An unexpected error occurred. Please try again later."
|
||||
|
||||
security:
|
||||
- BearerAuth: []
|
||||
|
||||
paths:
|
||||
/oauth2/token/delegate:
|
||||
post:
|
||||
operationId: createDelegation
|
||||
tags:
|
||||
- A2A Delegation
|
||||
summary: Create an A2A delegation chain
|
||||
description: |
|
||||
Creates a new agent-to-agent delegation chain. The authenticated agent
|
||||
(the *delegator*) grants a subset of its own scopes to the `delegateeAgentId`.
|
||||
|
||||
A cryptographically-signed `delegationToken` is returned. The delegatee
|
||||
presents this token to `POST /oauth2/token/verify-delegation` to prove
|
||||
delegated authority.
|
||||
|
||||
**Validation:**
|
||||
- Delegatee must be in the same organization as the delegator.
|
||||
- `scopes` must be a strict subset of the delegator's current token scopes.
|
||||
- `ttlSeconds` must be between 60 and 86400.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CreateDelegationRequest'
|
||||
example:
|
||||
delegateeAgentId: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
|
||||
scopes:
|
||||
- "agents:read"
|
||||
ttlSeconds: 3600
|
||||
responses:
|
||||
'201':
|
||||
description: Delegation chain created successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DelegationChain'
|
||||
example:
|
||||
id: "chain-abcd-1234-5678-ef01"
|
||||
tenantId: "org-1234-5678-abcd-ef01"
|
||||
delegatorAgentId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
delegateeAgentId: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
|
||||
scopes:
|
||||
- "agents:read"
|
||||
delegationToken: "chain-abcd-1234-5678-ef01.1743151200.1743154800"
|
||||
signature: "3a7f2b9c..."
|
||||
ttlSeconds: 3600
|
||||
issuedAt: "2026-04-07T09:00:00.000Z"
|
||||
expiresAt: "2026-04-07T10:00:00.000Z"
|
||||
revokedAt: null
|
||||
createdAt: "2026-04-07T09:00:00.000Z"
|
||||
'400':
|
||||
description: Validation error in request body.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
examples:
|
||||
scopeExceedsOwn:
|
||||
summary: Requested scope exceeds delegator's own scopes
|
||||
value:
|
||||
code: "SCOPE_EXCEEDS_DELEGATOR"
|
||||
message: "Delegated scopes must be a subset of the delegator's own token scopes."
|
||||
details:
|
||||
requested: ["agents:write"]
|
||||
available: ["agents:read"]
|
||||
invalidTtl:
|
||||
summary: TTL out of range
|
||||
value:
|
||||
code: "VALIDATION_ERROR"
|
||||
message: "ttlSeconds must be between 60 and 86400."
|
||||
crossTenant:
|
||||
summary: Delegatee in different tenant
|
||||
value:
|
||||
code: "CROSS_TENANT_DELEGATION"
|
||||
message: "Delegatee agent must be in the same organization as the delegator."
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'404':
|
||||
description: Delegatee agent not found.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "AGENT_NOT_FOUND"
|
||||
message: "Delegatee agent with the specified ID was not found."
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
|
||||
/oauth2/token/verify-delegation:
|
||||
post:
|
||||
operationId: verifyDelegation
|
||||
tags:
|
||||
- A2A Delegation
|
||||
summary: Verify a delegation token
|
||||
description: |
|
||||
Verifies a delegation token and returns the chain details if valid.
|
||||
|
||||
Returns `valid: true` with full chain metadata when the token is valid
|
||||
(exists, not expired, not revoked).
|
||||
|
||||
Returns `valid: false` when the token is expired or revoked.
|
||||
Does not throw an error for inactive tokens — always returns `200`.
|
||||
|
||||
Requires a valid Bearer JWT (any authenticated agent may verify a delegation).
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/VerifyDelegationRequest'
|
||||
example:
|
||||
delegationToken: "chain-abcd-1234-5678-ef01.1743151200.1743154800"
|
||||
responses:
|
||||
'200':
|
||||
description: Delegation verification result returned.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DelegationVerificationResult'
|
||||
examples:
|
||||
valid:
|
||||
summary: Valid delegation token
|
||||
value:
|
||||
valid: true
|
||||
chainId: "chain-abcd-1234-5678-ef01"
|
||||
delegatorAgentId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
delegateeAgentId: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
|
||||
scopes:
|
||||
- "agents:read"
|
||||
issuedAt: "2026-04-07T09:00:00.000Z"
|
||||
expiresAt: "2026-04-07T10:00:00.000Z"
|
||||
revokedAt: null
|
||||
expired:
|
||||
summary: Expired delegation token
|
||||
value:
|
||||
valid: false
|
||||
chainId: "chain-abcd-1234-5678-ef01"
|
||||
delegatorAgentId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
delegateeAgentId: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
|
||||
scopes:
|
||||
- "agents:read"
|
||||
issuedAt: "2026-04-06T09:00:00.000Z"
|
||||
expiresAt: "2026-04-06T10:00:00.000Z"
|
||||
revokedAt: null
|
||||
'400':
|
||||
description: Missing or malformed `delegationToken` field.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "VALIDATION_ERROR"
|
||||
message: "The 'delegationToken' field is required."
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
|
||||
/oauth2/token/delegate/{chainId}:
|
||||
parameters:
|
||||
- name: chainId
|
||||
in: path
|
||||
required: true
|
||||
description: UUID of the delegation chain to revoke.
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "chain-abcd-1234-5678-ef01"
|
||||
|
||||
delete:
|
||||
operationId: revokeDelegation
|
||||
tags:
|
||||
- A2A Delegation
|
||||
summary: Revoke a delegation chain
|
||||
description: |
|
||||
Immediately revokes a delegation chain.
|
||||
|
||||
After revocation, `POST /oauth2/token/verify-delegation` will return
|
||||
`valid: false` for the revoked chain's token.
|
||||
|
||||
**Idempotent** — revoking an already-revoked chain returns `204` without error.
|
||||
|
||||
Only the delegator agent or an admin may revoke a chain.
|
||||
Requires a valid Bearer JWT.
|
||||
responses:
|
||||
'204':
|
||||
description: Delegation chain revoked successfully (or was already revoked). No response body.
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
Reference in New Issue
Block a user