A wire contract is more than a JSON field list. It includes meaning, units, nullability, ordering, error behavior, and whether clients tolerate values they did not know at build time. A new optional field may be harmless to a tolerant reader but may break a strict decoder. A new enum member can make an old client crash even though the field name stayed the same. A server cannot assume the browser bundle it just deployed is the only consumer: cached tabs, installed apps, and integration jobs may be older. Compatibility must be tested with the real supported client set, not inferred from one schema diff.
Response Shape Evolution and Unknown Values
Working case
The permit API reports a case status of open, held, or closed. A new review workflow introduces awaiting-evidence. The current portal displays the new state, while a cached bundle from release 47 renders no status and still enables the Close button. Rather than sending the new state to all clients immediately, the server keeps an old representation that maps it to held with a clear read-only reason, or it gates the state until the old bundle can handle an unknown value safely. The new client has an explicit Unknown status display and disables actions it cannot justify. The status meaning stays server-owned.
Implementation boundary
function caseActionForStatus(status) {
const allowed = new Set(["open", "held", "closed"]);
return allowed.has(status) ? status : "read-only-unknown";
}
console.log(caseActionForStatus("awaiting-evidence"));
// Output: read-only-unknownInventory each consumer and the exact response fields it reads. Decide whether the change is additive, behavior-changing, or removal. Preserve old required fields during an expand window, and add new fields with explicit nullability and units. For enums, include a safe unknown-value path in clients before any server rollout that can emit a new member. Do not silently reinterpret a known field; use a new field or representation when meaning changes. Validate responses at the client boundary, but avoid a decoder that rejects every extra property unless that strictness is a conscious versioned contract. Enforce action permission on the server even if an old UI offers a button. Keep error payloads and pagination shapes under the same compatibility review.
Cost and boundaries
Dual response fields add bytes and serialization work, roughly O(N) in the number of returned records; at large page sizes or mobile bandwidth this can be visible. Mapping an unfamiliar status for an old client adds policy complexity and may conceal a distinction, so choose the fallback by task safety rather than convenience. Multiple client versions multiply test cases. Measure which supported client versions receive each status, decode failures, attempted stale actions, response bytes, and rollback time. Removing a field too early can cost more in incident work than carrying it for a bounded window.
Failure trace
Send awaiting-evidence to the oldest supported browser bundle. It must show a safe state and avoid an unauthorized Close request. Make the new field null, absent, and an unexpected type to test the boundary. Rename the status field in the server without a compatibility window and observe the old bundle failure. Try a response with both old and new fields disagreeing; choose one authority and surface a server contract error rather than accepting whichever appears first. Revoke the reviewer while an old page is open; the API still rejects the action.
Verification
- Known fields retain meaning and units throughout the overlap window.
- Old clients handle new enum members safely before they are emitted.
- Server permission remains authoritative for every action.
Practice drill
Take release 47 and release 63 client fixtures. Add an awaiting-evidence status for case 29 and a new optional reviewStage field. Run both clients against pre-expand, expanded, and contracted API fixtures. Record visible status, allowed actions, decode outcome, and response byte size for a 63-record page. Inject an unknown future status and prove both clients fail safely. State the exact supported lifetime of release 47 before considering removal of its fields.
Decision note
Treat unknown data and old clients as ordinary production states, with server-owned action rules.
Common Mistakes
- Calling a new enum value automatically additive.
- Removing an old field when only the latest browser bundle was tested.
- Treating an unknown status as open by default.
Related lessons
API Contract Evolution and Client Migration; API Version Selection and Representation Scope; Deprecation, Sunset, and Consumer Evidence; Consumer Contract Matrix and Expand-Contract Release; API Evolution and Compatibility Windows; TypeScript and Runtime API Boundaries.
Apply and check
Build Project: permit API client migration and review Web Development: API contract migration quiz.
