A module’s version field looks like it should mean “the version of the thing
this module installs.” Today it doesn’t — it’s a hand-typed number that tracks
recipe churn and correlates with nothing a consumer can check. caddy says
2.1.0 while installing some unknown Caddy; authentik says 1.2.0 while
deploying a 2024.6 image. This work makes manifest.yml#version mean one honest
thing: the version of the payload the module delivers, sourced rather than
asserted.
Why the rumour costs you
When the version is a rumour, upstream updates don’t propagate. Bump an app’s pinned upstream from one release to the next and the manifest version doesn’t move, so the registry sees nothing new — and the fleet keeps running the old, possibly-vulnerable software until someone remembers to also hand-bump the manifest. Deploy posture (“fast vs safe by semver delta”) is computed off that same disconnected number, so it’s noise.
A version source per module
Every module will declare how to resolve the version it delivers, and celilo
stamps that into manifest.yml#version and checks it never drifts. There are
three kinds:
pin— wrapper modules likecaddyandforgejoship a small resolver that reads the same pin they deploy, so the version equals what’s installed by construction.changeset— first-party apps author their version via changesets through one standard mechanism they get for free.recipe— payload-less modules stop pretending to have a meaningful semver.
The +N revision continues to carry recipe-only changes, and the capability
contract stays where it already lives — in provides.capabilities[].version —
untouched.
Stamp and check
A new celilo module version verb will run the declared resolver and write the
real version; celilo module check will run it and fail on drift — if you
bumped the pin but not the manifest, or left changesets unapplied. That check
replaces today’s stale-manifest heuristic with one that’s about the real thing.