Stable Names + Task Contracts: Keep Callers Stable While You Evolve Internals

A practical guide with a tiny JSON Schema, validation tips, and evolution rules—using report.generate as the example.

1 Background and Problem

In fast-moving teams, services evolve often. If callers bind to ad-hoc fields or specific implementations, upgrades break downstream systems.

The fix: To give each reusable capability a Stable Name (e.g., report.generate) and define a Task Contract with JSON Schema. The stable name is the unchanging entry point; the contract is the machine-checkable definition of inputs/outputs/errors. Internals can change freely as long as they continue to honor the contract.


2 Core Ideas


3 Minimal Schema

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "report.generate",
  "type": "object",
  "properties": {
    "period": { "type": "string", "pattern": "^\\d{4}-\\d{2}$" },
    "sourceIds": { "type": "array", "minItems": 1, "items": { "type": "string" } },
    "outputFormat": { "type": "string", "enum": ["pdf", "docx", "md"], "default": "pdf" }
  },
  "required": ["period", "sourceIds"]
}

4 Why This Works


5 Compatibility and Evolution Rules

Backward-compatible (allowed)

Potentially breaking (version it)


Takeaway: Pin the entry point with a Stable Name and pin the boundaries with a Task Contract. Validate in dev/CI/gateway. Evolve additively by default, version when necessary. You’ll move fast internally without toppling your callers.