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:
SentryAgent.ai Developer
2026-04-07 04:52:47 +00:00
parent d216096dfb
commit 7441c9f298
49 changed files with 8954 additions and 70 deletions

View File

@@ -0,0 +1,639 @@
openapi: "3.0.3"
info:
title: SentryAgent.ai — Identity Federation
version: 1.0.0
description: |
Cross-IdP identity federation endpoints for the SentryAgent.ai AgentIdP platform.
The federation subsystem enables this IdP to trust and verify JWT tokens issued by
external identity providers (partner IdPs). This allows agents from federated
organizations to authenticate without re-registering.
**Partner management** (`admin:orgs` scope required):
- `POST /federation/trust` — Register a new trusted partner IdP
- `GET /federation/partners` — List all registered partners
- `GET /federation/partners/:id` — Get a specific partner
- `PATCH /federation/partners/:id` — Update a partner
- `DELETE /federation/partners/:id` — Remove a partner
**Token verification** (any authenticated agent):
- `POST /federation/verify` — Verify a federated JWT from a trusted partner
servers:
- url: http://localhost:3000/api/v1
description: Local development server
- url: https://api.sentryagent.ai/v1
description: Production server
tags:
- name: Federation Partners
description: Trusted partner IdP management (admin:orgs scope)
- name: Federation Verification
description: Cross-IdP token verification (any authenticated agent)
components:
securitySchemes:
BearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
description: |
JWT access token obtained via `POST /token`.
Include as `Authorization: Bearer <token>`.
schemas:
FederationPartnerStatus:
type: string
enum:
- active
- suspended
- expired
description: |
Lifecycle status of a federation partner.
- `active` — partner is trusted; tokens accepted.
- `suspended` — temporarily disabled; tokens rejected.
- `expired` — `expires_at` has passed; tokens rejected.
example: active
FederationPartner:
type: object
description: A registered trusted federation partner IdP.
required:
- id
- name
- issuer
- jwks_uri
- allowed_organizations
- status
- created_at
- updated_at
properties:
id:
type: string
format: uuid
description: Immutable system-assigned UUID for this partner.
readOnly: true
example: "fed-abcd-1234-5678-ef01"
name:
type: string
description: Human-readable partner name.
example: "Acme Partner IdP"
issuer:
type: string
format: uri
description: Issuer URL — must match the `iss` claim in federated tokens.
example: "https://idp.acme-partner.example"
jwks_uri:
type: string
format: uri
description: URL of the partner's JWKS endpoint for token signature verification.
example: "https://idp.acme-partner.example/.well-known/jwks.json"
allowed_organizations:
type: array
items:
type: string
description: |
Allowlist of organization_id values accepted from this partner.
An empty array means all organizations from this partner are accepted.
example: ["org-acme-001", "org-acme-002"]
status:
$ref: '#/components/schemas/FederationPartnerStatus'
created_at:
type: string
format: date-time
readOnly: true
example: "2026-03-01T08:00:00.000Z"
updated_at:
type: string
format: date-time
readOnly: true
example: "2026-03-28T11:30:00.000Z"
expires_at:
type: string
format: date-time
nullable: true
description: Optional expiry timestamp. Null means the partner never expires.
example: "2027-01-01T00:00:00.000Z"
CreatePartnerRequest:
type: object
description: Request body for registering a new trusted federation partner.
required:
- name
- issuer
- jwks_uri
properties:
name:
type: string
description: Human-readable partner name.
minLength: 1
maxLength: 256
example: "Acme Partner IdP"
issuer:
type: string
format: uri
description: Issuer URL of the external IdP. Must match `iss` in federated tokens.
example: "https://idp.acme-partner.example"
jwks_uri:
type: string
format: uri
description: |
URL of the partner's JWKS endpoint. Must be reachable at registration time —
the endpoint is probed before the partner is persisted.
example: "https://idp.acme-partner.example/.well-known/jwks.json"
allowed_organizations:
type: array
items:
type: string
description: Optional allowlist of organization_id values. Defaults to empty (all allowed).
example: ["org-acme-001"]
expires_at:
type: string
format: date-time
description: Optional ISO 8601 date-time after which the partner will be automatically expired.
example: "2027-01-01T00:00:00.000Z"
UpdatePartnerRequest:
type: object
description: |
Request body for partially updating a federation partner.
All fields are optional; only provided fields are updated.
minProperties: 1
properties:
name:
type: string
minLength: 1
maxLength: 256
example: "Acme Partner IdP (Updated)"
jwks_uri:
type: string
format: uri
description: Updated JWKS endpoint URL. The JWKS cache will be invalidated on update.
example: "https://idp.acme-partner.example/.well-known/jwks.json"
allowed_organizations:
type: array
items:
type: string
example: ["org-acme-001", "org-acme-003"]
status:
$ref: '#/components/schemas/FederationPartnerStatus'
expires_at:
type: string
format: date-time
nullable: true
description: Updated expiry. Pass null to remove the expiry.
example: "2028-01-01T00:00:00.000Z"
FederationVerifyRequest:
type: object
description: Request body for verifying a federated token from a trusted partner.
required:
- token
properties:
token:
type: string
description: The JWT token string issued by the partner IdP.
example: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZ2VudC0wMDEiLCJpc3MiOiJodHRwczovL2lkcC5hY21lLXBhcnRuZXIuZXhhbXBsZSIsImV4cCI6MTc0MzE1NDgwMH0.signature"
expected_issuer:
type: string
format: uri
description: |
Optional issuer hint. When provided, the token's `iss` claim must match exactly.
Useful to prevent issuer confusion when multiple partners share JWKS.
example: "https://idp.acme-partner.example"
FederationVerifyResult:
type: object
description: Result of a successful federated token verification.
required:
- valid
- issuer
- subject
- claims
properties:
valid:
type: boolean
description: Whether the token is valid (true when verification passed).
example: true
issuer:
type: string
format: uri
description: The `iss` claim from the verified token.
example: "https://idp.acme-partner.example"
subject:
type: string
description: The `sub` claim from the verified token.
example: "agent-001-external"
organization_id:
type: string
description: The `organization_id` claim from the token, if present.
example: "org-acme-001"
claims:
type: object
description: All claims from the verified token payload.
additionalProperties: true
example:
iss: "https://idp.acme-partner.example"
sub: "agent-001-external"
aud: "https://api.sentryagent.ai"
iat: 1743151200
exp: 1743154800
organization_id: "org-acme-001"
ErrorResponse:
type: object
description: Standard error response envelope.
required:
- code
- message
properties:
code:
type: string
example: "FEDERATION_PARTNER_NOT_FOUND"
message:
type: string
example: "Federation partner 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: Federation partner not found.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
code: "FEDERATION_PARTNER_NOT_FOUND"
message: "Federation partner 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:
/federation/trust:
post:
operationId: registerFederationPartner
tags:
- Federation Partners
summary: Register a trusted federation partner
description: |
Registers a new external IdP as a trusted federation partner.
The partner's JWKS endpoint (`jwks_uri`) is probed at registration time
to confirm it is reachable. If the endpoint is unreachable, the request
is rejected with `422 Unprocessable Entity`.
Requires `admin:orgs` scope.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreatePartnerRequest'
example:
name: "Acme Partner IdP"
issuer: "https://idp.acme-partner.example"
jwks_uri: "https://idp.acme-partner.example/.well-known/jwks.json"
allowed_organizations:
- "org-acme-001"
expires_at: "2027-01-01T00:00:00.000Z"
responses:
'201':
description: Federation partner registered successfully.
content:
application/json:
schema:
$ref: '#/components/schemas/FederationPartner'
example:
id: "fed-abcd-1234-5678-ef01"
name: "Acme Partner IdP"
issuer: "https://idp.acme-partner.example"
jwks_uri: "https://idp.acme-partner.example/.well-known/jwks.json"
allowed_organizations:
- "org-acme-001"
status: "active"
created_at: "2026-04-07T09:00:00.000Z"
updated_at: "2026-04-07T09:00:00.000Z"
expires_at: "2027-01-01T00:00:00.000Z"
'400':
description: Validation error in request body.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
code: "VALIDATION_ERROR"
message: "Request validation failed."
details:
field: "jwks_uri"
reason: "Must be a valid HTTPS URL."
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'409':
description: A partner with the same issuer is already registered.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
code: "FEDERATION_PARTNER_CONFLICT"
message: "A federation partner with this issuer is already registered."
'422':
description: Partner JWKS endpoint is unreachable.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
code: "JWKS_UNREACHABLE"
message: "The partner JWKS endpoint is unreachable. Verify the URL and try again."
'500':
$ref: '#/components/responses/InternalServerError'
/federation/partners:
get:
operationId: listFederationPartners
tags:
- Federation Partners
summary: List all federation partners
description: |
Returns all registered federation partners.
Partners past their `expires_at` are returned with status `expired`.
Requires `admin:orgs` scope.
responses:
'200':
description: List of federation partners returned successfully.
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/FederationPartner'
example:
- id: "fed-abcd-1234-5678-ef01"
name: "Acme Partner IdP"
issuer: "https://idp.acme-partner.example"
jwks_uri: "https://idp.acme-partner.example/.well-known/jwks.json"
allowed_organizations: ["org-acme-001"]
status: "active"
created_at: "2026-03-01T08:00:00.000Z"
updated_at: "2026-03-28T11:30:00.000Z"
expires_at: "2027-01-01T00:00:00.000Z"
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'500':
$ref: '#/components/responses/InternalServerError'
/federation/partners/{id}:
parameters:
- name: id
in: path
required: true
description: UUID of the federation partner.
schema:
type: string
format: uuid
example: "fed-abcd-1234-5678-ef01"
get:
operationId: getFederationPartner
tags:
- Federation Partners
summary: Get a federation partner by ID
description: |
Returns a single federation partner record by its UUID.
Requires `admin:orgs` scope.
responses:
'200':
description: Federation partner returned successfully.
content:
application/json:
schema:
$ref: '#/components/schemas/FederationPartner'
example:
id: "fed-abcd-1234-5678-ef01"
name: "Acme Partner IdP"
issuer: "https://idp.acme-partner.example"
jwks_uri: "https://idp.acme-partner.example/.well-known/jwks.json"
allowed_organizations: ["org-acme-001"]
status: "active"
created_at: "2026-03-01T08:00:00.000Z"
updated_at: "2026-03-28T11:30:00.000Z"
expires_at: null
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'500':
$ref: '#/components/responses/InternalServerError'
patch:
operationId: updateFederationPartner
tags:
- Federation Partners
summary: Update a federation partner
description: |
Partially updates a federation partner's configuration.
Only provided fields are updated.
Changing `jwks_uri` invalidates the cached JWKS for this partner.
Requires `admin:orgs` scope.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdatePartnerRequest'
example:
status: "suspended"
allowed_organizations:
- "org-acme-001"
- "org-acme-003"
responses:
'200':
description: Federation partner updated successfully.
content:
application/json:
schema:
$ref: '#/components/schemas/FederationPartner'
example:
id: "fed-abcd-1234-5678-ef01"
name: "Acme Partner IdP"
issuer: "https://idp.acme-partner.example"
jwks_uri: "https://idp.acme-partner.example/.well-known/jwks.json"
allowed_organizations: ["org-acme-001", "org-acme-003"]
status: "suspended"
created_at: "2026-03-01T08:00:00.000Z"
updated_at: "2026-04-07T09:00:00.000Z"
expires_at: null
'400':
description: Validation error in request body.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
code: "VALIDATION_ERROR"
message: "Request validation failed."
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'500':
$ref: '#/components/responses/InternalServerError'
delete:
operationId: deleteFederationPartner
tags:
- Federation Partners
summary: Remove a federation partner
description: |
Permanently removes a federation partner. After removal, tokens issued
by the partner's IdP will no longer be accepted.
Requires `admin:orgs` scope.
responses:
'204':
description: Federation partner removed successfully. No response body.
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'500':
$ref: '#/components/responses/InternalServerError'
/federation/verify:
post:
operationId: verifyFederatedToken
tags:
- Federation Verification
summary: Verify a federated JWT token
description: |
Verifies a JWT token issued by a registered federation partner.
The token's `iss` claim is matched against registered active partners.
The signature is verified against the partner's JWKS.
The partner's `allowed_organizations` filter is applied if non-empty.
Returns verification result with claims on success.
Returns `422` with verification failure details on invalid tokens.
Requires any valid Bearer token (`agents:read` scope is sufficient).
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/FederationVerifyRequest'
example:
token: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZ2VudC0wMDEiLCJpc3MiOiJodHRwczovL2lkcC5hY21lLXBhcnRuZXIuZXhhbXBsZSJ9.signature"
expected_issuer: "https://idp.acme-partner.example"
responses:
'200':
description: Token verification result returned.
content:
application/json:
schema:
$ref: '#/components/schemas/FederationVerifyResult'
examples:
valid:
summary: Token verified successfully
value:
valid: true
issuer: "https://idp.acme-partner.example"
subject: "agent-001-external"
organization_id: "org-acme-001"
claims:
iss: "https://idp.acme-partner.example"
sub: "agent-001-external"
aud: "https://api.sentryagent.ai"
iat: 1743151200
exp: 1743154800
invalid:
summary: Token invalid or expired
value:
valid: false
issuer: ""
subject: ""
claims: {}
'400':
description: Missing or malformed token in request body.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
code: "VALIDATION_ERROR"
message: "The 'token' field is required."
'401':
$ref: '#/components/responses/Unauthorized'
'422':
description: |
Token is well-formed but verification failed (unknown issuer, expired,
invalid signature, or organization not in allowlist).
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples:
unknownIssuer:
summary: Unknown issuer
value:
code: "UNKNOWN_FEDERATION_ISSUER"
message: "No active federation partner found for this token issuer."
partnerSuspended:
summary: Partner is suspended
value:
code: "FEDERATION_PARTNER_SUSPENDED"
message: "The federation partner for this token issuer is suspended."
orgNotAllowed:
summary: Organization not in allowlist
value:
code: "FEDERATION_ORG_NOT_ALLOWED"
message: "The token's organization_id is not in the partner's allowed_organizations list."
'500':
$ref: '#/components/responses/InternalServerError'