A licence change, and the fork we did not take

On the twentieth of March a dependency we run everywhere changed to a source-available licence, and a foundation-backed fork was announced within the week. The reflex is to migrate immediately; the useful first step is working out whether anything applies to us at all.

The symptom

what actually happened, in a day:

  09:00  the announcement
  09:40  three messages in the team channel, two of
         them proposing a migration
  11:00  a fork announced by a foundation
  14:00  a blog post arguing the fork is the only
         responsible choice
  16:00  a second blog post arguing the opposite

and at no point did anybody read the licence.

The commentary arrives faster than the analysis and it is not a substitute for it. Every argument in circulation was about what the change means for the ecosystem, and none of it was about whether our specific use is affected — which is a question with a short answer.

Why it happens

A licence change is a business decision by a company that owns the copyright, and it is legitimate whether or not it is popular. The reaction it produces is proportional to how many people depend on the software rather than to how many of them are affected.

The fix

Four questions, in order

  1  does the new licence restrict our use?

     the restriction is on offering the software as a
     managed service to third parties. we run it as
     internal infrastructure behind our own
     application.

     → it does not apply. read from the licence text,
       not from a summary.

  2  is the version we run affected?

     the change is not retroactive. every release up to
     and including 7.2.4 remains under the old licence,
     permanently.

     → no.

  3  what changes if we do nothing?

     future releases, under the new terms. security
     patches for the version we run: unclear, and that
     is the actual risk.

  4  is there a fork, and is it real?

     six weeks old at the time of asking.
     → not yet answerable.

The third question is the one with teeth. The licence not applying to our use is comforting and irrelevant if the version we run stops receiving security patches, which is a question about the maintainers’ behaviour rather than about the licence text.

Reading the licence rather than the commentary

the operative clause, paraphrased and then checked
against the text:

  you may not make the functionality of the software
  available to third parties as a managed service.

our use:
  one instance, on our own hosts, used by our own
  application, not exposed to anybody.

the grey area we do NOT have:
  a multi-tenant platform where customers get their own
  cache. that would need a lawyer.

time spent: 40 minutes, including finding the licence
text rather than a summary of it.

What a migration would actually involve

the fork is protocol-compatible, which sounds like a
drop-in replacement and is half of one:

  the protocol            identical. our client works
                          unchanged.
  the container image     available, different name.
  the configuration       identical.
  the client library      ours works. the one used by
                          a package we depend on has
                          its own fork, at a different
                          version.
  the managed offering
  at our host             does not exist yet.
  the tooling             a monitoring exporter we use
                          has no fork support.

so: an afternoon, plus two unknowns.

Protocol compatibility means the migration is a configuration change and the ecosystem around it is what takes time. The monitoring exporter is the concrete blocker — it identifies the server by an INFO field that the fork changed, and a dashboard that reports nothing is not an acceptable outcome of an unnecessary migration.

The decision, and the trigger

# ADR-023: Stay on the current cache server

## Status
Accepted, 2024-04-19.

## Decision
Pin 7.2.4. Do not migrate. Revisit 2024-10.

## Triggers that change this before October
- a CVE in 7.2.x with no patch under the old licence
- our hosting provider dropping the managed offering
- the fork reaching a stable release with exporter
  support (checked monthly, automatically)
- a business requirement to offer per-tenant caches

## What we are NOT deciding
Whether the licence change was justified.

The triggers are what make this a decision rather than a deferral, and the last section is what keeps the record readable — a decision document that takes a position on the ethics of the change is a document that will be argued with rather than acted on.

The compatibility test we added

/**
 * Runs the full cache integration suite against both
 * servers. Scheduled monthly; not part of CI.
 */
public function testTheCacheContractHoldsAgainstBothServers(): void
{
    foreach (['redis://cache:6379', 'redis://fork:6379'] as $dsn) {
        $store = new PhpRedisStore($this->connect($dsn));

        $this->assertCacheContract($store);
    }
}

Running the contract suite against both monthly turns an assumption about compatibility into a measurement, and it costs a container in a scheduled job. By October it had run six times with no failures, which is what made the eventual answer — still not migrating, and the option is real — something more than a guess.

Verifying it worked

$ grep -n 'image: redis' docker-compose.yml
  image: redis:7.2.4@sha256:8c1f4a7e...

$ ./bin/check-adr-triggers ADR-023
  CVE in 7.2.x unpatched:        no
  managed offering dropped:      no
  fork stable + exporter:        fork stable yes,
                                 exporter no
  per-tenant requirement:        no
  → no trigger fired. next review 2024-10.

$ vendor/bin/phpunit --group=cache-compat
  Tests: 41, both servers.  OK

A script that checks the triggers is what stops a six-month review being a calendar entry somebody dismisses. Three of the four are answerable automatically; the fourth is a business question and is a prompt rather than a check.

What this costs

A decision that is deferred rather than made, which is the honest description. Pinning a version means the pin is now a thing that has to be justified at every review, and the pressure to just migrate and stop thinking about it will grow rather than shrink.

The monthly compatibility run is also a container and a scheduled job that exists to keep an option open, which is a cost with no benefit until the option is exercised. If it never is, that is six years of a job nobody needed — and the alternative is discovering the incompatibility during the migration that a CVE forced.