Builds of one module in flight together finish in any order, and the controller took whatever it heard last as what the module is:
takeIn → RegisterModule (internal/inventory/catalogue.go) overwrote module.manifest unconditionally. The resolved manifest carries the image digests the composer deploys, so the last outcome heard won.
Held / BuiltAgainst / ReadRepositories / Announceable (internal/inventory/builds.go) ordered by build.at, which is when the outcome was recorded (that is, when the build finished).
Plans had the same problem: planBuilt marked a module built in every open plan by module name, and settleFromRecords took any build recorded after the ask. So an earlier plan's leftover build could settle a later plan.
Fix
A build is ordered by when it was asked. The controller already writes that time into the correlation id (build-<unix nanos>), and every outcome echoes the id, so no wire change is needed.
link.NewBuildID and link.BuildAskedAt are the only places that write and read that shape. Any other id has no request time and keeps the old order.
Migration 0055 adds build.asked, backfilled from existing ids, and module.built_asked.
RegisterModule only replaces a module whose built_asked is null or no newer. Otherwise it returns ErrSuperseded, and the build is still recorded. A manifest handed over by hand counts as asked now.
Held and the other queries use coalesce(asked, at) desc, at desc.
planBuilt and settleFromRecords ignore a build that was asked before the plan's own ask. The daemon treats a superseded outcome as not a failure.
cmd: TestAnOlderBuildHeardLaterDoesNotReplaceTheNewer, TestABuildIDSaysWhenItWasAsked, plus extended TestAPlanSettlesAnAskedBuildFromTheRecords
With the ordering, the registration guard and the settle filter reverted, all four behavioural tests fail. Full suite passes against postgres 16, apart from the known unrelated TestTheResolverIsToldEveryMachineOnTheNetworkAndToldAgainWhenOneLeaves.
Note: the repo has no consolidated schema, only migrations, so 0055 is the only schema change.
novox/hq issue 219.
## Root cause
Builds of one module in flight together finish in any order, and the controller took whatever it heard last as what the module is:
- `takeIn` → `RegisterModule` (internal/inventory/catalogue.go) overwrote `module.manifest` unconditionally. The resolved manifest carries the image digests the composer deploys, so the last outcome heard won.
- `Held` / `BuiltAgainst` / `ReadRepositories` / `Announceable` (internal/inventory/builds.go) ordered by `build.at`, which is when the outcome was recorded (that is, when the build finished).
- Plans had the same problem: `planBuilt` marked a module built in every open plan by module name, and `settleFromRecords` took any build recorded after the ask. So an earlier plan's leftover build could settle a later plan.
## Fix
A build is ordered by when it was **asked**. The controller already writes that time into the correlation id (`build-<unix nanos>`), and every outcome echoes the id, so no wire change is needed.
- `link.NewBuildID` and `link.BuildAskedAt` are the only places that write and read that shape. Any other id has no request time and keeps the old order.
- Migration 0055 adds `build.asked`, backfilled from existing ids, and `module.built_asked`.
- `RegisterModule` only replaces a module whose `built_asked` is null or no newer. Otherwise it returns `ErrSuperseded`, and the build is still recorded. A manifest handed over by hand counts as asked now.
- `Held` and the other queries use `coalesce(asked, at) desc, at desc`.
- `planBuilt` and `settleFromRecords` ignore a build that was asked before the plan's own ask. The daemon treats a superseded outcome as not a failure.
## Tests
- inventory: `TestAnOlderRequestFinishingLaterIsNotWhatTheModuleHolds`, `TestABuildWithNoKnownRequestTimeIsOrderedByWhenItWasRecorded`, `TestARegistrationFromAnOlderRequestDoesNotReplaceANewerOne`
- cmd: `TestAnOlderBuildHeardLaterDoesNotReplaceTheNewer`, `TestABuildIDSaysWhenItWasAsked`, plus extended `TestAPlanSettlesAnAskedBuildFromTheRecords`
With the ordering, the registration guard and the settle filter reverted, all four behavioural tests fail. Full suite passes against postgres 16, apart from the known unrelated `TestTheResolverIsToldEveryMachineOnTheNetworkAndToldAgainWhenOneLeaves`.
Note: the repo has no consolidated schema, only migrations, so 0055 is the only schema change.
Builds of one module in flight together finish in any order, and the mesh
took whatever it heard last as what the module is: RegisterModule overwrote
the module's manifest unconditionally, and Held/BuiltAgainst/ReadRepositories
ordered builds by when they were recorded. A postgres build asked before the
mesh-tools runtime fix finished after the one asked after it, and the next
push deployed the stale image (novox/hq issue 219).
A build is now ordered by when it was asked, read from the build-<nanos> id
the controller writes: build.asked and module.built_asked (migration 0055).
A registration from an earlier request than the module's current one is
recorded and refused as superseded. A plan takes as its outcome only a build
asked at or after its own ask, so an earlier plan's leftover build cannot
settle a later plan. Ids of any other shape keep the old order.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
novox/hq issue 219.
Root cause
Builds of one module in flight together finish in any order, and the controller took whatever it heard last as what the module is:
takeIn→RegisterModule(internal/inventory/catalogue.go) overwrotemodule.manifestunconditionally. The resolved manifest carries the image digests the composer deploys, so the last outcome heard won.Held/BuiltAgainst/ReadRepositories/Announceable(internal/inventory/builds.go) ordered bybuild.at, which is when the outcome was recorded (that is, when the build finished).planBuiltmarked a module built in every open plan by module name, andsettleFromRecordstook any build recorded after the ask. So an earlier plan's leftover build could settle a later plan.Fix
A build is ordered by when it was asked. The controller already writes that time into the correlation id (
build-<unix nanos>), and every outcome echoes the id, so no wire change is needed.link.NewBuildIDandlink.BuildAskedAtare the only places that write and read that shape. Any other id has no request time and keeps the old order.build.asked, backfilled from existing ids, andmodule.built_asked.RegisterModuleonly replaces a module whosebuilt_askedis null or no newer. Otherwise it returnsErrSuperseded, and the build is still recorded. A manifest handed over by hand counts as asked now.Heldand the other queries usecoalesce(asked, at) desc, at desc.planBuiltandsettleFromRecordsignore a build that was asked before the plan's own ask. The daemon treats a superseded outcome as not a failure.Tests
TestAnOlderRequestFinishingLaterIsNotWhatTheModuleHolds,TestABuildWithNoKnownRequestTimeIsOrderedByWhenItWasRecorded,TestARegistrationFromAnOlderRequestDoesNotReplaceANewerOneTestAnOlderBuildHeardLaterDoesNotReplaceTheNewer,TestABuildIDSaysWhenItWasAsked, plus extendedTestAPlanSettlesAnAskedBuildFromTheRecordsWith the ordering, the registration guard and the settle filter reverted, all four behavioural tests fail. Full suite passes against postgres 16, apart from the known unrelated
TestTheResolverIsToldEveryMachineOnTheNetworkAndToldAgainWhenOneLeaves.Note: the repo has no consolidated schema, only migrations, so 0055 is the only schema change.