Files
SentryAgent.ai Developer 61ea975c79 docs: bedroom developer documentation — complete docs/developers/ set
Adds the full bedroom-developer-docs OpenSpec change implementation:

- docs/developers/README.md — index page
- docs/developers/quick-start.md — bootstrap to working token in 7 steps
- docs/developers/concepts.md — AgentIdP, AGNTCY, lifecycle, OAuth 2.0, free tier
- docs/developers/guides/README.md — guide index
- docs/developers/guides/register-an-agent.md — all fields, validation, common errors
- docs/developers/guides/manage-credentials.md — generate, list, rotate, revoke
- docs/developers/guides/issue-and-revoke-tokens.md — OAuth 2.0 flow, introspect, revoke
- docs/developers/guides/query-audit-logs.md — filters, pagination, 90-day retention
- docs/developers/api-reference.md — all 14 endpoints, all error codes, curl examples

Also commits deferred OpenSpec housekeeping from previous session:
- Archives phase-1-mvp-implementation change to openspec/changes/archive/
- Adds bedroom-developer-docs change artifacts (30/30 tasks complete)
- Syncs 4 delta specs to openspec/specs/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 14:13:03 +00:00

84 lines
6.6 KiB
Markdown

## 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}/credentials` is received with a valid Bearer token and the agent exists with `status: active`
- **THEN** the system generates a new credential, persists the bcrypt hash of the secret, and returns `201 Created` with a `CredentialWithSecret` response including the plain-text `clientSecret`
#### Scenario: clientSecret not returned after creation
- **WHEN** a GET request to `/agents/{agentId}/credentials` is made after credential creation
- **THEN** the `clientSecret` field is NOT present in any `Credential` object in the response
#### Scenario: Suspended agent cannot generate credentials
- **WHEN** a POST request to `/agents/{agentId}/credentials` is received for an agent with `status: suspended`
- **THEN** the system returns `403 Forbidden` with `code: AGENT_NOT_ACTIVE`
#### Scenario: Decommissioned agent cannot generate credentials
- **WHEN** a POST request to `/agents/{agentId}/credentials` is received for an agent with `status: decommissioned`
- **THEN** the system returns `403 Forbidden` with `code: AGENT_NOT_ACTIVE`
#### Scenario: Optional expiry respected
- **WHEN** a POST request to `/agents/{agentId}/credentials` is received with an `expiresAt` value that is a future date-time
- **THEN** the credential is created with the specified `expiresAt` value
#### Scenario: Past expiry rejected
- **WHEN** a POST request to `/agents/{agentId}/credentials` is received with an `expiresAt` value that is in the past
- **THEN** the system returns `400 Bad Request` with `code: VALIDATION_ERROR` and `details.field: expiresAt`
#### Scenario: Agent not found
- **WHEN** a POST request to `/agents/{agentId}/credentials` is received for a `agentId` that does not exist
- **THEN** the system returns `404 Not Found` with `code: 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}/credentials` is received with optional `page`, `limit`, `status` query parameters and a valid Bearer token
- **THEN** the system returns `200 OK` with a `PaginatedCredentialsResponse` containing `data`, `total`, `page`, and `limit`, with no `clientSecret` fields
#### Scenario: Filter by status
- **WHEN** a GET request to `/agents/{agentId}/credentials?status=active` is received
- **THEN** only credentials with `status: active` are 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}/rotate` is received with a valid Bearer token and the credential exists with `status: active`
- **THEN** the system generates a new secret, replaces the stored bcrypt hash, and returns `200 OK` with a `CredentialWithSecret` response including the new plain-text `clientSecret`. The `credentialId` remains unchanged.
#### Scenario: Revoked credential cannot be rotated
- **WHEN** a POST request to `/agents/{agentId}/credentials/{credentialId}/rotate` is received for a credential with `status: revoked`
- **THEN** the system returns `409 Conflict` with `code: CREDENTIAL_ALREADY_REVOKED`
#### Scenario: Credential not found
- **WHEN** a POST request to `/agents/{agentId}/credentials/{credentialId}/rotate` is received with a `credentialId` that does not exist for the given agent
- **THEN** the system returns `404 Not Found` with `code: 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 with `status: active`
- **THEN** the system sets `status` to `revoked`, sets `revokedAt` to the current timestamp, and returns `204 No Content`
#### Scenario: Already-revoked credential rejected
- **WHEN** a DELETE request to `/agents/{agentId}/credentials/{credentialId}` is received for a credential that is already `revoked`
- **THEN** the system returns `409 Conflict` with `code: 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: active` are set to `status: revoked` with `revokedAt` = 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}/credentials` is received without a valid Bearer token
- **THEN** the system returns `401 Unauthorized` with `code: UNAUTHORIZED`