CD phase 4 — plugin version skew #14
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Plugin sends its version on the socket URL and an X-Nectenda-Plugin header (CORS allow-lists updated); shard records versions, exposes nectenda_plugin_connections{version}, enforces MIN_PLUGIN_VERSION with close code 4007; plugin shows update-required; CI compat step runs the previous public plugin release against the new server; compatibility rule in docs/releasing.md. Checkpoint: an old plugin gets 4007 and the notice; compat step green.
Done and deployed. Commit
e1fd5fa, which travelled through the phase 3 gate and reached both hosts about 30 seconds after promotion.The wire. The plugin names its build on everything it sends:
X-Nectenda-Pluginon every request to a Nectenda service, and&v=on the WebSocket handshake, where a header cannot be set. The version is substituted into the bundle frommanifest.jsonat build time, so the number is written down once and the bundle carries a literal. Attachment downloads are deliberately excluded: those are presigned bucket URLs with no headers at all, and any extra header turns them into a preflight the bucket refuses.Both CORS allow-lists name the header, and both now have a test that fails when it is removed. A header missing from that list fails the preflight, the request never leaves the browser, and nothing appears in any log on either side. The plan noted this had bitten twice; it is now the kind of thing that cannot bite silently again.
The floor. The shard refuses anything below
MIN_PLUGIN_VERSIONafter the upgrade with close code 4007, and counts live connections per version asnectenda_plugin_connections{version}. Confirmed live on eu1: the metric is exported, and the floor in effect is0.It stays
0. A self-hosted server has no business refusing its owner's client, and there is no public release to raise it to yet. When there is, the metric is what makes raising it a decision about a number rather than a guess about whose sync stops.The plugin's answer to 4007 is update the plugin to keep syncing, said once per episode, with a status bar label of its own and slow retries — an update should resume sync without Obsidian being restarted. Editing continues locally throughout.
Compatibility is checked, not asserted. The gate downloads the previous public release and runs the sign-in and two-vault suites with it against the new server. Every other suite drives the plugin built from the same commit, so a change that breaks yesterday's plugin passes all of them. With no public release yet the step prints that there is nothing to fetch and passes visibly, so a green tick never quietly means the check did not run. I exercised the override it depends on against a real external plugin directory rather than only writing it.
On the checkpoint. "An old plugin gets 4007 and the notice" is proved against a real server through the test harness — real sockets, real upgrade, real close code — and by four mutation checks: the server ignoring the floor, the plugin dropping the 4007 branch, the plugin not naming its version, and the header removed from either allow-list. I did not raise the floor on production to demonstrate it there, because the only clients that exist are your own vaults and cutting them off would buy nothing before a public release.
One thing the tooling caught. My first version put a reference to a private document in a comment in
packages/shared, which is mirrored publicly.build-mirror.mjsrefused to stage it. Worth knowing that check earns its place.922 unit tests green, full multi-vault suite 57 passed.
That closes the continuous-deployment plan: phases 1 to 4 all done.