Featured image of post The extraction playbook only got real when chat left

The extraction playbook only got real when chat left

I wrote the process document before doing the hard one. Then the hard one went through it and rewrote a step.

I wrote the playbook before I’d done the hard one, which, said out loud, sounds a bit daft.

It didn’t feel daft at the time (they never do), and I’d do it again.

Why there was a playbook at all

Two things existed before it. signing had already left, months earlier, back when extracting a module was a one-off rather than a programme. That gave me the mechanics: a framework-free module implementing a small contract, consumed back by the framework through adapters, with its own CI, its own docs site, its own dependency-footprint guard. It worked. The spec later calls it the validation dry-run, which is generous… at the time it was just “the signing thing”.

Then came the report, which said which packages should go and in what order. Neither of those tells you how.

And I could see what was coming: a dozen extractions, each one re-deciding the same handful of questions. What do we call it? Where do the docs live? What does the repo need before it counts as real? When does the framework cut over? Get that wrong and you don’t get a toolkit, you get a dozen modules that each look like whichever week they were built in.

So on the 12th of July I wrote it down, and the spec is blunt about its own purpose: codify the repeatable process and conventions so every subsequent extraction is consistent and recognisable, rather than re-decided each time. A meta-spec, if you like, whose job is to stop other specs arguing.

Eight steps and a lot of opinions

The procedure came out at eight steps, and they’re deliberately boring:

  1. Bootstrap the repo, CI green on an empty scaffold.
  2. Move the code, and sever anything still reaching back into the framework.
  3. Tests and guards carried over, coverage held above 90%.
  4. Diátaxis docs and a Pages microsite.
  5. Cut v0.1.0.
  6. Framework cut-over in a single MR: add the dependency, delete the in-tree package.
  7. Cross-reference the new microsite from the framework’s docs.
  8. Downstream consumers repoint.

Alongside that, the naming: a subgroup rather than a prefix, so it’s go/chat and not gtb-chat. Bare component names, because recognition comes from the subgroup rather than from stuttering the framework’s name into every import. Docs subdomain mirroring the module path, so go/chat becomes chat.go.phpboyscout.uk and there’s nothing to remember. All very tidy. And all written by a man who had extracted one thing, and an easy one at that.

And then chat

The playbook names pkg/chat as the first greenfield extraction, in its own words. chat is also the package that scored 10 for downstream value and 4 for ease of decoupling, the widest gap in the whole table, and the reason the running order put it last rather than first.

So the first real test of the process was the worst case in the building, not quite by accident, but not by design either, just where the two documents happened to point at each other. And it went through all eight steps: go/chat exists, it’s on its own release line, and its go.mod mentions go-tool-base zero times! That was the whole point, and very much the hard part.

And then it broke step six.

The step that didn’t survive

Step six says: add the module dependency, delete the in-tree package. Open go-tool-base today and pkg/chat is still there.

Ten files of it: config_adapter.go, adapter_helpers.go, providers.go, a doc comment and some constants, each of them importing gitlab.com/phpboyscout/go/chat and doing something to it. I don’t think that’s a failed cut-over so much as a category the playbook didn’t have.

Because a framework that consumes an extracted module still needs somewhere to do the paperwork. go/chat takes typed settings and knows nothing about config containers, hot reload or the environment-prefix rules, and that’s why it’s reusable, but something has to turn the framework’s resolved configuration into those typed settings and that something has to live on the framework’s side of the line. It’s the border pattern again, arriving from a completely different direction and refusing to be optional.

So what step six actually means, now that something hard has been through it, is closer to delete the implementation, keep the border post. The name on the directory stays the same, and what’s inside it changes from a few thousand lines of chat client to a few hundred lines of translation.

I’d write it that early again

The playbook was written too early, and I’d do it again tomorrow. Steps one to five held perfectly: naming, repo scaffold, coverage bar, docs site, version cut. A dozen modules later those are the reason the toolkit looks like one thing rather than twelve, and not one of them has been re-argued since.

What the hard case changed was one step, and it changed it in a way I could never have reasoned my way to in advance. I’d have written “delete the package” every time, because that’s what happened on signing… and signing really did have nothing left to translate.

That isn’t an argument for waiting until you’ve done the hard one, because then you never write it at all and every extraction is a fresh argument. It’s more that a process document is a hypothesis. It looks like a rule, it’s formatted like a rule, people follow it like a rule, but until something difficult has run all the way through it you’re reading a description of the easy case with the confidence of a standard.

chat was the hard one, and it went first through the door marked first greenfield extraction, and it came out the other side having edited the instructions on its way past.

Built with Hugo · Theme Stack designed by Jimmy