Implements all P0 features per OpenSpec change phase-1-mvp-implementation: - Agent Registry Service (CRUD) — full lifecycle management - OAuth 2.0 Token Service (Client Credentials flow) - Credential Management (generate, rotate, revoke) - Immutable Audit Log Service Tech: Node.js 18+, TypeScript 5.3+ strict, Express 4.18+, PostgreSQL 14+, Redis 7+ Standards: OpenAPI 3.0 specs, DRY/SOLID, zero `any` types Quality: 18 unit test suites, 244 tests passing, 97%+ coverage OpenAPI: 4 complete specs (14 endpoints total) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
6.6 KiB
ADDED Requirements
Requirement: Generate new credentials for an agent
The system SHALL generate a new client_id/client_secret pair for a specified agent. The client_id SHALL equal the agent's agentId. The client_secret SHALL be a cryptographically random string with the prefix sk_live_ followed by 64 hex characters (256 bits of entropy). The plain-text secret SHALL be returned in the response exactly once and SHALL never be stored in plain text — only a bcrypt hash (10 rounds) SHALL be persisted. The agent MUST be in active status to generate credentials.
Scenario: Successful credential generation
- WHEN a POST request to
/agents/{agentId}/credentialsis received with a valid Bearer token and the agent exists withstatus: active - THEN the system generates a new credential, persists the bcrypt hash of the secret, and returns
201 Createdwith aCredentialWithSecretresponse including the plain-textclientSecret
Scenario: clientSecret not returned after creation
- WHEN a GET request to
/agents/{agentId}/credentialsis made after credential creation - THEN the
clientSecretfield is NOT present in anyCredentialobject in the response
Scenario: Suspended agent cannot generate credentials
- WHEN a POST request to
/agents/{agentId}/credentialsis received for an agent withstatus: suspended - THEN the system returns
403 Forbiddenwithcode: AGENT_NOT_ACTIVE
Scenario: Decommissioned agent cannot generate credentials
- WHEN a POST request to
/agents/{agentId}/credentialsis received for an agent withstatus: decommissioned - THEN the system returns
403 Forbiddenwithcode: AGENT_NOT_ACTIVE
Scenario: Optional expiry respected
- WHEN a POST request to
/agents/{agentId}/credentialsis received with anexpiresAtvalue that is a future date-time - THEN the credential is created with the specified
expiresAtvalue
Scenario: Past expiry rejected
- WHEN a POST request to
/agents/{agentId}/credentialsis received with anexpiresAtvalue that is in the past - THEN the system returns
400 Bad Requestwithcode: VALIDATION_ERRORanddetails.field: expiresAt
Scenario: Agent not found
- WHEN a POST request to
/agents/{agentId}/credentialsis received for aagentIdthat does not exist - THEN the system returns
404 Not Foundwithcode: AGENT_NOT_FOUND
Requirement: List credentials for an agent
The system SHALL return a paginated list of all credentials (both active and revoked) for an agent, ordered by createdAt descending. The clientSecret SHALL never be included in list responses.
Scenario: Successful credential list
- WHEN a GET request to
/agents/{agentId}/credentialsis received with optionalpage,limit,statusquery parameters and a valid Bearer token - THEN the system returns
200 OKwith aPaginatedCredentialsResponsecontainingdata,total,page, andlimit, with noclientSecretfields
Scenario: Filter by status
- WHEN a GET request to
/agents/{agentId}/credentials?status=activeis received - THEN only credentials with
status: activeare returned
Requirement: Rotate a credential
The system SHALL rotate an existing active credential by generating a new clientSecret for the same credentialId. The previous secret SHALL be immediately invalidated. The new plain-text secret SHALL be returned once and never persisted. Only active credentials can be rotated.
Scenario: Successful rotation
- WHEN a POST request to
/agents/{agentId}/credentials/{credentialId}/rotateis received with a valid Bearer token and the credential exists withstatus: active - THEN the system generates a new secret, replaces the stored bcrypt hash, and returns
200 OKwith aCredentialWithSecretresponse including the new plain-textclientSecret. ThecredentialIdremains unchanged.
Scenario: Revoked credential cannot be rotated
- WHEN a POST request to
/agents/{agentId}/credentials/{credentialId}/rotateis received for a credential withstatus: revoked - THEN the system returns
409 Conflictwithcode: CREDENTIAL_ALREADY_REVOKED
Scenario: Credential not found
- WHEN a POST request to
/agents/{agentId}/credentials/{credentialId}/rotateis received with acredentialIdthat does not exist for the given agent - THEN the system returns
404 Not Foundwithcode: CREDENTIAL_NOT_FOUND
Requirement: Revoke a credential
The system SHALL permanently revoke a credential by setting its status to revoked and recording a revokedAt timestamp. The credential record SHALL be retained for audit purposes. Revocation SHALL be irreversible. Tokens previously issued with this credential SHALL remain valid until their natural expiry (token revocation is handled separately via POST /token/revoke). Revoking an already-revoked credential SHALL return 409 Conflict.
Scenario: Successful revocation
- WHEN a DELETE request to
/agents/{agentId}/credentials/{credentialId}is received with a valid Bearer token and the credential exists withstatus: active - THEN the system sets
statustorevoked, setsrevokedAtto the current timestamp, and returns204 No Content
Scenario: Already-revoked credential rejected
- WHEN a DELETE request to
/agents/{agentId}/credentials/{credentialId}is received for a credential that is alreadyrevoked - THEN the system returns
409 Conflictwithcode: CREDENTIAL_ALREADY_REVOKED
Requirement: Agent decommission cascades to credential revocation
When an agent is decommissioned via DELETE /agents/{agentId}, the system SHALL revoke all active credentials for that agent as part of the same operation.
Scenario: All credentials revoked on agent decommission
- WHEN an agent is successfully decommissioned via
DELETE /agents/{agentId} - THEN all credentials for that agent with
status: activeare set tostatus: revokedwithrevokedAt= current timestamp
Requirement: Authentication required on all credential endpoints
All credential endpoints SHALL require a valid Bearer JWT. An agent MAY manage its own credentials using a self-issued token. Managing another agent's credentials SHALL return 403 Forbidden unless the caller holds an admin-scoped token (admin scope is not implemented in Phase 1 — return 403 for all cross-agent requests).
Scenario: Unauthenticated request rejected
- WHEN any request to
/agents/{agentId}/credentialsis received without a valid Bearer token - THEN the system returns
401 Unauthorizedwithcode: UNAUTHORIZED