Version and deprecate without breaking consumers
Shaping a good v1 is half the job. The versioning-and-deprecation standard owns the other half: changing that contract over time without breaking anyone. Its governing doctrine is blunt - never break userspace without consent. A public contract stays functional until it has been deprecated, sunset, and retired through process. There is no silent break.
1. The right scheme per surface
There is no single versioning scheme that fits a library, a wire protocol, and a graph. Cogitave picks per surface and makes the version observable so agents can negotiate it:
| Surface | Scheme | Default |
|---|---|---|
| Library / SDK | SemVer 2.0.0 | machine-verified before publish |
| HTTP API (public) | date-based, consumer-pinned (YYYY-MM-DD) | no hard cutover |
| gRPC / Protobuf | field-number immutability | additive-only |
| GraphQL | continuous evolution, @deprecated | never versioned |
| Event / JSON schema | registry compatibility mode | BACKWARD_TRANSITIVE |
| MCP | protocol date + per-capability SemVer | negotiated |
Two choices are worth internalizing. HTTP is date-based and consumer-pinned: a consumer sends Cogitave-Version: 2026-06-28, the server serves that frozen contract and transforms responses forward, so each integration upgrades deliberately by bumping one date instead of being force-migrated. A version is not a URL - it belongs in a header, not in /v2. gRPC rests on one absolute rule: a field number is forever. Never change, reuse, or retype a field number; reserved a number and its name on removal. Both are gated in CI (buf breaking for proto, the manifest and RFC for HTTP).
2. Additive by default, and the support window
Backward compatibility within a contract is absolute - additive change only. Any subtractive or semantics-altering change starts a new contract version and deprecates the old one. Cogitave supports the current contract plus the previous (N-1) for a minimum of 12 months, and date-pinned HTTP consumers for 24 months from the date they pin. The exact window is declared in the deprecating RFC and encoded in the manifest - never implicit.
3. The deprecation lifecycle
A contract moves through a fixed state machine, aligned to the docs moniker so the API and its documentation always agree:
preview -> active -> deprecated -> sunset -> retired
deprecated still serves and emits signals; sunset is the phase after sunsetAt when removal is imminent; retired returns a typed error with a migration link. Announcing deprecated to sunsetAt is a minimum 6 months; the total deprecated window is at least 12 months. Removal is gated by both the calendar (sunsetAt passed) and observation - you may not retire a contract you cannot prove is unused, measured by a deprecation-usage metric.
4. One manifest, no drift
A deprecated contract MUST emit every applicable signal: HTTP Deprecation and Sunset headers, in-schema markers (@deprecated, proto deprecated = true, OpenAPI deprecated: true), SDK/CLI warnings, a changelog Deprecated entry, and a migration guide. All of them are generated from one committed deprecation manifest, updated in the same PR as the change, so headers, docs moniker, and changelog cannot disagree.
IMPORTANT
A breaking or deprecating change is a design-class change. It requires an Accepted RFC through the request lifecycle before any code. Agents draft the RFC, manifest, and migration guide and open the PR; a human holds the merge and retirement gate - agents propose, they never enact.