docs/adr/0009-agent-v1-contract-stability.md
Rama main · réplica de lectura del repositorio original.
# ADR 0009 — Agent v1 wire-contract stability
**Status:** Accepted (2026-06-25)
## Context
MDMesh deployments auto-update (CI/CD epic): a new server rolls out while devices in the field still
run older APKs, and Device-Owner devices **cannot be force-updated** (the user must approve; some may
lag indefinitely). So at any moment a deployment serves a mix of agent versions. If a server update
silently breaks the agent-facing wire contract, those older devices fall off management — the worst
possible failure for an MDM. ADR 0006 (capability handshake) and ADR 0004 (kotlinx.serialization,
`ignoreUnknownKeys`) set this up; this ADR makes the **stability rule explicit and enforced**.
## Decision
The `/agent/v1` request/response contract is **additive-only**:
- New fields are **optional and defaulted**. Never remove, rename, repurpose, or change the type of an
existing field; never add a **required** request field (old agents won't send it).
- Agent **request** DTOs carry `@JsonIgnoreProperties(ignoreUnknown = true)` so a *newer* agent's
extra fields don't 400 a not-yet-updated server. The agent already ignores unknown response keys.
- A genuine breaking change ships as a **new versioned namespace** (`/agent/v2`); `/agent/v1` keeps
serving older agents until they're all gone.
**Enforcement (not discipline):** `AgentV1ContractTest` (in `:common`, DB-free, run in CI) replays
recorded oldest-supported v1 payloads against the current DTOs, asserts the load-bearing fields, and
asserts a future-agent payload with unknown fields still deserializes — plus that response field
names the agent parses are unchanged. The fleet-update **manifest** also carries a compatible-agent
protocol range so the server never offers a device an incompatible APK.
## Consequences
- Old APKs keep working across server updates by construction; a contract break fails the build.
- The cost is forward-only schema evolution + maintaining golden payloads for the oldest supported
agent (extend them when the floor moves).
- A real break is possible but deliberate and visible (a new `/agent/vN` + its own contract tests),
never accidental.