[client-reliability] client-reliability-9 cancel ownerless active HEAD probes

This commit is contained in:
thePR0M3TH3AN committed 2026-10-22 10:26:08 -04:00
1 parent e21be27f34
commit e9c960edcf
7 files changed
+654 -10

No files matched your search

@@ -0,0 +1 @@
Client9 HEAD-only candidate ready: controller cancel hook/abort+raced prompt settlement/timer identity cleanup;23new HEAD+37existing scenarios pass,16failing isolated mutants/cancelled0,lint11 pass. No existing test expectation edits. Explicit strict contract/integrity notes. Next commit candidate, merge current unstable, request full/browser slot; do not submit or run expensive jobs without grant. HLS/torrent separately open.
@@ -0,0 +1,11 @@
# Active HEAD owner cancellation
Source: docs/performance-efficiency-plan.md Stage3, campaign2026-10-22 priority2, fleet/RULE-CHANGES.md2026-10-22 and explicit dispatch routing56417d11 durably landed e04549f8.
Old scoped behavior: queued work and active media cancel on final-owner disposal, but active HEAD fetch remains until response/error/timeout. A fresh observer may join that obsolete inflight record. Controlled11-mode reproduction independently confirmed3 failures/8 controls.
New contract: final registered owner loss invalidates active HEAD synchronously, requests transport abort when supported and promptly settles the observer chain null independently of transport cooperation. HEAD timer and cancellation hook are released. Same-turn/later consumers start fresh work; obsolete response/error/timer/finalizer cannot affect replacement, cache, badges, media or mirrors. Unsupported AbortController still permits logical settlement and cleanup, but cannot promise transport abort.
Preserve shared and cross-controller ownership, legacy consumers, live HEAD success/error/timeout/opaque/bad-status media fallback and cache, media-hook handoff and FIFO/cap. Existing29 media lifecycle scenarios and4+4 adjacent controller/multisource scenarios remain unchanged. No retained assertion was relaxed or removed;23 new HEAD scenarios tighten previously uncovered behavior. This is a scope extension, not retroactive proof of HEAD cancellation by item8.
Only js/ui/urlHealthController.js HEAD phase changes. Observer, cache, app, HLS, torrent, RangeGET and limits/TTLs unchanged. HLS/torrent remain separately open. No browser/real network bytes/fullStage3 qualification.
+38 -10
View File
@@ -562,16 +562,44 @@ export default class UrlHealthController {
return result;
};
const lifecycle = options.lifecycle;
if (lifecycle && (lifecycle.cancelled || !lifecycle.owners.size)) {
lifecycle.cancelled = true;
return { outcome: "cancelled" };
}
const supportsAbort = typeof AbortController !== "undefined";
const controller = supportsAbort ? new AbortController() : null;
let timeoutId = null;
let resolveCancellation;
const cancellation = new Promise((resolve) => {
resolveCancellation = resolve;
});
const cancelActive = () => {
// Invalidate before resolving: same-turn rejoin must start fresh work.
lifecycle.cancelled = true;
resolveCancellation({ outcome: "cancelled" });
// Settlement must not depend on the transport observing this signal.
controller?.abort();
};
if (lifecycle) lifecycle.cancelActive = cancelActive;
const cleanupHead = () => {
if (timeoutId) clearTimeout(timeoutId);
if (lifecycle?.cancelActive === cancelActive) {
lifecycle.cancelActive = null;
}
};
const racers = [
fetch(trimmed, {
method: "HEAD",
mode: "no-cors",
cache: "no-store",
signal: controller ? controller.signal : undefined
cancellation,
new Promise((resolve) => {
resolve(
fetch(trimmed, {
method: "HEAD",
mode: "no-cors",
cache: "no-store",
signal: controller ? controller.signal : undefined
})
);
})
];
@@ -596,9 +624,8 @@ export default class UrlHealthController {
try {
responseOrTimeout = await Promise.race(racers);
} catch (err) {
if (timeoutId) {
clearTimeout(timeoutId);
}
cleanupHead();
if (lifecycle?.cancelled) return { outcome: "cancelled" };
this.logger.warn(`[probeUrl] HEAD request failed for ${trimmed}:`, err);
const fallback = await confirmWithVideoElement();
if (fallback) {
@@ -609,8 +636,9 @@ export default class UrlHealthController {
: { outcome: "unknown", error: err };
}
if (timeoutId) {
clearTimeout(timeoutId);
cleanupHead();
if (lifecycle?.cancelled || responseOrTimeout?.outcome === "cancelled") {
return { outcome: "cancelled" };
}
if (responseOrTimeout && responseOrTimeout.outcome === "timeout") {
@@ -0,0 +1,73 @@
# HEAD lifecycle integrity
```yaml
test_integrity_note:
change_type: ["new_tests", "spec_correction"]
scenarios:
- id: SCN-head-final-owner
given: "actual observer/controller HEAD with abortable, ignoring-abort or missing AbortController transport"
when: "final owner disposes, including duplicate disposal and already-completed HEAD boundary"
then: "prompt null settlement, timer cleanup, no cache/media/mirror/stale badge; abort if supported"
- id: SCN-head-replacement
given: "same-turn or later rejoin after disposal"
when: "obsolete success, error or timeout callback runs"
then: "fresh HEAD and inflight identity preserved, fresh healthy result only reaches live badge"
- id: SCN-head-sharing
given: "same-controller, cross-controller or legacy live survivor"
when: "other observer disposes then HEAD/media completes"
then: "one un-aborted HEAD, survivor gets healthy cache/badge, disposed badge unchanged"
- id: SCN-head-live-fallback
given: "live HEAD success/error/timeout/opaque/405 or failing media and mirror"
when: "controlled transport/media settles"
then: "healthy fallback or honest offline cache, timers/media slots released"
- id: SCN-head-handoff-fifo
given: "HEAD transitioning to media or saturated real media queue"
when: "owner leaves or live media releases slots"
then: "new media cancellation hook works, live FIFO and cap3 remain intact"
- id: SCN-head-before-start
given: "lifecycle already cancelled or without owners"
when: "real probeUrl entry is called"
then: "cancelled result, no fetch/media/timer"
observable_outcomes:
- "fetch count/AbortSignal, settled value, timer/source/listener cleanup"
- "real cache/inflight identity, live/disposed badges and media queue/FIFO"
determinism_controls:
- "controlled fetch with native AbortSignal and optional ignored abort"
- "manually driven positive-delay timers, native zero-delay JSDOM tasks"
- "actual observer/controller/cache/DOM media events; external network never used"
- "cleanup completes controlled transports/media and restores globals before natural exit"
anti_cheat_rationale:
prevents:
- "abort signal without prompt settlement"
- "null-only fake fix retaining transport or timer"
- "reuse of obsolete request or cache-on-cancel"
- "dropping shared/legacy owners or fallback/FIFO behavior"
- "stale inflight finalizer deleting fresh work"
- "retry/sleep/assertion relaxation"
relaxation:
did_relax_any_assertion: false
if_true_explain_spec_basis: ""
```
Spec scope extension: docs/spec_changes/2026-10-22-client-reliability-9-active-head.md. No existing test was changed. Original controller fails12/23 assertions and passes11 controls, cancelled0. Fixed controller23/23. All16 isolated mutants exit1 with actual failed assertions/cancelled0; production source never contains a mutant. Isolated cache-finalizer mutant changes a scratch copy only.
| Mutant | Failed scenarios | Pass | Exit |
|---|---:|---:|---:|
| baseline | 12 | 11 | 1 |
| no-head-hook | 10 | 13 | 1 |
| no-head-abort | 2 | 21 | 1 |
| no-prompt-settlement | 8 | 15 | 1 |
| no-sync-invalidation | 3 | 20 | 1 |
| retained-head-timer | 21 | 2 | 1 |
| no-prestart-guard | 2 | 21 | 1 |
| lost-shared-owner | 3 | 20 | 1 |
| cache-on-cancel | 12 | 11 | 1 |
| no-live-error-fallback | 1 | 22 | 1 |
| lost-media-hook | 1 | 22 | 1 |
| healthy-cache-corrupted | 14 | 9 | 1 |
| no-live-timeout-abort | 1 | 22 | 1 |
| no-mirror-fallback | 1 | 22 | 1 |
| fifo-reversed | 1 | 22 | 1 |
| stale-inflight-finalizer | 3 | 20 | 1 |
Mutant generation/reproduction: /tmp/client9-run-mutants.py; per-mutant source/test/TAP under /tmp/client9-mutant-*.mjs/.tap; structured /tmp/client9-mutants.json. Changes disable HEAD hook/abort/prompt settlement/synchronous marker/timer cleanup/prestart guard, lose shared owner, cache on cancellation, lose live error fallback/media hook/timeout abort/mirror fallback, corrupt healthy cache, reverse FIFO and remove cache finalizer identity guard. Baseline uses pre-change git controller. Every scenario is rejected by at least one mutant (grouped coverage); none survived. No forced termination, retries or waits for real timeout.
@@ -0,0 +1,12 @@
# HEAD-only candidate verification
- Test parent e21be27f34402bb274fa698b721dc01a0fa12623 (merge of notes145c09f7 and routing unstablee04549f8). Candidate source/tests subsequently committed byte-for-byte; no full/build/browser/product submission yet.
- `node_modules/.bin/prettier --write tests/url-health-head-lifecycle.test.mjs js/ui/urlHealthController.js`: formatting complete; product38+/10-, focused new suite23 scenarios. `git diff --check` exit0.
- Initial `env -u NODE_TEST_CONTEXT node --test tests/url-health-head-lifecycle.test.mjs` baseline/fixed produced file-level1 summary only (exit1/0); not used for scenario counts. Switched to direct Node per repo runner. Initial before-start baseline awaited blocked transport and cancelled3 tests; changed test to first assert no transport after draining continuations so broken variants fail assertions and clean naturally. No expectation relaxed.
- Final original-controller reproduction: /tmp/client9-baseline-tests.mjs imports isolated pre-change controller with absolute dependency imports. `env -u NODE_TEST_CONTEXT node /tmp/client9-baseline-tests.mjs`: exit1,23 total/11pass/12fail/0cancelled. /tmp/client9-direct-baseline.tap. Fixed direct23/23 exit0, /tmp/client9-direct-fixed.tap (before last test-only no-start assertion order adjustment); authoritative final focused run below covers final bytes.
- `env -u NODE_TEST_CONTEXT node scripts/run-targeted-tests.mjs tests/url-health-head-lifecycle.test.mjs tests/url-health-lifecycle.test.mjs tests/url-health-multisource.test.mjs tests/ui/url-health-controller.test.mjs`: /tmp/client9-focused.tap shows23+29+4+4 pass but initial concurrent tool completion metadata exit2 was inconsistent with all child results and runner success. Retained that observation; authoritative host rerun clears shard context too.
- `env -u NODE_TEST_CONTEXT -u UNIT_TEST_SHARD node scripts/run-targeted-tests.mjs tests/url-health-head-lifecycle.test.mjs tests/url-health-lifecycle.test.mjs tests/url-health-multisource.test.mjs tests/ui/url-health-controller.test.mjs`: approved host exit0,4files/60scenarios pass,0failed/0cancelled. /tmp/client9-focused-host.tap. Existing29+4+4 unchanged.
- `env -u NODE_TEST_CONTEXT npm run lint`: initial sandbox /tmp/client9-lint.log failed at git spawnEPERM. Approved identical host run /tmp/client9-lint-host.log exit0,11lint checks completed. No suppression/config/baseline edit.
- `python3 /tmp/client9-run-mutants.py`: final16 isolated mutants, each exit1/assertionfail>0/cancelled0, all natural exits. /tmp/client9-mutants.json and per-mutant TAP. Initial no-head-hook mutant accidentally targeted media due substring indentation; corrected to HEAD suffix and reran all16. Coverage and counts in integrity note. No product file mutated for experiments.
- Measured new contract: cancelled HEAD prompt null despite uncooperative/noAbort transport; timer0; fresh same-turn and later request; late success/error/timer/replacement identity; shared/cross/legacy live continuation; live fallback/cache; handoff cancellation; media cap3/FIFO preserved. Controller HEAD-only; no observer/cache/app/HLS/torrent edits.
- Unverified: unchanged full normal gate, build/artifact/browser/native abort/network bytes, real playback/CPU, HLS/torrent active cancellation/wholeStage3, remote open PR overlap (prior DNS exit6). No product queue submit or expensive-slot acquisition. Dispatch routing required for next gates.
+513
View File
@@ -0,0 +1,513 @@
import test from "node:test";
import assert from "node:assert/strict";
import { JSDOM } from "jsdom";
// Resolve real cache dependencies with installed Node packages before DOM setup.
import * as cache from "../js/state/cache.js";
import UrlHealthController from "../js/ui/urlHealthController.js";
import { attachUrlHealthBadges } from "../js/urlHealthObserver.js";
import {
setCardLivenessPolicy,
getCardLivenessPolicy
} from "../js/constants.js";
import { updateVideoCardSourceVisibility } from "../js/utils/cardSourceVisibility.js";
const flush = () => new Promise((resolve) => setImmediate(resolve));
let sequence = 0;
function fixture(t, { ignoreAbort = false, missingAbort = false } = {}) {
const dom = new JSDOM("<!doctype html><body></body>", {
url: "https://example.invalid"
});
const names = [
"window",
"document",
"HTMLElement",
"localStorage",
"fetch",
"IntersectionObserver",
"requestAnimationFrame",
"setTimeout",
"clearTimeout",
"AbortController"
];
const saved = names.map((key) => [
key,
Object.getOwnPropertyDescriptor(globalThis, key)
]);
Object.assign(globalThis, {
window: dom.window,
document: dom.window.document,
HTMLElement: dom.window.HTMLElement,
localStorage: dom.window.localStorage
});
if (missingAbort) globalThis.AbortController = undefined;
const policy = getCardLivenessPolicy();
setCardLivenessPolicy("show-pending");
const resources = new Set(),
listeners = new Map(),
handlers = new Map(),
pauses = [];
const observers = [],
media = [],
heads = [],
promises = [],
warnings = [],
registrations = [],
visibility = [],
checks = [];
const id = (name) => `url-lifecycle-${++sequence}-${name}`;
const url = (name) => `https://media.invalid/${name}.mp4`;
globalThis.IntersectionObserver = class {
constructor(callback) {
this.callback = callback;
this.cards = [];
observers.push(this);
}
observe(card) {
this.cards.push(card);
}
takeRecords() {
return [];
}
disconnect() {
this.disconnected = true;
}
show() {
this.callback(
this.cards.map((target) => ({
target,
isIntersecting: true,
intersectionRatio: 1
}))
);
}
};
const timers = new Map(),
nativeSetTimeout = globalThis.setTimeout,
nativeClearTimeout = globalThis.clearTimeout;
globalThis.setTimeout = (fn, ms) => {
if (!ms) return nativeSetTimeout(fn, ms);
const handle = {};
timers.set(handle, { fn, ms });
return handle;
};
globalThis.clearTimeout = (handle) => {
if (!timers.delete(handle)) nativeClearTimeout(handle);
};
globalThis.fetch = (source, options) =>
new Promise((resolve, reject) => {
const head = { source, options, resolve, reject, aborted: 0 };
heads.push(head);
options.signal?.addEventListener(
"abort",
() => {
head.aborted++;
if (!ignoreAbort) reject(new DOMException("Aborted", "AbortError"));
},
{ once: true }
);
});
const proto = dom.window.HTMLMediaElement.prototype;
const addListener = proto.addEventListener,
removeListener = proto.removeEventListener;
proto.addEventListener = function (type, fn, options) {
if (["loadeddata", "canplay", "error"].includes(type)) {
if (!listeners.has(this)) listeners.set(this, new Map());
if (!handlers.has(this)) handlers.set(this, new Map());
listeners.get(this).set(type, fn);
handlers.get(this).set(type, fn);
}
return addListener.call(this, type, fn, options);
};
proto.removeEventListener = function (type, fn, options) {
listeners.get(this)?.delete(type);
return removeListener.call(this, type, fn, options);
};
proto.load = function () {
if (this.getAttribute("src")) {
if (!media.includes(this)) media.push(this);
resources.add(this);
} else resources.delete(this);
};
proto.pause = function () {
pauses.push(this);
};
const controller = new UrlHealthController({
state: {
...cache,
setInFlightUrlProbe(...args) {
promises.push(args[2]);
return cache.setInFlightUrlProbe(...args);
}
},
logger: {
warn(...args) {
warnings.push(args);
}
},
utils: {
updateVideoCardSourceVisibility(card) {
visibility.push(card);
updateVideoCardSourceVisibility(card);
}
}
});
function feed(
videos,
{
container = document.createElement("section"),
consumer = controller
} = {}
) {
container.replaceChildren();
for (const video of videos) {
const card = document.createElement("article");
card.className = "card";
card.dataset.videoId = video.id;
card.dataset.urlHealthEventId = video.id;
card.dataset.urlHealthUrl = encodeURIComponent(video.url);
card.dataset.urlHealthState = "checking";
card.dataset.streamHealthState = "unsupported";
const badge = document.createElement("span");
badge.dataset.urlHealthState = "checking";
card.append(badge);
container.append(card);
updateVideoCardSourceVisibility(card);
}
document.body.append(container);
const registration = { container, videos };
registration.dispose = attachUrlHealthBadges(
container,
({ badgeEl, url, eventId }) => {
checks.push(eventId);
consumer.handleUrlHealthBadge({
video: videos.find((v) => v.id === eventId),
url,
badgeEl
});
}
);
registration.observer = observers.at(-1);
registration.cards = [...container.children];
registration.badges = registration.cards.map(
(card) => card.firstElementChild
);
registrations.push(registration);
return registration;
}
const video = (name) => {
const key = id(name);
return { id: key, url: url(key) };
};
const done = (element, event = "loadeddata") =>
element.dispatchEvent(new dom.window.Event(event));
async function finishAll() {
// Event-driven drain: each iteration completes the currently created media.
// No wall-clock delay or retry of a failed assertion.
for (const head of heads)
head.resolve({ ok: true, status: 200, type: "basic" });
await flush();
let handled = 0;
while (handled < media.length) {
for (const element of media.slice(handled)) {
done(element);
handled++;
}
await flush();
}
await Promise.all(promises);
await flush();
}
t.after(async () => {
for (const registration of registrations) registration.dispose();
await finishAll();
dom.window.close();
setCardLivenessPolicy(policy);
for (const [key, descriptor] of saved) {
if (descriptor) Object.defineProperty(globalThis, key, descriptor);
else delete globalThis[key];
}
});
return {
dom,
resources,
listeners,
handlers,
pauses,
controller,
media,
heads,
timers,
promises,
warnings,
visibility,
checks,
feed,
video,
done,
finishAll
};
}
// All transport and time boundaries are controlled; ownership, cache, observer,
// controller, DOM events and media queue are real. Flush drains continuations,
// never a wall-clock wait or assertion retry.
const inFlight = (v) =>
cache.getInFlightUrlProbe(v.id, v.url, { confirmPlayable: true });
const cached = (v) => cache.getCachedUrlHealth(v.id, v.url);
function tracking(promise) {
const result = { settled: false };
promise.then((value) => Object.assign(result, { settled: true, value }));
return result;
}
async function start(f, name = "head") {
const v = f.video(name);
v.sources = [{ url: `${v.url}?mirror` }];
const old = f.feed([v]);
old.observer.show();
await flush();
assert.equal(f.heads.length, 1);
assert.equal(f.media.length, 0);
return { v, old, promise: f.promises[0], result: tracking(f.promises[0]) };
}
function cleanCancelled(f, v, old, result) {
assert.equal(result.settled, true, "cancelled HEAD must settle promptly");
assert.equal(result.value, null);
assert.equal(cached(v), null);
assert.equal(inFlight(v), null);
assert.equal(f.timers.size, 0, "no HEAD or media timer retained");
assert.equal(f.heads.length, 1, "no obsolete mirror");
assert.equal(f.media.length, 0, "no obsolete media fallback");
assert.equal(f.controller.activeVideoProbes, 0);
assert.equal(old.badges[0].dataset.urlHealthState, "checking");
}
for (const transport of ["abortable", "ignores-abort", "missing-abort"]) {
test(`last HEAD owner disposal releases promise/timer with ${transport} transport`, async (t) => {
const f = fixture(t, {
ignoreAbort: transport === "ignores-abort",
missingAbort: transport === "missing-abort"
});
const { v, old, result } = await start(f);
assert.equal(f.timers.size, 1);
old.dispose();
old.dispose();
await flush();
assert.equal(f.heads[0].aborted, transport === "missing-abort" ? 0 : 1);
cleanCancelled(f, v, old, result);
// Even a transport that never settles its own promise cannot block release.
});
}
for (const timing of ["same-turn", "after-flush"]) {
for (const late of ["success", "error", "timeout"]) {
test(`HEAD ${timing} rejoin has fresh identity despite late ${late}`, async (t) => {
const f = fixture(t, { ignoreAbort: true });
const { v, old, promise, result } = await start(f);
const oldTimer = [...f.timers.values()][0].fn;
old.dispose();
if (timing === "after-flush") await flush();
const live = f.feed([v]);
live.observer.show();
const replacement = inFlight(v);
assert.notEqual(replacement, promise, "same-turn owner must start fresh");
await flush();
assert.equal(f.heads.length, 2);
assert.equal(result.settled, true);
assert.equal(result.value, null);
assert.equal(
inFlight(v),
replacement,
"old finalizer preserves replacement"
);
if (late === "success")
f.heads[0].resolve({ ok: true, status: 200, type: "basic" });
if (late === "error") f.heads[0].reject(new Error("obsolete HEAD error"));
if (late === "timeout") oldTimer();
await flush();
assert.equal(inFlight(v), replacement);
assert.equal(cached(v), null);
assert.equal(f.media.length, 0);
assert.equal(f.timers.size, 1, "only replacement timer remains");
assert.equal(old.badges[0].dataset.urlHealthState, "checking");
f.heads[1].resolve({ ok: true, status: 200, type: "basic" });
await flush();
assert.equal(f.media.length, 1);
f.done(f.media[0]);
await flush();
assert.equal(cached(v).status, "healthy");
assert.equal(live.badges[0].dataset.urlHealthState, "healthy");
assert.equal(old.badges[0].dataset.urlHealthState, "checking");
assert.equal(f.timers.size, 0);
assert.equal(f.controller.activeVideoProbes, 0);
});
}
}
for (const kind of ["shared", "cross-controller", "legacy"]) {
test(`HEAD disposal preserves ${kind} survivor and live media handoff`, async (t) => {
const f = fixture(t);
const { v, old, result } = await start(f);
const consumer =
kind === "cross-controller"
? new UrlHealthController({ state: cache, logger: { warn() {} } })
: f.controller;
let live;
if (kind === "legacy") {
const badge = document.createElement("span");
document.body.append(badge);
consumer.handleUrlHealthBadge({ video: v, url: v.url, badgeEl: badge });
live = { badges: [badge] };
} else {
live = f.feed([v], { consumer });
live.observer.show();
}
await flush();
old.dispose();
await flush();
assert.equal(f.heads.length, 1);
assert.equal(f.heads[0].aborted, 0);
assert.equal(result.settled, false);
assert.equal(f.timers.size, 1);
f.heads[0].resolve({ ok: true, status: 200, type: "basic" });
await flush();
assert.equal(f.media.length, 1);
f.done(f.media[0]);
await flush();
assert.equal(cached(v).status, "healthy");
assert.equal(live.badges[0].dataset.urlHealthState, "healthy");
assert.equal(old.badges[0].dataset.urlHealthState, "checking");
assert.equal(f.timers.size, 0);
});
}
for (const response of [
"success",
"error",
"timeout",
"opaque",
"bad-status"
]) {
test(`live HEAD ${response} retains playable media fallback/cache`, async (t) => {
const f = fixture(t);
const { v, old, result } = await start(f);
if (response === "error")
f.heads[0].reject(new Error("controlled HEAD error"));
else if (response === "timeout") [...f.timers.values()][0].fn();
else
f.heads[0].resolve({
ok: response !== "bad-status",
status: response === "bad-status" ? 405 : 200,
type: response === "opaque" ? "opaque" : "basic"
});
await flush();
assert.equal(f.media.length, 1);
assert.equal(f.timers.size, 1);
f.done(f.media[0]);
await flush();
assert.equal(result.settled, true);
assert.equal(cached(v).status, "healthy");
assert.equal(old.badges[0].dataset.urlHealthState, "healthy");
assert.equal(f.heads[0].aborted, response === "timeout" ? 1 : 0);
assert.equal(f.timers.size, 0);
assert.equal(f.controller.activeVideoProbes, 0);
});
}
test("HEAD completion immediately before disposal never starts media or mirrors", async (t) => {
const f = fixture(t);
const { v, old, result } = await start(f);
f.heads[0].resolve({ ok: true, status: 200, type: "basic" });
old.dispose();
await flush();
cleanCancelled(f, v, old, result);
});
test("HEAD to media handoff retains the new cancellation hook", async (t) => {
const f = fixture(t);
const { v, old, result } = await start(f);
f.heads[0].resolve({ ok: true, status: 200, type: "basic" });
await flush();
assert.equal(f.media.length, 1);
assert.equal(f.controller.activeVideoProbes, 1);
old.dispose();
await flush();
assert.equal(result.settled, true);
assert.equal(result.value, null);
assert.equal(f.resources.size, 0);
assert.equal(f.listeners.get(f.media[0]).size, 0);
assert.equal(f.timers.size, 0);
assert.equal(f.controller.activeVideoProbes, 0);
assert.equal(cached(v), null);
assert.equal(f.heads.length, 1);
});
test("HEAD cancellation during saturated media queue preserves live FIFO and cap", async (t) => {
const f = fixture(t),
videos = ["a", "b", "c", "queued1", "queued2"].map(f.video);
const live = f.feed(videos);
live.observer.show();
await flush();
for (const head of f.heads)
head.resolve({ ok: true, status: 200, type: "basic" });
await flush();
assert.equal(f.media.length, 3);
assert.equal(f.controller.activeVideoProbes, 3);
assert.equal(f.controller.videoProbeQueue.length, 2);
const obsolete = f.video("obsolete"),
old = f.feed([obsolete]);
old.observer.show();
await flush();
const cancelled = tracking(f.promises.at(-1));
old.dispose();
await flush();
assert.equal(cancelled.settled, true);
assert.equal(cancelled.value, null);
assert.equal(f.controller.activeVideoProbes, 3);
assert.equal(f.controller.videoProbeQueue.length, 2);
f.done(f.media[0]);
await flush();
assert.equal(f.media.length, 4);
assert.equal(f.media[3].getAttribute("src"), videos[3].url);
assert.equal(f.controller.activeVideoProbes, 3);
f.done(f.media[1]);
await flush();
assert.equal(f.media.length, 5);
assert.equal(f.media[4].getAttribute("src"), videos[4].url);
await f.finishAll();
assert.equal(f.controller.activeVideoProbes, 0);
assert.equal(cached(obsolete), null);
assert.equal(f.timers.size, 0);
});
for (const state of ["no-owners", "already-cancelled"]) {
test(`HEAD ${state} lifecycle starts no fetch or timer`, async (t) => {
const f = fixture(t),
lifecycle = {
owners: new Set(),
cancelled: state === "already-cancelled"
};
if (state === "already-cancelled") lifecycle.owners.add({ active: true });
const promise = f.controller.probeUrl("https://media.invalid/before.mp4", {
confirmPlayable: true,
lifecycle
});
f.promises.push(promise);
await flush();
assert.equal(f.heads.length, 0);
assert.equal(f.timers.size, 0);
assert.equal(f.media.length, 0);
assert.deepEqual(await promise, { outcome: "cancelled" });
});
}
test("live HEAD and failed media still try one mirror then cache offline", async (t) => {
const f = fixture(t);
const { v, old, result } = await start(f);
f.heads[0].resolve({ ok: false, status: 405, type: "basic" });
await flush();
assert.equal(f.media.length, 1);
f.done(f.media[0], "error");
await flush();
assert.equal(f.heads.length, 2);
assert.equal(f.heads[1].source, v.sources[0].url);
f.heads[1].resolve({ ok: false, status: 503, type: "basic" });
await flush();
assert.equal(f.media.length, 2);
f.done(f.media[1], "error");
await flush();
assert.equal(result.settled, true);
assert.equal(cached(v).status, "offline");
assert.equal(old.badges[0].dataset.urlHealthState, "offline");
assert.equal(f.timers.size, 0);
assert.equal(f.controller.activeVideoProbes, 0);
});
@@ -0,0 +1,6 @@
- [x] Independent evidence and explicit HEAD implementation routing verified landed e04549f8.
- [x] Minimal controller HEAD fix,23 real lifecycle scenarios and strict integrity/spec notes.
- [x] Focused60/4files,16naturally failing mutants,cancelled0,lint11.
- [ ] Commit/merge candidate and send READY + slot request.
- [ ] Allocated full/normal gate, durable landing/ancestry, final closure.
- [ ] HLS/torrent separately routed/measured; no wider Stage3 claim.