patchAppliedActionableRevisionStatus reads the EphemeralRunnerSet through
the uncached reader precisely because a cached read cannot be trusted
there, then derived Status.Phase from a cached list of the child runners.
The optimistic lock on the patch covers the EphemeralRunnerSet object
alone, so it could not vouch for that list, while a successful write made
the result look consistent. The list also sits inside RetryOnConflict,
where a cached read can return the same stale data on every attempt.
The ownership filter moves into the process, because resourceOwnerKey is
a client-side index on the manager's cache and the API server rejects
.metadata.controller as an unsupported field label. The predicate lives
next to the indexer so the two do not drift apart.
Add the index to the revision patch test fakes, which need it now that
the function lists children, and a test that separates the two reads:
it fails when the list goes through the cache and passes when it does
not.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The optimistic lock and the monotonicity guard in
patchAppliedActionableRevisionStatus both prevent the applied revision
marker from moving backwards, and neither was covered.
Reproducing the lost update needs two reconciles racing on one object with
a stale cached read, which is awkward to provoke deterministically. The
observable property is cheaper: the emitted status patch must carry a
resourceVersion precondition, since that is what lets the API server reject
a stale write. The test intercepts SubResourcePatch and asserts on the bytes
the reconciler actually emits, so it cannot pass while the patch is built
some other way.
Both assertions were mutation checked. Reverting the patch option to
client.MergeFrom fails the precondition test, reporting the emitted patch
as {"status":{"appliedActionableRevision":7}} with no resourceVersion.
Disabling the >= guard fails the backwards test.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>