# CommonGrants Versioning Strategy: TypeSpec-Driven Semantic Versioning Explained

> Discover the CommonGrants versioning strategy. Learn how TypeSpec-driven semantic versioning and @Versioning decorators ensure clear API version provenance.

- Repository: [U.S. Department of Health & Human Services/simpler-grants-protocol](https://github.com/hhs/simpler-grants-protocol)
- Tags: architecture
- Published: 2026-03-03

---

**CommonGrants uses a TypeSpec-driven semantic versioning strategy where each protocol version is represented by a constant in the `CommonGrants.Versions` namespace, and API artifacts declare their version provenance using `@Versioning` decorators.**

The `hhs/simpler-grants-protocol` repository implements a declarative versioning system that enables the Common Grants API to evolve safely while giving downstream developers precise control over which version they consume. Every model, field, route, and filter in the core library carries explicit metadata about when it was introduced, renamed, or changed.

## How Versioning Works in CommonGrants

The protocol treats versioning as a first-class concern within the TypeSpec schema definitions. Rather than maintaining separate files for each API version, CommonGrants uses decorators to annotate symbols in place, allowing the compiler to generate version-specific OpenAPI specifications from a single source of truth.

### Version Constants in the Core Library

All supported protocol versions are defined as constants in the `CommonGrants.Versions` namespace. These constants serve as the canonical reference points for the `@Versioning` decorators used throughout the codebase.

In `lib/core/lib/core/types.tsp` (lines 31-38), the version constants are declared:

```tsp
namespace CommonGrants.Versions {
  const v0_1 = "0.1.0";
  const v0_2 = "0.2.0";
  const v0_3 = "0.3.0";
}

```

These constants provide the temporal markers that other files reference when declaring the lifecycle of API artifacts.

### TypeSpec Versioning Decorators

The CommonGrants library leverages TypeSpec's built-in versioning library to annotate schema changes. The four primary decorators used are:

- **`@Versioning.added()`** – Marks when a symbol first appeared in the protocol
- **`@Versioning.renamedFrom()`** – Records that a symbol was renamed from a previous identifier
- **`@Versioning.typeChangedFrom()`** – Documents when a field's type was modified
- **`@Versioning.useDependency()`** – Allows consumer specifications to target a specific protocol version

## Annotating API Changes with @Versioning

Every change to the CommonGrants protocol is documented through decorators attached directly to the TypeSpec models. This approach creates an explicit audit trail that tooling can use to generate migration guides and versioned client libraries.

### Adding New Models and Fields

When new functionality is introduced, developers annotate the model or field with `@Versioning.added()` and the appropriate version constant. For example, in `lib/core/lib/core/models/person.tsp` (line 8), a model added in v0.1 would be decorated:

```tsp
@Versioning.added(CommonGrants.Versions.v0_1)
model Person {
  id: string;
  name: string;
}

```

Similarly, new fields added in later versions carry the same annotation. This ensures that code generators know exactly which version of the schema includes each property.

### Renaming Symbols Across Versions

The protocol maintains backward compatibility by explicitly tracking renames. In `lib/core/lib/core/models/form.tsp` (line 12), a field renamed in v0.3 would use:

```tsp
model Form {
  @Versioning.renamedFrom(CommonGrants.Versions.v0_3, "oldFieldName")
  newFieldName: string;
}

```

The `@Versioning.renamedFrom` decorator preserves the historical name, allowing older clients to map their existing data to the new schema while newer clients use the current identifier.

### Changing Types Safely

When a field's type must evolve, the `@Versioning.typeChangedFrom` decorator documents the transition. As shown in `lib/core/lib/core/filters/numeric.tsp` (line 15), this marks when a field changed from one type to another:

```tsp
model NumericFilter {
  @Versioning.typeChangedFrom(CommonGrants.Versions.v0_2, string)
  value: integer;
}

```

This pattern ensures that type migrations are machine-readable, enabling automated validation of data across protocol versions.

## Consuming Specific Protocol Versions

Implementers can lock their specifications to a specific CommonGrants version using the `@Versioning.useDependency` decorator. This is demonstrated in `templates/quickstart/main.tsp` (line 9):

```tsp
@Versioning.useDependency(CommonGrants.Versions.v0_2)

import "@common-grants/core";

// All imported symbols resolve to their v0.2 definitions

```

By declaring the dependency explicitly, the TypeSpec compiler resolves all imported symbols to their state as of v0.2, even if the core library has since added new fields or models. This provides **stable API contracts** for downstream consumers while allowing the protocol to evolve.

## Governance and Versioning Strategy Modifications

The versioning strategy itself is subject to governance through the RFC (Request for Comments) process. According to `website/src/content/docs/governance/rfc/index.mdx` (lines 22-25), proposals can modify how the protocol handles versioning, ensuring the strategy adapts to community needs while maintaining stability.

RFCs that affect versioning must consider backward compatibility and provide clear migration paths, consistent with the existing decorator-based approach.

## Summary

- **Explicit version constants** in `lib/core/lib/core/types.tsp` provide canonical version markers (v0_1, v0_2, v0_3).
- **TypeSpec decorators** (`@Versioning.added`, `@Versioning.renamedFrom`, `@Versioning.typeChangedFrom`) annotate every API change with its version of origin.
- **Consumer control** via `@Versioning.useDependency` allows implementations to target specific protocol versions for stable contracts.
- **Machine-readable evolution** enables automated generation of versioned OpenAPI schemas and migration tooling.
- **RFC governance** ensures the versioning strategy can adapt through community-driven proposals.

## Frequently Asked Questions

### How does CommonGrants handle breaking changes?

CommonGrants handles breaking changes through the `@Versioning` decorator system. When a field is renamed or its type is changed, the old name or type is preserved in the schema metadata using `@Versioning.renamedFrom` or `@Versioning.typeChangedFrom`. This allows the TypeSpec compiler to generate separate OpenAPI specifications for each version, ensuring that existing integrations continue to function while new implementations can adopt the updated schema.

### What version of CommonGrants should my implementation target?

You should target the latest stable version that supports the features you need, declared using `@Versioning.useDependency(CommonGrants.Versions.v0_X)` in your main TypeSpec file. According to the quickstart template in `templates/quickstart/main.tsp`, locking to a specific version (such as v0.2) ensures your generated API contracts remain stable even as the core library evolves. You can upgrade versions deliberately by changing the dependency constant and testing your integration against the new schema.

### Where are the version constants defined?

The version constants are defined in `lib/core/lib/core/types.tsp` within the `CommonGrants.Versions` namespace. This file contains the canonical version strings (v0_1, v0_2, v0_3) that serve as the reference points for all `@Versioning` decorators throughout the codebase. Any new protocol version must be added to this namespace before it can be referenced in other files.

### Can the versioning strategy itself be modified?

Yes, the versioning strategy can be modified through the RFC process documented in `website/src/content/docs/governance/rfc/index.mdx`. Changes to how versions are managed, how deprecations are handled, or how the decorator system functions require community review and approval via RFC. This ensures that modifications to the versioning system itself undergo proper scrutiny and maintain backward compatibility where possible.