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>
This commit is contained in:
76
openspec/specs/oauth2-token/spec.md
Normal file
76
openspec/specs/oauth2-token/spec.md
Normal file
@@ -0,0 +1,76 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Issue access token via Client Credentials grant
|
||||
The system SHALL issue a signed RS256 JWT access token when an agent authenticates with a valid `client_id` (agentId) and `client_secret` using the OAuth 2.0 Client Credentials grant (RFC 6749 §4.4). The request body SHALL use `application/x-www-form-urlencoded` encoding. The response SHALL include `Cache-Control: no-store` and `Pragma: no-cache` headers. The system SHALL enforce a free-tier limit of 10,000 token requests per calendar month per client.
|
||||
|
||||
#### Scenario: Successful token issuance
|
||||
- **WHEN** a POST request to `/token` is received with `grant_type=client_credentials`, a valid `client_id`, and a valid `client_secret` for an `active` agent
|
||||
- **THEN** the system verifies the credential, issues a signed JWT with `sub` = `agentId`, `scope` = requested (or default) scope, `exp` = now + 3600s, and returns `200 OK` with `TokenResponse`
|
||||
|
||||
#### Scenario: Invalid client credentials rejected
|
||||
- **WHEN** a POST request to `/token` is received with a `client_id` that does not exist or a `client_secret` that does not match
|
||||
- **THEN** the system returns `401 Unauthorized` with `error: invalid_client`
|
||||
|
||||
#### Scenario: Suspended agent cannot obtain tokens
|
||||
- **WHEN** a POST request to `/token` is received for an agent with `status: suspended`
|
||||
- **THEN** the system returns `403 Forbidden` with `error: unauthorized_client` and a description indicating the agent is suspended
|
||||
|
||||
#### Scenario: Decommissioned agent cannot obtain tokens
|
||||
- **WHEN** a POST request to `/token` is received for an agent with `status: decommissioned`
|
||||
- **THEN** the system returns `403 Forbidden` with `error: unauthorized_client`
|
||||
|
||||
#### Scenario: Unsupported grant type rejected
|
||||
- **WHEN** a POST request to `/token` is received with a `grant_type` other than `client_credentials`
|
||||
- **THEN** the system returns `400 Bad Request` with `error: unsupported_grant_type`
|
||||
|
||||
#### Scenario: Invalid scope rejected
|
||||
- **WHEN** a POST request to `/token` is received with a `scope` value that contains an unrecognised scope identifier
|
||||
- **THEN** the system returns `400 Bad Request` with `error: invalid_scope`
|
||||
|
||||
#### Scenario: Free tier monthly token limit enforced
|
||||
- **WHEN** a POST request to `/token` is received and the agent has already made 10,000 token requests in the current calendar month
|
||||
- **THEN** the system returns `403 Forbidden` with `error: unauthorized_client` and a description indicating the monthly free-tier limit is reached
|
||||
|
||||
### Requirement: Token introspection (RFC 7662)
|
||||
The system SHALL determine whether a given access token is currently active (valid, not expired, not revoked). The endpoint SHALL return `200 OK` for both active and inactive tokens — the `active` field in the response SHALL indicate validity. The caller SHALL hold a valid Bearer token with `tokens:read` scope.
|
||||
|
||||
#### Scenario: Active token introspection
|
||||
- **WHEN** a POST request to `/token/introspect` is received with a valid, non-expired, non-revoked token and the caller has `tokens:read` scope
|
||||
- **THEN** the system returns `200 OK` with `active: true` and the token's claims (`sub`, `client_id`, `scope`, `token_type`, `iat`, `exp`)
|
||||
|
||||
#### Scenario: Expired or revoked token introspection
|
||||
- **WHEN** a POST request to `/token/introspect` is received with a token that is expired or has been revoked
|
||||
- **THEN** the system returns `200 OK` with `active: false` and no other claims
|
||||
|
||||
#### Scenario: Insufficient scope for introspection
|
||||
- **WHEN** a POST request to `/token/introspect` is received with a valid Bearer token that does not have `tokens:read` scope
|
||||
- **THEN** the system returns `403 Forbidden` with `code: INSUFFICIENT_SCOPE`
|
||||
|
||||
### Requirement: Token revocation (RFC 7009)
|
||||
The system SHALL invalidate a given access token immediately. Revoking an already-revoked or expired token SHALL be a successful, idempotent operation (RFC 7009 §2.1). Revoked token JTIs SHALL be stored in Redis with TTL equal to the token's remaining lifetime.
|
||||
|
||||
#### Scenario: Successful token revocation
|
||||
- **WHEN** a POST request to `/token/revoke` is received with a valid Bearer token and a `token` parameter containing a valid JWT
|
||||
- **THEN** the system adds the token's JTI to the Redis revocation list, and returns `200 OK` with an empty body
|
||||
|
||||
#### Scenario: Revocation of already-revoked token is idempotent
|
||||
- **WHEN** a POST request to `/token/revoke` is received with a token that is already in the Redis revocation list
|
||||
- **THEN** the system returns `200 OK` with an empty body (no error)
|
||||
|
||||
#### Scenario: Missing token parameter rejected
|
||||
- **WHEN** a POST request to `/token/revoke` is received with no `token` field in the body
|
||||
- **THEN** the system returns `400 Bad Request` with `code: VALIDATION_ERROR`
|
||||
|
||||
### Requirement: JWT claims structure
|
||||
All issued JWTs SHALL contain the following claims: `sub` (agentId), `client_id` (agentId), `scope` (space-separated granted scopes), `jti` (UUID, unique per token), `iat` (issued-at Unix timestamp), `exp` (expiry Unix timestamp). Tokens SHALL be signed with RS256.
|
||||
|
||||
#### Scenario: JWT contains required claims
|
||||
- **WHEN** a token is issued via `POST /token`
|
||||
- **THEN** the decoded JWT payload contains `sub`, `client_id`, `scope`, `jti`, `iat`, and `exp` fields
|
||||
|
||||
### Requirement: Rate limiting on token endpoints
|
||||
The system SHALL enforce a rate limit of 100 requests per minute per `client_id` on all token endpoints.
|
||||
|
||||
#### Scenario: Rate limit exceeded on token endpoint
|
||||
- **WHEN** a client sends more than 100 requests to any token endpoint within a 60-second window
|
||||
- **THEN** the system returns `429 Too Many Requests` with `X-RateLimit-Limit`, `X-RateLimit-Remaining: 0`, and `X-RateLimit-Reset` headers
|
||||
Reference in New Issue
Block a user