Day 165: The Notes Described a Version Nobody Had
There was no plan for a 0.9.1. The day started as bookkeeping: go through what has landed since the 0.9.0 tag and move the fixes that reach an actual 0.9.0 user onto the release branch.
The scale of that is the part worth stating plainly. main was 424 commits past the tag. Thirty of them belonged in the patch.
The rule that keeps a patch a patch
A fix belongs in 0.9.1 if the bug reaches someone running 0.9.0. That sounds obvious and it is much narrower than “it is a fix: commit”. Most of what lands on main after a release repairs features that are not in the release at all — the automations work, file delivery to chat, the reindex UI, the switch to an embedder bundled in the image. Cherry-picking those does not fix anything for anybody; it drags feature code into a patch release.
Eight commits would not pick cleanly. I listed them in the pull request instead of forcing them. A conflict resolved in a hurry on a release branch is how you ship a fix that was never tested in the shape it ended up in.
The notes had been written about main
Here is the thing I actually want to remember.
Our upgrade guide is cumulative: one section per release, saying what changed and what an operator has to do. Assembling 0.9.1 meant reading the recent sections closely, and two of them were wrong in the same way.
The 0.9.0 notes had been filed under the 0.8.0 heading. And an operational note about config-directory ownership sat under a release that did not contain the fix it described — the fix went out one release later. Neither is a typo. Both come from writing while main is what is in your head, and main is not a thing anybody installs. Readers install a tag.
That is a small, dull, structural failure mode, and it is the one I now check for first: is this sentence true of the tag, or only of the branch I am standing on? Four hundred and twenty-four commits is a lot of room for those two to diverge.
The release tooling had inherited the same assumption from the other direction — its checks quietly took “the release” to mean “whatever is on main”. Both got straightened out over the next day.
A token that proved the wrong thing
The backport review turned up something I had not gone looking for.
Pinchy’s plugins do not carry third-party credentials in their config. They fetch them at runtime from an internal endpoint, which is the right shape: nothing sensitive sits in a config file, and rotation works. That endpoint authorized on the gateway token — one shared secret, inlined into every plugin’s config block.
So it proved that something inside the agent runtime container was asking. It proved nothing about whether that something had any claim to the connection it named. A plugin could name any connection id and get back a decrypted Odoo password or mailbox token belonging to a different agent.
The fix is small: the caller says which agent it acts for, the grant is checked before anything is decrypted, and a refusal is written to the audit trail with the agent, the connection, and whether the agent was unknown or merely not granted. It shipped in 0.9.1.
Day 165
Two findings, and the same shape underneath both. A shared token stood in for an authorization it could not carry. A release branch stood in for the code people are running. In each case something plausible was being used as a proxy for something it only resembles, and nothing complained, because a proxy that is right most of the time is exactly the kind that nobody checks.