docs: spec agent config endpoint flip to flat JWT-scoped URL

Server team confirmed /api/v1/agents/config is the only AGENT-role
config read route; the env-scoped URL from the 2026-04-16 design
returns 403 in practice. This spec documents the agent-side flip,
retains strict response env validation, and drops the now-unused
application parameter from fetchApplicationConfig.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-17 10:24:42 +02:00
parent 47ea09bef1
commit e6266a2f4a

View File

@@ -0,0 +1,89 @@
# Agent Config Endpoint — Flat, JWT-Resolved
**Date:** 2026-04-17
**Status:** Approved
**Scope:** `cameleer-core`, `cameleer-common`
**Supersedes:** The agent-side URL from `2026-04-16-env-scoped-config-url-agent-design.md` only. The env-scoped route family (`/api/v1/environments/{envSlug}/...`) remains for user-facing endpoints on the server; it simply is not how the agent fetches its own config.
## Problem
The 2026-04-16 landing moved the agent's config fetch to `GET /api/v1/environments/{envId}/apps/{app}/config`. In practice that route is 403 for the agent because the server's only `hasRole("AGENT")` config-read endpoint is flat and JWT-scoped (`SecurityConfig.java:127`). The env-scoped route is reserved for user-facing calls.
## Design
### Endpoint
```
GET {baseUrl}/api/v1/agents/config
```
Zero URL parameters. The server resolves `(application, environment)` from the agent's JWT subject → registry entry (heartbeat-authoritative), with the JWT env claim as fallback.
### `ServerConnection.fetchApplicationConfig`
Signature changes from `(String application)` to `()` — the parameter was only used to build the URL and is now redundant. Callers drop the argument.
New behavior:
```
path = "/api/v1/agents/config"
```
401/403 refresh-retry path (existing) targets the same URL.
Post-response checks are unchanged from the 2026-04-16 design:
1. HTTP status / 401+403 refresh path (existing).
2. Deserialize envelope → `ApplicationConfig`.
3. Merge `mergedSensitiveKeys` into `ApplicationConfig.sensitiveKeys` (existing).
4. Version check: if `config.getVersion() == 0`, return `null` (no config stored).
5. Env validation (version > 0 only): throw if `config.getEnvironment() == null` or does not equal `registeredEnvironmentId`.
The response envelope keeps `environment` populated when `version > 0`, so strict validation remains a cheap cross-check that the server's JWT→env resolution agrees with what the agent registered as.
### Fields and state
- `registeredEnvironmentId` is still populated by `register()` and still used for response validation and the heartbeat body. No change.
- `ConfigCacheManager` keeps its per-application key. No change.
- Heartbeat body keeps `environmentId`. No change.
### PROTOCOL.md
- Section 3 endpoint table: replace the `GET /api/v1/environments/{environmentId}/apps/{applicationId}/config` row with `GET /api/v1/agents/config` — "Fetch per-agent config (server resolves application and environment from the agent's JWT)".
- "Config Endpoint Response Envelope" heading: update to the new URL.
- The paragraph on strict env validation and the `version == 0` bypass stays as-is — the response envelope and validation behavior are unchanged.
- SSE reconnection bullet that references the config URL: update to the flat URL.
## Error handling
Unchanged from 2026-04-16. Table applies verbatim:
| Case | Behavior |
|---|---|
| 401 / 403 | Refresh token, retry once. |
| Non-200 after refresh | `RuntimeException("Config fetch failed: HTTP nnn")`. |
| 200 + `version == 0` | Return `null`. Env not validated. |
| 200 + `version > 0` + `environment == null` | Throw. |
| 200 + `version > 0` + `environment != registeredEnvironmentId` | Throw. |
| 200 + `version > 0` + env matches | Return config. |
## Testing
`cameleer-core/src/test/java/com/cameleer/core/connection/ServerConnectionTest.java`:
- **URL shape** — `ArgumentCaptor<HttpRequest>` asserts the request URI path is `/api/v1/agents/config` (no env/app segments).
- **Strict mismatch rejection** — retained; response body still carries `environment`, and the agent still rejects a mismatch.
- **Strict null rejection** — retained.
- **Happy path env match** — retained.
- All `fetchApplicationConfig(...)` call sites in tests drop the argument.
## Non-goals
- No changes to `/api/v1/data/*` (unchanged, JWT-authoritative).
- No changes to `/api/v1/agents/{instanceId}/*` (register, heartbeat, deregister, refresh, ack, SSE).
- No removal of `registeredEnvironmentId` field or heartbeat env field.
- No protocol-version bump.
## Rollout
Hard cut on main. Server's agent-config route is already live (prior env-scoped URL is 403), so this fixes the agent immediately on deploy.