Skip to content

Chapter 13: Upstream Product as a Dependency

Wrapping, Patching, and the Upgrade Rehearsal


1. The Three Ways, and Two Are Wrong

A team needs one behaviour changed in an upstream product — a login redirect, a quota. Vendoring the source is the obvious move: the change is two lines, and the copy is in the repository. Two years later they are pinned to a release from 2023, upstream no longer recognises the tree, and every security release since has been a manual merge nobody did. The two-line change has become a permanent role.

That is the failure this chapter is about. The three structures decide how the work is done, and an upstream product is the strongest external constraint on all three. Chapter 11 chose what to bring in, and how you would leave it. This chapter is that choice's hardest case: an upstream product — a whole application you deploy and configure, whose behaviour, data schema and upgrade path are its own. A library is a dependency you can pin; a product is a dependency with opinions.

Three ways to hold it, and two are wrong.

Vendor it. Copy the source into your repository. Now you own the maintenance of a product you did not write, and every upstream release is a manual merge.

Fork it. Take the source and diverge. Now you own a product, its security patches, and the eventual reconciliation.

Wrap it. Keep the product as it ships, and put your behaviour in a layer you own: configuration as code, an override layer, and your data in your own store.

flowchart LR
  U["upstream release"] --> W["your wrapper<br/>config · overrides · data"]
  W --> A["your application"]
  P["your patches — deltas only"] --> W
  P -.->|"drift visible"| U

What this shows: the wrapper is the seam; patches are deltas against a product you still upgrade, not a fork you now maintain.

The rule this chapter defends: wrap the product, patch it only as deltas, and rehearse every upgrade. The day you copy the source into your own tree is the day you became its maintainer.

💰 Cost Check: vendoring feels free and costs a permanent maintenance role; a fork feels decisive and costs the security patches you now owe; wrapping costs an afternoon and a runbook. The expensive choice is the one that looks like ownership without the authority — a copied source tree whose upstream no longer knows you exist.

🎯 What You'll Learn

  • A dependency with opinions — what makes a product different from a library you can pin
  • Wrapping, not vendoring — the three ways to hold a product, and why two make you its maintainer
  • Patch-only forks — deltas, automation, visible drift, and a sync that is rehearsed
  • The upgrade rehearsal — read · prepare · rehearse · verify · sign, and the evidence it leaves

✅ What to Check

  • Is your upstream vendored, forked or wrapped? If the answer is "we copied it in", you are maintaining a product.
  • Where does your data live — the product's store, or yours?

2. Wrapping, Not Vendoring

Part What it holds Where it lives
configuration as code the product's settings, versioned your repository, applied by a command
the override layer your templates, policies, extensions your repository, loaded by the product
your data what the product stores, in a store you control your database, with its own backups

The wrapper is the seam: the part of your estate the product can be cut away from. It is what makes the product replaceable. Everything you would need to reproduce behaviour lives in your repository; the product becomes an engine you can swap, upgrade or drop.

Vendoring loses that. The moment the source is in your tree, your behaviour and the product's are indistinguishable, and the next upgrade is a merge.

✅ What to Check

  • Can you rebuild your behaviour from your repository, with a fresh copy of the product?
  • Is any configuration living only in the product's admin console?

3. Patch-Only Forks

Sometimes the product must be changed — a bug that will not be fixed, a behaviour you cannot wrap. Then the rule is patch-only:

  • deltas, not source — a patch series, or a small set of commits, against a known upstream version;
  • automation, not memory — a build that applies the patches to a fresh upstream checkout;
  • drift visible — a check that fails when the patch series no longer applies;
  • sync rehearsed — an upgrade that re-applies and tests, not a merge resolved by hand.

A patch-only fork stays a dependency. A fork that becomes a branch of the source has become a product, and the maintenance is now yours indefinitely.

✅ What to Check

  • Is the patch series applied by automation, or by a person following notes?
  • Does anything fail when upstream changes a file your patch touches?

4. The Upgrade Rehearsal

An upgrade is the moment an upstream dependency becomes visible to users. It should be boring, and boring is a process:

  1. read the upstream release notes and the diff;
  2. prepare the upgrade — version bumps, config changes, patch re-application;
  3. rehearse it in a preview environment, against your own data;
  4. verify with the same checks that gate a release;
  5. sign — a human accepts the change and its evidence.

What the agent prepares: the release-note summary, the diff analysis, the config delta, the patch re-application, the rehearsal run and its evidence.

What the human signs: the upgrade, the rollback decision, and anything the release notes describe as breaking.

The accelerator rule: compatibility software — an upstream product run for somebody else's convenience — never defines your architecture. If it did, the day upstream changes direction you would migrate an architecture, not a dependency. Keep a migration-slice plan ready: the order in which you would move your behaviour off the product, one slice at a time, and what each slice would need.

✅ What to Check

  • Has an upgrade been rehearsed on real data, in a preview environment?
  • Which of your upstream dependencies would change your architecture if they changed direction?

5. Upstream Governance

An upstream product makes decisions for you by default. Governance is the written answer to three questions:

Question The record
what do we accept from upstream? the licence and supply-chain verdict
what do we refuse, and how? the patch inventory, and the wrapper
what do we document? the upgrade runbook and its last rehearsal

Licence and supply-chain review is part of the decision, not a later legal step: a licence that forbids your use is a build-or-buy answer, and a dependency with no maintainer is a risk you have chosen. The verdict belongs in the decision record — the licence, the maintenance signal and the version you first adopted — beside the five axes, so the next person reads why at the same time as what.

✅ What to Check

  • Is there a patch inventory, and does it match reality?
  • Is the licence verdict recorded, or assumed?

6. Build the Wrapper

Artefacts

Artefact What it is for Where it lives
wrapper config · overrides · data upstream/<product>/
patch inventory every patch, why it exists, where it applies upstream/<product>/patches/README.md
upgrade runbook read · prepare · rehearse · verify · sign the Playbook
rehearsal record one upgrade, with its evidence the evidence pack

The patch inventory, as a shape — one row per patch, with its reason:

# upstream/<product>/patches.yaml
product: <name>   pinned: <version>
patches:
  - id: <slug>
    reason: <the defect or behaviour it addresses>
    applies_to: <file or area>
    retire_when: <what upstream change would make it unnecessary>

Decisions

Choice Default What changes if you choose otherwise
wrap, vendor or fork wrap vendoring makes you the maintainer
a fork patch-only, automated a divergent source tree you own
upgrade cadence rehearsed, on a schedule an upgrade attempted under pressure
architecture influence none the product defines your design

✅ Checks

  • The wrapper rebuilds your behaviour on a fresh upstream copy.
  • The patch series applies by automation, and a non-applying patch fails the build.
  • One upgrade has been rehearsed on real data with its evidence.

Sequence

  1. Write the wrapper — config, overrides, data. Checkpoint: behaviour reconstructs from your repository.
  2. Inventory the patches, with retire conditions. Checkpoint: every patch has a reason and a retirement.
  3. Write the upgrade runbook. Checkpoint: read · prepare · rehearse · verify · sign.
  4. Rehearse one upgrade in a preview environment. Checkpoint: the rehearsal names what broke.

Done When

  • Your behaviour survives a fresh copy of the upstream product.
  • Every patch has a reason and a retirement condition.
  • An upgrade has been rehearsed, verified and signed.

7. 🔑 Chapter Summary

An upstream product is a dependency with opinions — wrap it, patch it deliberately, rehearse the upgrade.

  1. Vendoring and forking both make you the maintainer; wrapping keeps the product replaceable.
  2. The wrapper is config, overrides and your own data — enough to rebuild your behaviour.
  3. Patch-only forks stay dependencies: deltas, automation, visible drift, rehearsed sync.
  4. The upgrade rehearsal is read, prepare, rehearse, verify, sign — with agents preparing and a human signing.
  5. Compatibility software never defines the architecture; keep a migration-slice plan, with the order and the slices written down.