DSDS is a pre-1.0 draft, maintained by one person, not a working group. Some parts of the schema can grow to cover new cases without a spec change; other parts are locked in. This page is the compatibility contract behind that status — what can change, how you'll be told, and what never changes regardless. For the rules themselves and how each is enforced, see Conformance.

What counts as a breaking change

A change is breaking if a document, tool, or workflow that worked before it ships stops working (or starts failing validation) after — not just a rename or removal. Concretely, all of the following count as breaking, even when they look like tightening rather than deleting:

  • Renaming, removing, or restructuring a field.
  • Adding a new required field to an existing shape.
  • Narrowing an existing constraint — an enum losing a value, a pattern getting stricter, a type narrowing.
  • Adding a new validator rule to scripts/validate.js's DSDS-01–DSDS-11 catalog. A document that validated cleanly yesterday can fail today with no schema change at all, because the new rule now runs against it. This is easy to overlook precisely because no field moved — treat a new conformance rule with the same weight as a schema change, because it has the same effect on existing documents.
  • Any change to what npm run check accepts that a previously-passing document, run today, would fail.

Not breaking: new optional fields, new union members, relaxed constraints, new advisory (lint-docs.js) rules — a warning never blocks a build — and documentation-only edits.

Version semantics
ChangeVersion bumpYour documents
Documentation-only editsnoneUnaffected
Additions and loosenings (new optional fields, new union members, relaxed constraints, new advisory lint rules)patchRemain valid unchanged
Tightenings and behavior changes (new constraints, new required fields, moved fields, a new validator rule)minorMay need edits; the CHANGELOG lists every affected position
Renames and removalsbatched minor, pre-1.0 (major, post-1.0)Migration script provided (scripts/migrate-to-*.js)

Pre-1.0, a minor release can carry a tightening or a breaking change with no deprecation window first — that is what "pre-1.0" commits to here, and it's why the table above says "minor" rather than "major" for a rename. Read the CHANGELOG before upgrading a minor version, not just a major one. Post-1.0, tightenings and removals wait for a major version and follow the deprecation window below; see "The post-1.0 contract."

Deprecation policy

From the point a form is scheduled for removal, DSDS commits to:

  1. Marking it deprecated in the schema — the standard JSON Schema deprecated: true annotation where the shape supports it, a $comment explaining the removal where it doesn't (e.g. a whole shape being replaced).
  2. Naming its replacement in the same schema description or $comment — never just "removed," always "removed, use X instead."
  3. Flagging it via lint-docs.js (a warning, never a build failure) so it shows up in CI before it disappears.
  4. Keeping it valid for at least one further minor release after the deprecation notice ships, before the removal that breaks it.

This is a process commitment, not yet a schema-level machine-readable one — a consumer currently learns "deprecated, replaced by X" from the annotation and description text, not a dedicated replacedBy/removeIn field. That's a planned schema addition; until it ships, the schema text is the source of truth for a field's deprecation status.

Breaking-change windows

Pre-1.0 breaking changes land batched into a named release, not trickled one field at a time across several minors — a single CHANGELOG entry and migration script to read, not several. When a breaking window is being prepared, it's announced on this page (what's changing, and the release it lands in) before that release ships, the same practice earlier 0.15.x releases followed. Each window ships with its own migration script, following scripts/migrate-to-0.20.js's shape: convert what can be converted automatically, preserve anything else under $extensions["com.dsds.migration"], and print a warning for every case that needed a manual call.

No breaking window is currently open. 0.20.0 was itself the last one — the entity/documentBlocks model's replacement — and the plan from here is additive releases (new optional fields, new fixtures, restored tooling) until the 1.0 criteria below are met.

How schema changes get made

Every schema file under schema/ has comments explaining why it's shaped the way it is, and often what it replaced. Reading those comments alongside the schema is the best way to understand how the spec has changed — see the CHANGELOG for exactly how each old field maps to its new one.

Migrating a 0.15.2 document

scripts/migrate-to-0.20.js [--dry-run] converts a 0.15.2 .dsds.json document (the entity/documentBlocks model) to a 0.20.0 .dsds.yaml one, following the CHANGELOG's own field-by-field mapping. It writes a new sibling file rather than overwriting the input, and it's best-effort, not a guarantee: a few 0.15.2 shapes (most notably the api block's inline property/event documentation) have no 0.20.0 equivalent to convert to — 0.20.0 points sourceFiles at real source instead of inlining an extracted API.

Anything the script can't place in a typed field is preserved under the migrated item's own $extensions["com.dsds.migration"] rather than dropped, and every such case — plus every genuinely manual decision, like an alternatives pointer that wasn't really id-shaped in the source, or a ref that pointed at a token-group's own id before its children flattened to top-level entries — prints as a ⚠ line in the script's own output. Run npm run validate on the result afterward.

Designed to grow without a version bump

A handful of fields accept any string matching a pattern, instead of a fixed list of allowed values — so adding a new value never needs a schema change. Safe to build tooling around, but don't treat today's set as complete:

  • entries/token.tokenType — a token's category (color, spacing, typography, …), checked by pattern, not a fixed list.
  • common/ref.rel — a reference's relationship (depends-on, same-as, implements, …), open-ended; new values just get documented in the schema's own comments.
  • metadata.status's status value — stable, experimental, deprecated, and more, open for the same reason.
  • entry.id and common/id — a lowercase-dash-dot pattern, not a fixed list of parts, so ids fit whatever hierarchy your system uses.
  • $extensions — vendor or tool-specific data, grouped by namespace; a tool integration can add a field of its own any time, without waiting on a release.
More likely to require a spec change

A few fields are locked to a fixed list of values, because the number of possible cases is a fact about how the spec itself works, not an open-ended vocabulary. Be defensive about this list, not the one above:

  • entry.kind — five well-known values (system, component, token, theme, entry), four with their own entries/.schema.yaml file, plus a custom dot-separated kind name (e.g. acme.icon-library) for a document that wants its own recognizable name. Adding a well-known value changes what this spec can describe at all, so that bar stays high.
  • common/requirement-level — five values (must, should, should-not, must-not, may), taken directly from RFC 2119. This vocabulary belongs to that standard, not DSDS.
  • sections/* (the four section kinds) — guidelines, definitions, steps, section. Any entry kind can use any of these. section is the generic fallback, the same role entry plays for entries; freeform isn't a section kind at all, it's a field every section kind can carry. The part of the schema most likely to still change before 1.0.
Criteria for declaring 1.0

1.0 is declared when, at minimum:

  1. The kind lists stop changing — across at least one real pass of merging or splitting them, with no further changes needed.
  2. A second independent tool exists — at least one tool the spec authors don't maintain reads or writes DSDS documents for real.
  3. The validator's extra rules stop changing — scripts/validate.js's DSDS-01–DSDS-11 (see the rule catalog) stop being added or renamed release to release.

Until then, the fixed lists above are the most stable part of the schema. Everything else can still change between minor versions, including the exact shape of any one sections/*.schema.yaml file.

Versioned artifacts never change

Whatever else moves, a published version directory doesn't. Every version DSDS has ever shipped stays served at its own immutable URL — /v0.20.0/dsds.bundled.yaml and /v0.20.0/dsds.bundled.schema.json today, and every earlier version at its own path, frozen at the bytes it shipped with. A document that pins schemaVersion and a $schema URL keeps validating against exactly what it was written against, indefinitely.