apistockdocs
v0.4 GitHub apistock.dev
Decisions/Records

Scaffold compatibility and upgrade path

ADR-0016Accepted (2026-09-14)

Status: Accepted (2026-09-14) · Supersedes (with ADR-0015): ADR-0012 · Amended by: ADR-0050 (the merge base is rebuilt from the recorded release and inputs and checked against file hashes, not replayed operations)

Context#

A developer creates an app today and edits it heavily. Two years later apistock has changed significantly. The upgrade must not lose their changes, and must not require the CLI for every small update, or developers stop upgrading.

The merge spike (spikes/merge/README.md) showed that 3-way merges preserve edits, that neighbouring-line edits conflict, and that the merge base must be rebuilt by replaying recorded operations.

Options#

  1. Regenerate and let developers diff manually.
  2. Upgrade guides only.
  3. A layered strategy: library upgrades through go get, a scaffold compatibility promise, bridge releases for majors, and tooled merges for owned code.

Decision#

Option 3.

Scaffold compatibility promise (from 1.0)#

Scaffold code generated by any template vX.Y compiles and behaves correctly against every library vX.Z where Z ≥ Y.

Minor upgrades are therefore a plain go get (for example a Dependabot pull request). aps upgrade for minors is optional polish.

Mechanisms by kind of change#

Change Mechanism
Bug fix, security fix, new behaviour Library release; go get
Renamed or deprecated API Old API kept as a wrapper with //go:fix inline; go fix ./... rewrites callers
Breaking API change (major) Analyzers with suggested fixes shipped by apistock, run by aps upgrade --major
Improved scaffold (template change) 3-way merge: base and theirs rebuilt by replaying apistock.lock operations at old and new versions; runs on a branch
Schema change New migration files copied in; released migrations never change; breaking schema changes split across releases (add + backfill, then remove)

Major upgrade path (example: v1.4 → v2)#

  1. Upgrade to the last v1 release (go get, go fix ./..., optional aps upgrade).
  2. The last v1 release is a bridge release: it already contains the v2 API shapes next to deprecated v1 ones, so the app migrates in small steps while still on v1.
  3. aps upgrade --major on branch aps-upgrade/v2: rewrite import paths, run analyzers, merge scaffolds, copy migrations, then build and test.
  4. Majors can't be skipped.
  5. The previous major receives security fixes for 12 months after the next major ships.

Rules for maintainers#

  • Prefer library changes over template changes; template changes need a changelog entry and an upgrade test.
  • Templates render gofmt-formatted output.
  • Before every release, CI generates the previous release's reference apps, applies scripted hand edits, and upgrades them. Any manual step must appear in the release notes.

Why#

Each kind of change gets its cheapest safe path. The worst case is a merge conflict on a branch; developer code is never overwritten.

Trade-offs#

  • Library design constrained by old scaffolds (the compatibility promise).
  • Significant investment in upgrade tooling and CI before features.

Consequences#

  • Every feature pull request must state how existing apps receive it.
  • apistock.lock must record operations, not only file hashes (ADR-0021).
  • During v0 the promise is best effort; each release documents upgrade steps.
esc
↑↓ move↵ openesc close