fix(control-pane): stop a stale poll lowering the staleness mark

The settled-token guard correctly ignored an older poll, but that poll
still set settledPoll to its own token on the way out. With three polls
settling in the order 3, 1, 2, the ignored poll 1 pulled the mark back
to 1, so the even older poll 2 then passed the guard and replaced poll
3's counts, or marked a healthy view offline.

The mark now only moves forward. A poll that settles late is itself
stale, so it has no business moving the mark at all.

Covered by a new runtime case that settles three polls out of order as
3, 1, 2, once with an older success and once with an older failure. The
success case catches the counts regressing, the failure case catches a
healthy view being marked offline. Reverting the fix trips each of them
independently, and the unmutated file passes all 21.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
This commit is contained in:
pasmud
2026-09-27 14:03:13 +00:00
co-authored by CommandCodeBot
parent cee58ba39e
commit d42d918bd3
2 changed files with 54 additions and 2 deletions
@@ -307,10 +307,12 @@ function renderControlPlaneViewHtml() {
// reported as an outage rather than swallowed.
timer = setTimeout(function () { controller.abort(); }, TIMEOUT_MS);
// A poll that has already answered, or timed out, owns the view. Anything
// that settles later is superseded and must be dropped.
// that settles later is superseded and must be dropped. The watermark only
// ever moves forward: a poll that settles late is itself stale, and letting
// it lower the mark would re-admit an even older poll behind it.
function settle() {
clearTimeout(timer);
settledPoll = token;
if (token > settledPoll) settledPoll = token;
}
fetch('/api/control-plane', { signal: controller.signal }).then(function (r) {
if (!r.ok) throw new Error('Control-plane request failed');
+50
View File
@@ -395,6 +395,56 @@ let passed = 0;
`a timed out poll must announce the outage, got ${hung.elements.get('announce').textContent}`);
passed += 1;
// Three polls settling out of order as #3, #1, #2. The ignored #1 must not
// pull the staleness mark back to 1, or the even older #2 would then be let
// through on top of #3's counts.
async function settleOutOfOrder(second, third) {
let releaseFirst, releaseSecond, releaseThird;
const holdOne = new Promise(resolve => { releaseFirst = resolve; });
const holdTwo = new Promise(resolve => { releaseSecond = resolve; });
const holdThree = new Promise(resolve => { releaseThird = resolve; });
const view = await render([
{ ok: true, data: populatedView({ counts: { tasks: 3, lanes: 1, agents: 2, advisories: 1, resolutions: 0 } }), hold: holdOne },
second(holdTwo),
third(holdThree)
]);
// Start all three, then settle them newest first.
await view.pollAgain();
await view.pollAgain();
await view.pollAgain();
releaseThird();
await settle();
releaseFirst();
await settle();
releaseSecond();
await settle();
return view;
}
const newest = { tasks: 3, lanes: 1, agents: 2, advisories: 9, resolutions: 0 };
// The ignored #1 is a success, and the older #2 is also a success.
const outOfOrderSuccess = await settleOutOfOrder(
hold => ({ ok: true, data: populatedView({ counts: { tasks: 3, lanes: 1, agents: 2, advisories: 2, resolutions: 1 } }), hold }),
hold => ({ ok: true, data: populatedView({ counts: newest }), hold })
);
assert.ok(outOfOrderSuccess.labelOf('c').includes('9 advisories')
&& !outOfOrderSuccess.labelOf('c').includes('2 advisories'),
`an older success must not lower the staleness mark, got ${outOfOrderSuccess.labelOf('c')}`);
passed += 1;
// The ignored #1 is a success, and the older #2 is a failure, which would
// otherwise mark a healthy view offline.
const outOfOrderFailure = await settleOutOfOrder(
hold => ({ ok: false, data: { ok: false, error: 'snapshot unavailable' }, hold }),
hold => ({ ok: true, data: populatedView({ counts: newest }), hold })
);
assert.notStrictEqual(outOfOrderFailure.elements.get('status').textContent, 'offline',
'a superseded failure must not mark a healthy view offline');
assert.ok(outOfOrderFailure.labelOf('c').includes('9 advisories')
&& !/unavailable/i.test(outOfOrderFailure.labelOf('c')),
`the newest counts must survive an ignored poll, got ${outOfOrderFailure.labelOf('c')}`);
passed += 1;
console.log(`Results: Passed: ${passed}, Failed: 0`);
})().catch(error => {
console.error(error.message);