mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-29 17:02:28 +00:00
land: area/client-reliability eafa364d39 (client-reliability)
This commit is contained in:
commit
af61c2716b
39 files changed
+861
-72
No files matched your search
@@ -0,0 +1 @@
|
||||
Client9 resumed at5f8e58be, existing item reclaimed. Runtime identical unstable and prior146076dd handoff;4 browser evidence docs retained. Current narrow goal: diagnose original Firefox HLS body-entry prerequisite using preserved unexecuted d0100521 instrumented scratch, only after fresh dispatch slot+cooling clearance. No product/test edits or broader cancellation work. Preserve all failures and strict5000ms bound; no repeat full/build needed for unchanged runtime. See matching test log; dispatch blocker sent.
|
||||
@@ -0,0 +1,4 @@
|
||||
Goal: reconcile existing HLS evidence and recommend remaining lifecycle boundary under interpreter13:41 routing.
|
||||
Success: preserve prior failure/control results, verify current source ancestry/equality and raw evidence hash, commit existing dirty notes, send scoped recommendation.
|
||||
Non-goals: no product/tests/browser/full/build/submission, no live or wholeStage3 claim; QA owns sole browser.
|
||||
See matching reconciled test log. Next: await explicit offline active-torrent investigation routing.
|
||||
@@ -0,0 +1,4 @@
|
||||
Goal: interpreter-authorized current-HEAD offline active torrent lifecycle investigation, no implementation.
|
||||
Baseline HEAD e9af76df, real observer/grid/queue/probePeers, fake WebTorrent IO+clock.
|
||||
Result: corrected12cases7pass5fail/naturalexit1; cleanup0remaining resources; existing lifecycle11/11 naturalexit0. See matching TEST_LOG.
|
||||
Next: dispatch independent reproduction and explicit gridHealth/probePeers-only ownership plus spec correction before fixes/tests. QA owns browser; no full/build/submission.
|
||||
@@ -0,0 +1 @@
|
||||
Approved dispatch routing read locally: actual baseline independently reproduced12=7pass5fail, no repeat needed. Scope gridHealth+optional probePeers lifecycle only. Goal final-owner null settlement/resource cleanup/fresh identity; preserve shared/standalone/live/cap4/FIFO/TTL/deferred destroy safety. Spec and integrity written first. Focused offline/mutants/targeted lint allowed; no full/build/browser/submission until resource grant.
|
||||
@@ -0,0 +1,5 @@
|
||||
- Preserve seven existing dirty notes verbatim alongside a new reconciliation, rather than rewriting old failed/incomplete results.
|
||||
- Treat separate Firefox prefix3/3 as controlled qualification; original header-onlyFAIL remains separate. No speculative product fix or repeated browser.
|
||||
- Use actual product SHA83fd2495 for ancestry; c95abe22 is a build identifier.
|
||||
- Next recommended investigation is active torrent final-owner disposal under item9/priority2, with real production chain and controlled IO; source hints alone are not reproduced defect or implementation authority.
|
||||
- Notes-only changes need source/hash/whitespace verification, no redundant full/lint/build. Commit evidence locally; no queue submission while source-only routing holds.
|
||||
@@ -0,0 +1,5 @@
|
||||
- Investigation uses actual production chain, fake only observer delivery/WebTorrent IO/timers/persistence; no implementation or committed tests.
|
||||
- Preserve first12=5pass/7fail fixture: two control failures are JSDOM storage-event timers; separate memoryStorage IO fixture12=7pass/5fail retains identical assertions and original evidence.
|
||||
- Treat five failures as planned Stage3 active-cancellation gap, not regression against existing intentional active-sharing/cache contracts. Need explicit routed spec correction before changing those tests.
|
||||
- Smallest fix boundary gridHealth ownership/identity/cache plus probePeers lifecycle only. Preserve deferred temporary-torrent destruction after callback unwinds and release queue slot after teardown; retain shared probe client/other owners.
|
||||
- No live browser/WebTorrent/network/CPU/RangeGET claim; no product edits until independent routing.
|
||||
@@ -0,0 +1,6 @@
|
||||
- Grid publishes owner/release/inflight promise before synchronous client IO so disposal or same-source registration inside constructor/add is safe; cancelled identity removed synchronously and cache guarded.
|
||||
- Optional third probePeers lifecycle follows existing owner-record convention; standalone behavior unchanged. Cancel settlesnull after deferred temporary-torrent destroy call returns/throws; timers clear immediately, cancel hook/owned listeners removed at final teardown.
|
||||
- Retain inert error handler through deferred teardown, even cancellation insideadd; repeated/late callbacks are settled guards. Shared probeClient remains alive; queue slot only releases after destroy, preserving event unwind/cap4/FIFO.
|
||||
- Original tests367/417 contract corrected only after routed spec+integrity note; preserve separate shared-live/cache/TTL/timeout controls and strict noobsoletecache/freshrequest/resource assertions.
|
||||
- No live WebTorrent transport/byte teardown qualification from fake IO. Async low-level network shutdown not measured; existing teardown call ordering preserved.
|
||||
- Stop at completedfocused boundary for requested kit restart; no commit/merge/submission before restart, all snapshots saved.
|
||||
@@ -80,6 +80,10 @@
|
||||
|
||||
## client-reliability
|
||||
|
||||
- 2026-10-26 19:16UTC: item9 full lint11PASS; normal queue c20635bc interrupted for sustained90.1C→86C over38s. Durable rejected/red exit1, unstable04c8c7cf unchanged, not landed. Partial275/438starts/275unique,163notstarted; original log/result preserved in state/evidence/client9-torrent-validation-20261026. Slot RELEASED, fresh resource+new gate grant required; no auto-resubmit/build/browser. Evidence test_logs/TEST_LOG_2026-10-26_19-16-00_client-reliability-9-thermal-interruption.md.
|
||||
|
||||
- 2026-10-26 resumed item9 active-torrent candidate `1b2362d2`, merged unstable `04c8c7cf` at `1bf50c1f`; exact saved hashes/focused60/60/19distinct killed mutants preserved. Remaining full lint/full/build/browser/normal landing require dispatch slot plus fresh health; TCPU97.1C at19:07:29 exceeds85C. No new tests or submission. Hold recorded and blocker mailed; real peers/byte teardown/wholeStage3 unverified. Evidence: test_logs/TEST_LOG_2026-10-26_19-07-50_client-reliability-9-resumed-ready.md.
|
||||
|
||||
- 2026-10-20: item6 DONE: retained actual profile-history wiring regression landed b3fab032;normal gate exit0 with431 discovered/431 unique file starts,focused38/38,5 killed mutants,lint11/11,submitted ancestry0. Test-only,existing product behavior passes. Evidence test_logs/TEST_LOG_2026-10-20_15-41-00_client-reliability-6-landed.md. Real signers/relays/encryption/full-app modal/browser/build/synced Clear unverified. Expensive slot released;IDLE for owner local review,no optional work.
|
||||
|
||||
- 2026-10-20: item6 test-only retention ready:new real-profile wiring3/3,focused5files/38scenarios,5 isolated runtime mutants failnaturally/0cancelled,lint11pass. Local remove/lastitem/clear/recreation andfake-synced reduced+empty replacement covered;historicalno-op rejected. No product/existing suite edits;full/normal slot pendingexplicit grant,full/browser/build unrun;real signers/relays/fullapp/syncedClear unverified. Unique integrity/evidence in test_logs/*client-reliability-6*.
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"addedOn": "2026-10-22",
|
||||
"filedAt": "fb54cea3a2ba99b4860421724a5050af6b97c293",
|
||||
"brief": "docs/agents/CAMPAIGN-2026-10-22.md",
|
||||
"evidence": "HEAD-only fix e9c960ed/merged09f52bdd durably landed f41ac656 normalgateexit0,435discovered/435unique starts missingextra0,ancestry0;build/artifactexit0;controlled native Chromium+Firefox lifecycle PASS with synthetic loadeddata/publicdeny. Prior60focused/16mutants/lint11. Resources released. HLS/torrent/RangeGET separately OPEN; no wholeStage3 claim. test_logs/TEST_LOG_2026-10-22_20-52-00_client-reliability-9-browser.md HLS read-only atd04d501e:12modes/7controls pass/5cancellationfail/0cancelled/naturalexit1; GET/body finalowner retains timer/unsettled,rejoin old,latecache. Await explicit controller+HLShelper routing. test_logs/TEST_LOG_2026-10-22_20-54-00_client-reliability-9-hls-investigation.md HLS explicitlyrouted20:55:13Z;minimal controllerhandoff+helperfetch/bodycancel implemented;new35+existing94=129pass/6files,19isolatedmutantsnaturallyfail/cancelled0,lint11exit0;noexistingtestsmodified. READY for serialized full/build/browser/normal slot after candidatecommit/merge; notsubmitted. test_logs/TEST_LOG_2026-10-22_21-03-00_client-reliability-9-hls-ready.md"
|
||||
"evidence": "HEAD-only fix e9c960ed/merged09f52bdd durably landed f41ac656 normalgateexit0,435discovered/435unique starts missingextra0,ancestry0;build/artifactexit0;controlled native Chromium+Firefox lifecycle PASS with synthetic loadeddata/publicdeny. Prior60focused/16mutants/lint11. Resources released. HLS/torrent/RangeGET separately OPEN; no wholeStage3 claim. test_logs/TEST_LOG_2026-10-22_20-52-00_client-reliability-9-browser.md HLS read-only atd04d501e:12modes/7controls pass/5cancellationfail/0cancelled/naturalexit1; GET/body finalowner retains timer/unsettled,rejoin old,latecache. Await explicit controller+HLShelper routing. test_logs/TEST_LOG_2026-10-22_20-54-00_client-reliability-9-hls-investigation.md HLS explicitlyrouted20:55:13Z;minimal controllerhandoff+helperfetch/bodycancel implemented;new35+existing94=129pass/6files,19isolatedmutantsnaturallyfail/cancelled0,lint11exit0;noexistingtestsmodified. READY for serialized full/build/browser/normal slot after candidatecommit/merge; notsubmitted. test_logs/TEST_LOG_2026-10-22_21-03-00_client-reliability-9-hls-ready.md 2026-10-26 active-torrent investigation at e9af76df: actual grid observer/queue/probePeers fake WebTorrent IO+clock; corrected scratch12=7pass/5fail/naturalexit1/allcleanup0/network0. Final-owner retains1torrent/2listeners/timeout+poll/unsettled, same-turn+later joins obsolete probe, latecache and occupied FIFO capacity; shared/live-wire/error/timeout/queued-priority/deferred-teardown/staleobserver controls pass. Existing lifecycle11/11 unchanged naturalexit0. First JSDOM timer-confounded12=5pass/7fail preserved; IO-isolated correction documented. Proposed gridHealth+probePeers optional lifecycle ONLY, needs explicit routing/spec correction for prior active-sharing/cache assertions; no product/test edits/full/build/browser/submission. test_logs/TEST_LOG_2026-10-26_15-25-00_client-reliability-9-torrent-investigation.md 2026-10-26 resumed active-torrent candidate1b2362d2 merged unstable04c8c7cf at1bf50c1f; four candidate hashes exactly match durable focused60/60/fail0/cancelled0 and19unique killed mutants/20variant executions/all27newcase map. No runtime validation-input change on merge. Four targeted lint passes only; full lint/full/build/browser/normal queue pending explicit dispatch resource grant; health19:07:29 TCPU97.1C exceeds85C. No new tests/repeated baseline/submission. test_logs/TEST_LOG_2026-10-26_19-07-50_client-reliability-9-resumed-ready.md 2026-10-26 full lint11pass; normal queue c20635bc thermally interrupted at275/438starts (275unique/163neverstarted), durable rejected/red exit1 at19:15:32, unstable04c8c7cf unchanged. Sustained90.1C\u219286C38s; approved SIGINT only verified owned gate group204078. Partial/result uniquely preserved state/evidence/client9-torrent-validation-20261026; RELEASE sent, renewed dispatch resource+gate grant required/no automatic resubmit. test_logs/TEST_LOG_2026-10-26_19-16-00_client-reliability-9-thermal-interruption.md"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
# Client9 active torrent ownership contract correction
|
||||
|
||||
Authority: dispatch docs/agents/ROUTING-2026-10-26-client9-torrent.md (17:38UTC), read locally by client after interpreter18:53 relay. Independent exact12cases=7pass/5fail/naturalexit1/network0/cleanup0 confirms client baseline. This note precedes any prior expectation edits.
|
||||
|
||||
Source: docs/performance-efficiency-plan.md Stage3 explicitly extends owner cancellation to active torrent probes and says removed cards do not retain probes. Earlier queued-only implementation intentionally left running probes eligible across final-owner disposal, a new same-source owner and cache completion.
|
||||
|
||||
Old tests/grid-health-lifecycle.test.mjs scenarios originally at367 and417 permit last-owner active work to be rejoined and ownerless success cached. New contract: final-owner disposal synchronously marks old work cancelled/unavailable; it cannot be rejoined, cache, or paint. Timer/cancel hooks are cleaned, cancelled promise settles null, and safe temporary-torrent destruction happens after event unwind before queue slot release exactly once. A fresh same-source owner requests fresh work; old callbacks/finalizer cannot delete/cache/paint over it. Cancellation does not depend on an AbortController or cooperative transport.
|
||||
|
||||
Retain strict independent controls: one leaving owner cannot cancel another live owner, active healthy/error/timeout caches retain existing TTL behavior, standalone helper callers preserve results, queue cap4/priority/FIFO and destroy-before-slot safety remain. The shared probe client and unrelated torrents must survive. Replace the two superseded scenarios with equally strict sharing-with-live-owner and obsolete-cache/fresh-request assertions, and add real grid+probe resource/settlement/identity tests. No silent deletion or relaxed assertions.
|
||||
|
||||
Scope only js/gridHealth.js ownership/inflight/cache and js/webtorrent.js probePeers optional lifecycle, plus focused tests/notes. No app, playback/service-worker engine, URL/HLS, magnet/tracker policy, concurrency/TTL/config changes. Current offline source defect does not qualify real peers/network byte release, browser decoding/playback, CPU, RangeGET or all Stage3. Full/build/browser/queue still need separate resource grant.
|
||||
+41
-34
@@ -195,20 +195,48 @@ function queueProbe(magnet, cacheKey, priority = 0, webSeeds = [], owner) {
|
||||
|
||||
if (probeInflight.has(cacheKey)) {
|
||||
const record = probeInflight.get(cacheKey);
|
||||
record.owners.add(owner);
|
||||
return record;
|
||||
if (!record.cancelled) {
|
||||
record.owners.add(owner);
|
||||
owner.release = () => releaseProbe(record, owner, cacheKey);
|
||||
return record;
|
||||
}
|
||||
}
|
||||
|
||||
const record = { owners: new Set([owner]), cancelled: false };
|
||||
owner.release = () => releaseProbe(record, owner, cacheKey);
|
||||
// Publish ownership and the result before starting IO: client.add() can
|
||||
// synchronously unregister a card or register another owner of this source.
|
||||
let resolveJob;
|
||||
let rejectJob;
|
||||
record.promise = new Promise((resolve, reject) => {
|
||||
resolveJob = resolve;
|
||||
rejectJob = reject;
|
||||
})
|
||||
.then((result) => {
|
||||
if (record.cancelled) return null;
|
||||
const normalized = normalizeResult(result);
|
||||
if (cacheKey) setCacheEntry(cacheKey, normalized);
|
||||
return normalized;
|
||||
})
|
||||
.finally(() => {
|
||||
if (cacheKey && probeInflight.get(cacheKey) === record) {
|
||||
probeInflight.delete(cacheKey);
|
||||
}
|
||||
});
|
||||
if (cacheKey) probeInflight.set(cacheKey, record);
|
||||
record.job = probeQueue.run(
|
||||
() =>
|
||||
torrentClient
|
||||
.probePeers(magnet, {
|
||||
timeoutMs: PROBE_TIMEOUT_MS,
|
||||
maxWebConns: 2,
|
||||
polls: PROBE_POLL_COUNT,
|
||||
urlList: webSeeds,
|
||||
})
|
||||
.probePeers(
|
||||
magnet,
|
||||
{
|
||||
timeoutMs: PROBE_TIMEOUT_MS,
|
||||
maxWebConns: 2,
|
||||
polls: PROBE_POLL_COUNT,
|
||||
urlList: webSeeds,
|
||||
},
|
||||
record
|
||||
)
|
||||
.catch((err) => ({
|
||||
healthy: false,
|
||||
peers: 0,
|
||||
@@ -219,26 +247,7 @@ function queueProbe(magnet, cacheKey, priority = 0, webSeeds = [], owner) {
|
||||
})),
|
||||
priority
|
||||
);
|
||||
record.promise = record.job.promise
|
||||
.then((result) => {
|
||||
if (record.cancelled) {
|
||||
return null;
|
||||
}
|
||||
const normalized = normalizeResult(result);
|
||||
if (cacheKey) {
|
||||
setCacheEntry(cacheKey, normalized);
|
||||
}
|
||||
return normalized;
|
||||
})
|
||||
.finally(() => {
|
||||
if (cacheKey && probeInflight.get(cacheKey) === record) {
|
||||
probeInflight.delete(cacheKey);
|
||||
}
|
||||
});
|
||||
|
||||
if (cacheKey) {
|
||||
probeInflight.set(cacheKey, record);
|
||||
}
|
||||
record.job.promise.then(resolveJob, rejectJob);
|
||||
|
||||
return record;
|
||||
}
|
||||
@@ -246,13 +255,13 @@ function queueProbe(magnet, cacheKey, priority = 0, webSeeds = [], owner) {
|
||||
function releaseProbe(record, owner, cacheKey) {
|
||||
owner.active = false;
|
||||
record.owners.delete(owner);
|
||||
// A running probe keeps its existing lifecycle and cache behavior. Only work
|
||||
// still waiting for a slot can be cancelled when its last card unregisters.
|
||||
if (!record.owners.size && record.job?.cancel()) {
|
||||
if (!record.owners.size && !record.cancelled) {
|
||||
record.cancelled = true;
|
||||
if (probeInflight.get(cacheKey) === record) {
|
||||
probeInflight.delete(cacheKey);
|
||||
}
|
||||
record.job?.cancel();
|
||||
record.cancelActive?.();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -524,10 +533,8 @@ function handleCardVisible({ card, pendingByCard, liveCards, priority = 0 }) {
|
||||
const webSeeds = webSeed ? [webSeed] : [];
|
||||
|
||||
const owner = { active: true };
|
||||
const record = queueProbe(magnet, infoHash, priority, webSeeds, owner);
|
||||
owner.release = () => releaseProbe(record, owner, infoHash);
|
||||
|
||||
pendingByCard.set(card, owner);
|
||||
const record = queueProbe(magnet, infoHash, priority, webSeeds, owner);
|
||||
|
||||
record.promise
|
||||
.then((result) => {
|
||||
|
||||
+55
-32
@@ -77,12 +77,15 @@ export class TorrentClient {
|
||||
|
||||
/**
|
||||
* Helper to check if a magnet link has active peers without starting a full
|
||||
* download. Used for "Stream Health" badges/indicators in the UI.
|
||||
* download. Optional lifecycle belongs to grid cards; standalone callers
|
||||
* retain their normal wire/error/timeout results.
|
||||
*/
|
||||
async probePeers(
|
||||
magnetURI,
|
||||
{ timeoutMs = 8000, maxWebConns = 2, polls = 3, urlList = [] } = {}
|
||||
{ timeoutMs = 8000, maxWebConns = 2, polls = 3, urlList = [] } = {},
|
||||
lifecycle
|
||||
) {
|
||||
if (lifecycle?.cancelled || lifecycle?.owners?.size === 0) return null;
|
||||
const magnet = typeof magnetURI === "string" ? magnetURI.trim() : "";
|
||||
if (!magnet) {
|
||||
return {
|
||||
@@ -137,6 +140,26 @@ export class TorrentClient {
|
||||
let torrent = null;
|
||||
let timeoutId = null;
|
||||
let pollId = null;
|
||||
let adding = false;
|
||||
let disposalStarted = false;
|
||||
let result = null;
|
||||
const cancelActive = () => {
|
||||
lifecycle.cancelled = true;
|
||||
finalize(null);
|
||||
};
|
||||
const settleHealthy = () => {
|
||||
const peers = Math.max(1, Math.floor(normalizeNumber(torrent?.numPeers, 1)));
|
||||
finalize({ healthy: true, peers, reason: "peer" });
|
||||
};
|
||||
const settleError = (err) => {
|
||||
const peers = Math.max(0, Math.floor(normalizeNumber(torrent?.numPeers, 0)));
|
||||
finalize({
|
||||
reason: "error",
|
||||
error: toError(err),
|
||||
peers,
|
||||
webseedOnly: peers === 0 && hasWebSeed,
|
||||
});
|
||||
};
|
||||
|
||||
// A successful probe commonly settles from WebTorrent's `wire` event.
|
||||
// Do not destroy the temporary torrent while that event is still being
|
||||
@@ -144,21 +167,23 @@ export class TorrentClient {
|
||||
// listeners return, and synchronous destruction can leave its bitfield
|
||||
// null (`recalculate(...).fill` in the bundled client). The next task
|
||||
// guarantees the callback has fully unwound before cleanup.
|
||||
const disposeProbeTorrent = (target, onDisposed = () => {}) => {
|
||||
if (!target) {
|
||||
onDisposed();
|
||||
return;
|
||||
}
|
||||
const disposeProbeTorrent = () => {
|
||||
if (adding || disposalStarted) return;
|
||||
disposalStarted = true;
|
||||
const dispose = () => {
|
||||
try {
|
||||
target.destroy({ destroyStore: true });
|
||||
torrent?.destroy({ destroyStore: true });
|
||||
} catch (err) {
|
||||
// A completed or already-destroyed probe is harmless.
|
||||
} finally {
|
||||
onDisposed();
|
||||
torrent?.removeListener("wire", settleHealthy);
|
||||
torrent?.removeListener("error", settleError);
|
||||
if (lifecycle?.cancelActive === cancelActive) lifecycle.cancelActive = null;
|
||||
resolve(lifecycle?.cancelled ? null : result);
|
||||
}
|
||||
};
|
||||
setTimeout(dispose, 0);
|
||||
if (torrent) setTimeout(dispose, 0);
|
||||
else dispose();
|
||||
};
|
||||
|
||||
const finalize = (overrides = {}) => {
|
||||
@@ -179,10 +204,10 @@ export class TorrentClient {
|
||||
? performance.now()
|
||||
: Date.now();
|
||||
|
||||
const isHealthy = Boolean(overrides.healthy);
|
||||
const peersCount = Number.isFinite(overrides.peers) ? overrides.peers : 0;
|
||||
const isHealthy = Boolean(overrides?.healthy);
|
||||
const peersCount = Number.isFinite(overrides?.peers) ? overrides.peers : 0;
|
||||
|
||||
const result = {
|
||||
result = overrides === null ? null : {
|
||||
healthy: isHealthy,
|
||||
peers: peersCount,
|
||||
reason: isHealthy ? "peer" : "timeout",
|
||||
@@ -192,13 +217,18 @@ export class TorrentClient {
|
||||
durationMs: Math.max(0, endedAt - startedAt),
|
||||
...overrides,
|
||||
};
|
||||
emit("torrent-probe-result", result);
|
||||
if (result) emit("torrent-probe-result", result);
|
||||
// Keep the grid-health queue slot occupied until the delayed teardown
|
||||
// completes. Otherwise the next probe can begin before this probe has
|
||||
// detached its tracker listeners, briefly exceeding listener limits.
|
||||
disposeProbeTorrent(torrent, () => resolve(result));
|
||||
disposeProbeTorrent();
|
||||
};
|
||||
|
||||
if (lifecycle) lifecycle.cancelActive = cancelActive;
|
||||
if (lifecycle?.cancelled || lifecycle?.owners?.size === 0) {
|
||||
cancelActive();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const addOptions = {
|
||||
announce: trackers,
|
||||
@@ -225,6 +255,7 @@ export class TorrentClient {
|
||||
if (hasExplicitWebSeed) {
|
||||
addOptions.urlList = urlList;
|
||||
}
|
||||
adding = true;
|
||||
torrent = client.add(augmentedMagnet, addOptions);
|
||||
} catch (err) {
|
||||
finalize({
|
||||
@@ -233,26 +264,18 @@ export class TorrentClient {
|
||||
peers: 0,
|
||||
webseedOnly: hasWebSeed,
|
||||
});
|
||||
} finally {
|
||||
adding = false;
|
||||
}
|
||||
// Even cancellation inside add() must absorb a late error until the
|
||||
// returned torrent can be safely destroyed on the next task.
|
||||
torrent?.on("error", settleError);
|
||||
if (settled) {
|
||||
disposeProbeTorrent();
|
||||
return;
|
||||
}
|
||||
|
||||
const settleHealthy = () => {
|
||||
const peers = Math.max(1, Math.floor(normalizeNumber(torrent?.numPeers, 1)));
|
||||
finalize({ healthy: true, peers, reason: "peer" });
|
||||
};
|
||||
|
||||
torrent.once("wire", settleHealthy);
|
||||
|
||||
torrent.once("error", (err) => {
|
||||
const peers = Math.max(0, Math.floor(normalizeNumber(torrent?.numPeers, 0)));
|
||||
finalize({
|
||||
healthy: false,
|
||||
reason: "error",
|
||||
error: toError(err),
|
||||
peers,
|
||||
webseedOnly: peers === 0 && hasWebSeed,
|
||||
});
|
||||
});
|
||||
if (settled) return;
|
||||
|
||||
if (safeTimeout > 0) {
|
||||
timeoutId = setTimeout(() => {
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# Client9 active torrent test integrity
|
||||
|
||||
Written before editing prior367/417 expectations. Exact authority and old/new contract: docs/spec_changes/2026-10-26-client-reliability-9-active-torrent.md. Prior failed baseline and original queued-only suite preserved in investigation evidence; production files and old assertions are in ab1ea6de parent history.
|
||||
|
||||
```yaml
|
||||
test_integrity_note:
|
||||
change_type: [new_tests, spec_correction]
|
||||
scenarios:
|
||||
- id: SCN-active-torrent-last-owner
|
||||
given: real grid observer and active probe with one card owner
|
||||
when: the final owner unregisters
|
||||
then: cancelled work settles null, clears timers/hooks and destroys temporary torrent after event unwind before slot release, without cache or stale badge writes
|
||||
- id: SCN-active-torrent-shared-and-replacement
|
||||
given: shared live owners or a same-source replacement after final-owner loss
|
||||
when: one owner leaves or old callbacks and finalizer run
|
||||
then: live shared work is preserved and cancelled identity cannot affect fresh work
|
||||
- id: SCN-active-torrent-live-standalone
|
||||
given: a live owner or standalone probe caller
|
||||
when: wire, error or normal timeout occurs
|
||||
then: existing results, cache TTL, shared probe client and bounded queue ordering remain
|
||||
observable_outcomes:
|
||||
- temporary torrent lifetime, destroy ordering/count, timers/listeners/hook, returned results
|
||||
- actual card badge and persisted cache values, new work count, FIFO progress and resource cap
|
||||
determinism_controls:
|
||||
- fake WebTorrent IO, controlled clock and observer delivery, map-backed persistence IO
|
||||
- isolated mutant copies, no public fetch/WebSocket, no sleeps or retries
|
||||
anti_cheat_rationale:
|
||||
prevents:
|
||||
- cached obsolete results or stale identity mutation
|
||||
- cancelling a still shared live owner
|
||||
- immediate unsafe destroy or premature queue-slot release
|
||||
- mocked probe implementation bypassing real resource cleanup
|
||||
relaxation:
|
||||
did_relax_any_assertion: false
|
||||
if_true_explain_spec_basis: ''
|
||||
```
|
||||
|
||||
Contract correction changes outcomes, not assertion strength: original last-owner-gap reuse/cache expectations become fresh-work/no-obsolete-cache/resource assertions, while separate live-owner/shared/cache/timeout controls remain. Mutant mappings and exact validation results will be recorded before handoff; no passing or mutant-kill counts claimed yet.
|
||||
|
||||
Final focused27new+11corrected+22adjacent=60/60;19unique mutants all killed,20variant executions with added extended run; all27newscenarios covered. Full per-scenario mappings and failures: state/evidence/client9-torrent-impl-20261026/new-scenario-mutant-map.json and mutants/coverage.json. No mutants survived; all mutated suite runs cancelled0, natural failure exits. Restored state is automatic because only isolated copies were changed. Full/build/browser/landing pending separately.
|
||||
@@ -0,0 +1,8 @@
|
||||
# Client9 source-only resume
|
||||
|
||||
- HEAD `7b667e79`, clean before this evidence note; existing item9 reclaimed, no new item. Latest rules/PROGRAM/area TODO/mailbox and saved notes read.
|
||||
- `node --check /tmp/client9-hls-native-smoke-prepared.mjs`: exit 0. No browser invocation. SHA256 `28cf3511c0ac154e59ac519c0f68dfe4099cb37e016bae31c3564ed58db9375e`.
|
||||
- dist version `c95abe22`; script-pinned controller/observer/HLS-helper/cache hashes verified exactly. Source controller/helper equal dist. `git merge-base --is-ancestor 83fd2495 HEAD` and same against unstable both exit 0. Existing 437-file landing/build evidence read, not repeated.
|
||||
- Original Chromium same-source replacement timeout failure retained in `/tmp/client9-hls-native-smoke-chromium-output.md`; no later run output found. Corrected method/path/occurrence-aware source remains unexecuted.
|
||||
- Resource snapshots 02:50:44Z CPU82.1C/load2.95 and 02:51:02Z CPU85.1C/load3.60 fail required <75C start threshold. Swap82%, available memory65%. No expensive job launched. Dispatch received exact source readiness and blocker; fresh exclusive browser grant pending.
|
||||
- Remaining: two fresh cooling snapshots <75C about30s apart/load<8, explicit dispatch grant, bounded Chromium then separately granted Firefox; preserve original failure. No real decoding, playback, segments, public IO, torrent, RangeGET or full Stage3 qualification.
|
||||
@@ -0,0 +1,8 @@
|
||||
# Client9 Chromium invocation blocked before launch
|
||||
|
||||
- Dispatch fresh exclusive grant at 02:52:46Z; two dispatch cooling snapshots reported 74.8C then69.1C/42s. Local fresh health02:52:57Z CPU68.1C/package67C/load2.50/RAM63.6%, swap82% advisory. START announced.
|
||||
- Command: `env -u NODE_TEST_CONTEXT -u UNIT_TEST_SHARD node /tmp/client9-hls-native-smoke-prepared.mjs chromium > /tmp/client9-hls-chromium-20261024T0253.log 2>&1`.
|
||||
- Exact reviewed fixture SHA256 `28cf3511c0ac154e59ac519c0f68dfe4099cb37e016bae31c3564ed58db9375e`, dist `c95abe22`, HEAD `7b667e79`.
|
||||
- Natural exit1 in0.27s: `Error: listen EPERM: operation not permitted 127.0.0.1`, before browser launch. This is a sandbox loopback restriction; no browser assertion or method timeline reached.
|
||||
- No retry, escalation, Firefox, full suite, build or product change. Slot immediately RELEASED to dispatch; `.task` records blocker. Earlier Chromium replacement timeout evidence preserved unchanged.
|
||||
- HLS browser qualification remains open; no real decoding/playback/segments/public IO/torrent/RangeGET/Stage3 qualification.
|
||||
@@ -0,0 +1,9 @@
|
||||
# Client9 controlled native Chromium HLS qualification
|
||||
|
||||
- HEAD `7b667e79`; dist `c95abe22`; exact fixture SHA256 `28cf3511c0ac154e59ac519c0f68dfe4099cb37e016bae31c3564ed58db9375e`. Pinned controller/observer/helper/cache hashes matched.
|
||||
- Dispatch02:53:39Z granted one normal host approval invocation after sandbox loopback EPERM. Fresh local health02:53:50Z CPU58.1C/load1.24; swap82% advisory. START announced; normal approval mechanism accepted host invocation.
|
||||
- Command: `env -u NODE_TEST_CONTEXT -u UNIT_TEST_SHARD node /tmp/client9-hls-native-smoke-prepared.mjs chromium > /tmp/client9-hls-chromium-host-20261024T0254.log 2>&1`. Natural exit0 in0.57s; finally browser/server cleanup completed; slot immediately released.
|
||||
- Three cases passed: fetch cancellation followed by exact same-source sequential rejoin; body-read cancellation; shared live owner. Both cancellations settle null and abort GET without aborting completed HEAD; cancelled fetch writes0/stale badge checking, body cancellation writes unchanged1. Replacement healthy/badge healthy/write1. Shared owner1HEAD/1GET/no abort, remaining badge healthy/totalwrites2.
|
||||
- Method/path/occurrence timeline:0HEAD cancel,1GET cancel,2HEAD same-source replacement,3GET replacement,4HEAD body,5GET body,6HEAD shared,7GET shared. All8 responses closed. Four nonlocal requests denied; page errors0. Full exact JSON retained in unique host output.
|
||||
- Original13:16UTC Chromium replacement timeout failure and sandbox EPERM output preserved unchanged; corrected method-aware fixture now passes once. No product/assertion edits, Firefox/repeated host invocation/full/build/submission.
|
||||
- Evidence uses native observer/fetch/AbortController/Response.text against controlled loopback responses. No same-turn browser identity, decoder, segments, actual playback, live network/peers, torrent, RangeGET, CPU improvement or wholeStage3 claim. Firefox awaits separate grant.
|
||||
@@ -0,0 +1,17 @@
|
||||
# Client9 controlled HLS browser checkpoint
|
||||
|
||||
- HEAD `7b667e79`; existing landed product `83fd2495`; dist `c95abe22`. No product/test/assertion edits, rebuild, full gate or new submission. Exact fixture SHA256 `28cf3511c0ac154e59ac519c0f68dfe4099cb37e016bae31c3564ed58db9375e` retained unchanged for both browsers.
|
||||
- Original Chromium13:16UTC replacement timeout remains preserved in `/tmp/client9-hls-native-smoke-chromium-output.md`. Sandbox loopback EPERM attempt remains `/tmp/client9-hls-chromium-20261024T0253.log`; it reached no browser. Neither failure erased by later results.
|
||||
- Host Chromium: separate normal host approval under dispatch02:53:39Z grant; natural exit0/0.57s, three cases passed. Fetch cancellation+sequential exact-source replacement, body-read cancellation, shared live owner. Final JSON8requests=4HEAD+4GET/allresponsesclosed/pageErrors0/publicdenied4. Cancelled GETs abort/null/no obsolete cache; completed HEADs not aborted; replacement/shared owner healthy. Unique output `/tmp/client9-hls-chromium-host-20261024T0254.log`; detail in TEST_LOG_2026-10-24_02-54-00_client-reliability-9-chromium.md.
|
||||
- Host Firefox: separate dispatch02:54:44Z grant; fresh02:54:56Z CPU64.1C/package63C/load0.89. START announced. Command `env -u NODE_TEST_CONTEXT -u UNIT_TEST_SHARD node /tmp/client9-hls-native-smoke-prepared.mjs firefox > /tmp/client9-hls-firefox-host-20261024T0255.log 2>&1` via normal host approval. Natural exit1 after approximately5s; finally cleanup returned and slot immediately RELEASED.
|
||||
- Firefox error: `page.evaluate: Timed out waiting for native body-read entry /hls/body/pending.m3u8 after 5000ms`, fixture line294. Source execution reaches this after first-case fetch cancellation/transport close/exact-source replacement assertions pass. Body-read cancellation and shared-owner cases did not complete. No final JSON/method timeline emitted; do not infer GET-arrival counts or transport state from absent output.
|
||||
- Firefox result is failed/incomplete browser qualification. Cause remains unclassified: native response-body entry prerequisite was not observed within existing bound. No timeout extension, sleep, retry, assertion relaxation, fixture change or speculative product fix. Dispatch notified outcome/release.
|
||||
- Browser scope: actual native IntersectionObserver/fetch/AbortController/Response.text against controlled loopback endpoints and pinned built modules; nonlocal requests denied. No real decoding, segments, playback, public-network/peers, same-turn browser identity, torrent, RangeGET, CPU improvement or wholeStage3 qualification. Chromium pass does not establish Firefox pass.
|
||||
- Next gate: dispatch scope review for source-only diagnosis of Firefox prerequisite; no further browser invocation or submission authorized by these consumed grants. Existing item9 remains open.
|
||||
|
||||
## Source-only prerequisite inspection
|
||||
|
||||
- Fixture body endpoint writes HTTP200 headers and calls `res.flushHeaders()` but writes no body bytes before disposal (lines72-78). Its comment that native GET resolves is an assumption, not measured Firefox evidence.
|
||||
- Product helper awaits native fetch before invoking `response.text()` (`js/services/hlsPlayback.js:419`, `:427`). The fixture records only fetch invocation/signal and Response.text entry, not fetch resolution/rejection or response status.
|
||||
- Body-read watchdog starts before observer registration (fixture294-295), so its5000ms budget includes observer activation, HEAD and GET delivery. Failure output provides no final per-stage timeline. Header-only delivery/buffering, pre-body request outcome, and prerequisite ordering remain hypotheses; current evidence cannot distinguish them.
|
||||
- First-case preceding assertions are source-order evidence only, not a separately emitted Firefox case report. No source edits or rerun performed. A future separately reviewed diagnostic would need stage-specific timestamps/status and method/path/occurrence output preserved on failure before changing fixture behavior.
|
||||
@@ -0,0 +1,7 @@
|
||||
# Client reliability resume at 5f8e58be
|
||||
|
||||
- Read disk fleet prompt/skill, newest RULE-CHANGES, PROGRAM, RESUME-BRIEF, area TODO, .task, repo memory, KNOWN_ISSUES and preserved HLS context/todo/decisions/browser evidence. Mailbox empty; reclaimed existing client-reliability-9. No new item.
|
||||
- HEAD `5f8e58bec0ff83383b35bf0968f1cf0f66edc9cb`; unstable `0ffd92a8d2072a19fe61ac6edcf5097a9538fddd`. Tree initially clean. `git diff --name-only unstable HEAD`: four retained browser evidence docs only. `git diff --exit-code unstable HEAD -- js tests config package.json package-lock.json`: exit0. Controller/helper diff against prior handoff146076dd: exit0. Existing product evidence therefore remains applicable; no duplicate suite/build run.
|
||||
- `env -u NODE_TEST_CONTEXT -u UNIT_TEST_SHARD node --check /tmp/client9-hls-native-diagnostic-20261024T0259.mjs`: exit0. Diagnostic SHA256 `d0100521a1aeaaecd58e9ad08d3c42d8365c37e286c66f6e157071efb6316870`; original prepared script `28cf3511c0ac154e59ac519c0f68dfe4099cb37e016bae31c3564ed58db9375e` preserved. All four dist module SHA256 values match diagnostic pins. Source-only inspection confirms failure timeline and finally cleanup instrumentation; diagnostic remains UNEXECUTED.
|
||||
- Fleet health19:45:39Z: max76C/load1.99/available67.4%/swap98%. Does not meet cooling start threshold. No browser, full gate, build, submission or product/test edit launched.
|
||||
- BLOCKED message delivered to dispatch19:45:57Z requesting fresh exclusive bounded diagnostic/browser routing and cooling clearance; previous grants expired. .task records WAITING. Remaining Firefox body-entry prerequisite unclassified; prior Chromium3/3 does not qualify Firefox, real playback, segments, live IO, torrent, RangeGET or whole Stage3.
|
||||
@@ -0,0 +1,7 @@
|
||||
# Reserved diagnostic preparation and resource HOLD
|
||||
|
||||
- Read interpreter mailbox19:53:30: one unchanged d0100521 Firefox diagnostic conditionally granted after two fresh health snapshots below75C about30seconds apart, reasonableload/ampleRAM. Dispatch.task independently reserves sole slot and parks competing heavy work. No outgoing acknowledgement requested when blocked; interpreter reads.task.
|
||||
- HEAD5f8e58be. Script SHA256 d0100521a1aeaaecd58e9ad08d3c42d8365c37e286c66f6e157071efb6316870 unchanged; node --check exit0. Four module hashes match saved pins; dist data-site-version c95abe22. Prior strict Chromium failure, Chromium3/3 and Firefox body-entry5000ms failure retained.
|
||||
- Source diff against original28cf3511: diagnostic adds method/path/occurrence/stage timestamps, native fetch/text/probe settlement tracing and failure/cleanup output. Existing network behavior, assertions and5000ms deadlines unchanged. diff exit1 means expected instrumentation differences, not test failure. No script edits.
|
||||
- Health19:53:43.729Z max74.1C/load4.08/RAMavailable62.7%;19:54:26.104Z max78.1C/load4.9/RAM59.5%;19:55:09.881Z max78C/load9.8/RAM60%. Swap98% throughout. No qualifying two-snapshot pair; latest load also exceeds8. Browser NOTSTARTED; conditional one-run grant unconsumed.
|
||||
- HOLD in.task with concrete readings and next check on interpreter cooling/resume signal; require two new qualifying samples before execution. No background watcher/scheduler/process stops, no gates/build/productwrites/submission, no rerun or timeout/assertion change. Firefox prerequisite cause remains unclassified; no live playback/segments/torrent/RangeGET qualification.
|
||||
@@ -0,0 +1,10 @@
|
||||
# One reserved Firefox HLS diagnostic — prerequisite failure isolated
|
||||
|
||||
- Interpreter22:03:18 post-outage conditional ONE unchanged diagnostic grant; exclusive brief browser slot, other heavy work parked. ACK and START recorded.task. HEAD5f8e58bec0ff83383b35bf0968f1cf0f66edc9cb. All existing dirty notes preserved.
|
||||
- Fresh health22:03:29.023Z max64.1C/load6.02/availableRAM68.8%/battery56%38min;22:04:09.921Z max63.8C/load3.55/RAM68.9%/battery55%39min. Both qualify. Saved d0100521a1aeaaecd58e9ad08d3c42d8365c37e286c66f6e157071efb6316870 script unchanged, node --check exit0, four module hashes match pinned c95abe22 dist. Prior failed results retained.
|
||||
- Normal host approval granted. Command: `env -u NODE_TEST_CONTEXT -u UNIT_TEST_SHARD node /tmp/client9-hls-native-diagnostic-20261024T0259.mjs firefox > /tmp/client9-hls-firefox-diagnostic-20261024T2204.log 2>&1`. Natural exit1; diagnostic cleanup-finished at7831.007ms. No retry, timeout extension or assertion/script/product edits.
|
||||
- Actual native observer/head/GET cancellation case: first GET arrives2148.415ms, abort2152.706ms, transport close2154.187ms, probe null settlement2154.754ms. All strict firstState assertions and close check passed before next case. Replacement same event/URL performs fresh HEAD+GET,200 response/text25chars,healthy settlement2223.815ms; strict healthy badge/cache-write assertions passed before body case.
|
||||
- Body case: observer2235.746ms; HEAD arrives2246.768ms/completes200; native GET invoked2248.613ms/arrives2253.099ms; server flushes200headers with0bodybytes2253.578ms. No fetch-resolve, fetch-reject or text-entry event for this body GET before prerequisite timeout at7238.467ms. At failure GET still open; body-owner disposal has not run. Case3 shared-owner scenario never reached.
|
||||
- Failure is `page.evaluate: Timed out waiting for native body-read entry /hls/body/pending.m3u8 after 5000ms`, script338. Browser/server finally cleanup closes body GET7651.709ms and completes7831.007ms; all6request responses closed. Slot RELEASED immediately on terminal process result. No final success JSON; no unsupported page-error/public-denial totals inferred.
|
||||
- Observation narrows original failure to the native fetch/body-entry prerequisite after header-only delivery. It does not demonstrate a defect in cancellation during Response.text: that scenario never starts. Header-only native delivery/buffering is a hypothesis, not independently proven browser behavior. No speculative product patch. Any new fixture experiment requires separately scoped routing; grant consumed.
|
||||
- Exact raw output preserved at /tmp/client9-hls-firefox-diagnostic-20261024T2204.log and state/evidence/client-reliability-9/firefox-diagnostic-20261024T2204.log. No gate/build/submission. Firefox overall qualification remains incomplete; real playback,segments,publicIO,torrent,RangeGET and wholeStage3 unverified.
|
||||
@@ -0,0 +1,9 @@
|
||||
# Source-only Firefox HLS fixture control proposal
|
||||
|
||||
- Interpreter22:05:40 requests separately named source-only control; no new network/browser, product patch or repeated diagnostic. QA owns next sole browser. Original d0100521 and exact failed output00390c96 remain preserved. First fetch cancellation and exact-source replacement PASS; body-entry prerequisite FAIL; shared UNRUN.
|
||||
- Proposed file `/tmp/client9-hls-native-prefix-control-20261024T2207.mjs`, SHA256 `2988ddf749762b09cec37cc5b49558e9f1ae8cd710fec43365b10dce8cc912bc`. Copy retained in state/evidence/client-reliability-9. `node --check` exit0. UNEXECUTED; no network or browser invoked.
|
||||
- Exact behavioral delta within GET `/hls/body/` response only: after existing HTTP200+flushHeaders, call `res.write(' ')`, one pinned ASCIIspace/hex20; emit `prefix-written` with bodyBytes1/prefixHex20. Leave stream open without end. Original headers-flushed/bodyBytes0 event remains. Comments explain hypothesis; no other code altered. `diff -u` exit1 is expected differences, not validation failure.
|
||||
- Rationale from source: product waits for fetch before calling response.text, and trimStart removes this harmless leading space if a complete manifest is ever delivered. The held stream ensures the body remains incomplete; cancellation must still settle null/abort/no cache after native text-entry. Original observed trace proves header-only delivery did not reach native fetch resolution before deadline in this fixture. It does NOT establish that Firefox universally requires a body byte; buffering, browser/network stack or fixture behavior remain possible. Proposed byte writes to server socket but does not itself prove native delivery.
|
||||
- All original assertions remain: HEAD200 before GET; correct native GET body reader; final-owner cancellation null, GET abort and HEAD not aborted, unchanged cache writes1, transport closes; fresh same-source healthy replacement; shared-owner behavior. All5000ms deadlines, real observer/fetch/Response.text, publicdeny, module/buildpins, cancellation ownership, cleanup and case ordering unchanged. No retries/sleeps/timeouts/assertion relaxation.
|
||||
- Proposed interpretation for a separately authorized single control: original header-only failure is comparison baseline. If prefix-written is followed by fetch-resolve200 and text-entry before disposal, then null/no-write/abort/close passes, it establishes the intended native body-read prerequisite for this control. Preserve shared result separately. If no fetch-resolve/text-entry or new failure, retain failure as inconclusive and stop; do not add bytes/delays/retries automatically. A pass supports fixture sensitivity to first-byte delivery in this controlled run, not a universal protocol claim or a product fix. No run authorized by this proposal.
|
||||
- Original diagnostic SHA remains d0100521a1aeaaecd58e9ad08d3c42d8365c37e286c66f6e157071efb6316870. HEAD5f8e58be. Product/tests/config/build untouched; no gate, submission or release qualification. Review/routing required before any execution.
|
||||
@@ -0,0 +1,10 @@
|
||||
# Firefox prefix control — three cases pass
|
||||
|
||||
- Interpreter03:25:37 explicitly reviewed proposal and granted ONE unchanged prefix control after fresh safe health; QA explicit release verified by interpreter. Health03:25:50.976Z max71.8C/load1.63/RAMavailable69.7%/battery96%charging. No long job launched; swap nearfull constraint retained.
|
||||
- HEAD5f8e58bec0ff83383b35bf0968f1cf0f66edc9cb. Exact script2988ddf749762b09cec37cc5b49558e9f1ae8cd710fec43365b10dce8cc912bc unchanged; node --check exit0 and four dist modulepins verified/c95abe22. Original d0100521 baseline and failure00390c96 preserved.
|
||||
- START recorded.task. Normal host approval granted. Command: `env -u NODE_TEST_CONTEXT -u UNIT_TEST_SHARD node /tmp/client9-hls-native-prefix-control-20261024T2207.mjs firefox > /tmp/client9-hls-firefox-prefix-20261025T0326.log 2>&1`. Natural exit0; cleanup-finished2849.213ms. RELEASE recorded immediately after result. No retry, sleeps, changed deadlines/assertions or product/test/config edits.
|
||||
- PASS3/3: (1) final-owner native fetch GET abort/null/no cache/stale badge and fresh exact-event/source replacement healthy; (2) native Response.text cancellation after real body entry: GET aborted/null/no extra cache write, HEAD remains unaborted; (3) two owners share1HEAD/1GET, first release does not abort live GET, remaining owner becomes healthy/cachewrites2.
|
||||
- Body trace hostmonotonicms: GET arrives2078.402, headersflushed2078.682, pinned ASCIIspace0x20 written2078.861, native fetch resolves200 at2083.682, native text-entry2084.018, final-owner dispose/abort2089.382, responseclose2089.557, textrejectAbortError2090.271, probenull2090.355. No body completion before disposal; actual native streaming body path exercised.
|
||||
- Final JSON pass=true,8requests=4HEAD+4GET,all8responsesclosed,pageErrors0,publicDeniedCount4. Controlled loopback endpoints, real native observer/fetch/AbortController/Response.text; module hash pins unchanged. No live public IO, segments, decoding, actual playback, torrent/RangeGET/same-turn browser identity or wholeStage3 qualification.
|
||||
- Comparison: original header-only body GET did not reach fetch resolution/text-entry within5000ms; separately named one-byte held-open stream did, and unchanged cancellation assertions passed. Supports first-byte-sensitive fixture prerequisite for these controlled runs, not universal Firefox protocol behavior or new product defect/fix. Original FAIL remains a separate result.
|
||||
- Exact raw log preserved /tmp/client9-hls-firefox-prefix-20261025T0326.log and state/evidence/client-reliability-9/client9-hls-firefox-prefix-20261025T0326.log. Raw SHA256 6d6ec7e58373fc12580df851dd9321283334a1d969d0d644bba906ce427d25e8. No gate/build/submission; existing dirty evidence preserved. Next action await bounded routing; item9 remains open for separately scoped remaining work.
|
||||
@@ -0,0 +1,16 @@
|
||||
# Client9 source-only evidence reconciliation
|
||||
|
||||
- Routing: interpreter mail 2026-10-26T13:41:14Z requests reconciliation and next bounded lifecycle recommendation; QA owns next sole browser. No client browser/full/build/product changes authorized by this step.
|
||||
- Inspected HEAD 5f8e58bec0ff83383b35bf0968f1cf0f66edc9cb, branch area/client-reliability. Item9 reclaimed, only open area item, filedAt fb54cea3a2ba99b4860421724a5050af6b97c293. Seven pre-existing untracked handoff/evidence files preserved verbatim and included in this reconciliation.
|
||||
- Current verification: git diff --exit-code unstable HEAD -- js tests config package.json package-lock.json => 0; git merge-base --is-ancestor 83fd2495 HEAD => 0; same for f41ac656 => 0; git diff --exit-code 83fd2495 HEAD -- js/ui/urlHealthController.js js/services/hlsPlayback.js => 0. Landed HEAD/HLS source remains present unchanged. No duplicate product tests needed for notes-only work.
|
||||
- Historical full/build evidence: HLS landed 83fd2495 after unchanged normal gate exit0, 437 unique starts; focused129/6files,19 failing mutants,lint11 and build/artifact recorded in TEST_LOG_2026-10-23_13-00-00_client-reliability-9-hls-landed.md. These are prior results, not rerun today. Earlier divergence and thermal interruption remain recorded.
|
||||
- c95abe22 is the recorded dist build version, not an available Git object (git merge-base check rejected it as invalid). Use 83fd2495 for product ancestry. This does not invalidate recorded module hash pins or establish current preview state.
|
||||
- Original Chromium replacement failure and original Firefox header-only body-entry failure remain separate failures (TEST_LOG_2026-10-24_02-55-00_client-reliability-9-browser-checkpoint.md). Diagnostic d0100521 naturally exits1: GET arrives/headers flush, but native fetch does not resolve or enter Response.text within unchanged5000ms; body disposal case never begins, shared case unrun. No cancellation defect demonstrated by that prerequisite failure.
|
||||
- Separately authorized prefix control2988ddf7 naturally exits0,3/3 with cleanup2849ms;8requests/8closed/pageErrors0/publicDenied4. One ASCIIspace held-open stream reaches native fetch/text entry; final-owner GET abort/null/no obsolete cache, replacement healthy and shared live owner controls pass. Existing log TEST_LOG_2026-10-25_03-27-00_client-reliability-9-prefix-result.md records scope/timeline. Current sha256sum of saved raw evidence => 6d6ec7e58373fc12580df851dd9321283334a1d969d0d644bba906ce427d25e8, matches recorded hash.
|
||||
- Qualification: controlled native Chromium original fixture3/3 and Firefox separate prefix control3/3. Original Firefox fixture remains failed; this is evidence of fixture sensitivity in those runs, not universal Firefox behavior or real playback qualification. No live public IO, media segments/decoder, torrent, RangeGET, CPU benefit, same-turn browser replacement or wholeStage3 claim.
|
||||
|
||||
## Recommended next bounded lifecycle item
|
||||
|
||||
- Remain under campaign priority2/item9: actual grid observer -> queue -> TorrentClient.probePeers active final-owner disposal. Source only: js/gridHealth.js releaseProbe cancels queued jobs only; started jobs keep prior cache/lifecycle. Queue cancel returns false once job leaves queue. probePeers in js/webtorrent.js owns timeout/poll and temporary torrent, resolves after deferred destruction, and currently exposes no owner cancellation input. Source suggests an investigation boundary; no current runtime defect has been reproduced in this turn.
|
||||
- Request dispatch routing for offline scratch measurements only, using real observer/grid queue/probePeers with controlled fake WebTorrent IO and clock, public traffic denied. Count final-owner torrent/listeners/timers/settlement/slot/cache/inflight before/after disposal; compare shared live owner, same-turn/later rejoin, late callbacks/replacement, queued competition/FIFO, live wire/error/timeout and delayed teardown. Preserve existing four-slot cap and teardown-before-release safety. Inspect existing integrity/spec before tests; no product/test change until measured defect plus routing.
|
||||
- Keep playback/RangeGET and Amber staged separately per PROGRAM. No new browser/full/build/submission in this checkpoint; no target branch change or preview refresh. Commit is local evidence preservation, not landing proof. Item9 stays open pending separately routed remaining slice.
|
||||
@@ -0,0 +1,39 @@
|
||||
# Client9 active torrent investigation — no product changes
|
||||
|
||||
- Authority: interpreter mail2026-10-26T15:19:48Z, INVESTIGATION only after source plan; actual gridHealth/queue/probePeers with fake IO/clock, no real peer traffic. QA sole browser; no client browser/full/build/product/test edits or submission. Read newest RULE-CHANGES/test-integrity and Stage3 source before scratch.
|
||||
- HEAD e9af76dfd23c6ebdde355d376af05261bdb89f00, own area/client-reliability branch; clean initial tree. Runtime still unchanged at final inspection (`git diff --exit-code e9af76df -- js tests` =>0). filedAt fb54cea3 precedes already landed HEAD/HLS fixes; remaining torrent boundary still applies at current HEAD.
|
||||
- Source plan: import actual js/gridHealth.js/createCardObserver/queue and actual TorrentClient.probePeers. Substitute WebTorrent class at constructor IO boundary with EventEmitter-based temporary torrents; transparent wrapper records actual method settlement. Controlled setTimeout/setInterval clock, synthetic observer delivery/JSDOM cards, fake persistence IO. No copied/rewritten product modules. No live tracker/WebRTC/swarm/decoder/browser. fetch/WebSocket guards record0 attempted calls; fake WebTorrent means no real peer IO. setImmediate only drains task/promise turns; no wall-clock sleeps/retries/timeouts increased/force exit.
|
||||
|
||||
## Commands and retained results
|
||||
|
||||
- `env -u NODE_TEST_CONTEXT -u UNIT_TEST_SHARD node /tmp/client9-torrent-investigation-20261026.mjs > /tmp/client9-torrent-investigation-20261026.log 2>&1`: natural exit1,12cases/5pass/7fail. Five intended Stage3 cancellation assertions fail; two extra controls fail counting1 remaining zero-delay JSDOM StorageImpl event as torrent timer. Source node_modules/jsdom/lib/jsdom/living/webstorage/Storage-impl.js:74 schedules setTimeout on setItem. First script/log preserved unchanged; this is not a product error/timeout resource failure.
|
||||
- Separate storage-isolated fixture keeps actual storage serialization/read functions, substitutes memory map at localStorage IO instead of JSDOM's asynchronous storage-event dispatch. All scenario assertions unchanged. `env -u NODE_TEST_CONTEXT -u UNIT_TEST_SHARD node /tmp/client9-torrent-investigation-storage-isolated-20261026.mjs > /tmp/client9-torrent-investigation-storage-isolated-20261026.log 2>&1`: natural exit1,12cases/7pass/5fail. All12 finally-cleanups activeTorrents0/listeners0/unsettled0/timers0; each created torrent destroyed exactly once, max actual fake torrent resources4. This correction isolates fixture IO and does not change product code or rerun unchanged flakiness.
|
||||
- `env -u NODE_TEST_CONTEXT -u UNIT_TEST_SHARD node --test tests/grid-health-lifecycle.test.mjs > /tmp/client9-grid-existing-contract-20261026.log 2>&1`: natural exit0, runner reports file-level1pass/0fail/0cancelled (~2028ms); no individual scenario count in that output, not called11 from that run.
|
||||
- `env -u NODE_TEST_CONTEXT -u UNIT_TEST_SHARD node tests/grid-health-lifecycle.test.mjs > /tmp/client9-grid-existing-contract-direct-20261026.log 2>&1`: natural exit0, actual11scenarios/11pass/0fail/0cancelled/0skip (~332ms). Direct invocation exposes subtest counts, including prior active-sharing and disposed-active-cache contracts. Existing suite unchanged.
|
||||
- Raw scripts/logs and SHA256 manifest retained under /home/user/.local/state/agent-fleet/bitvid/evidence/client-reliability-9/client9-torrent-investigation-20261026.sha256. Corrected script SHA25696eec664a054ba0d63c903c3f9bb086f57526e17b3df8951056898af162cfbe4; corrected log882780bf806cade580ffe0287709a5cf0f879acc7ea21842561bece63c522832. Original script86b5d189733cdcbc7fab716d3fb48136590f0be6a52955d46586d276aa255ef0/log42ffd9a486f930ef0bd308373c26e15894df71785c48d8672edd480ecadfb792.
|
||||
|
||||
## Corrected fixture observations
|
||||
|
||||
| Case | Result | Boundary evidence |
|
||||
| --- | --- | --- |
|
||||
| Final owner dispose | FAIL | Before and after dispose plus task/zero-delay drain:1torrent,2listeners,1timeout+1poll,1unsettled;0destroy. Expected prompt release without original20s deadline. |
|
||||
| Disposed late wire | FAIL | Controlled late success destroys torrent/cleans timers but populates persistent healthy cache; disposed card remains checking. No stale badge defect. |
|
||||
| Same-turn fresh owner | FAIL | Still1probe/1torrent, expected fresh second probe after prior final-owner disposal; new owner joins old work. |
|
||||
| Later fresh owner | FAIL | Same1probe after task drain; retained obsolete work remains deduplicated. |
|
||||
| Queue capacity after disposal | FAIL | Four active; fifth live source queued. Dispose one active final owner => still4starts/4resources/8listeners/4timeouts+4polls; expected fifth start after obsolete teardown. This is retained occupied capacity, not exceeding cap. |
|
||||
| Shared live owner | PASS | Two feeds share1probe; first dispose retains live work/timer; wire yields healthy live card/cache, disposed card checking. |
|
||||
| Live wire/cache | PASS | Healthy result and cache reuse; no extra add. |
|
||||
| Live error | PASS | Unhealthy/error, temp torrent destruction, timers cleaned, negative cache retained. |
|
||||
| Live20s timeout | PASS | Unsettled at19999 fake ms; timeout settles/unhealthy/cache/cleanup at20000. |
|
||||
| Queued prune/priority/FIFO | PASS | Cancelled queued source never adds/caches; equal priority insertion then lower priority, peak4. |
|
||||
| Deferred teardown/slot | PASS | Wire settles internally but next add waits for zero-delay destroy; destroy precedes fifth add, peak4. |
|
||||
| Late disposed observer | PASS | No old-card updates/new add; late observer guarded. |
|
||||
|
||||
## Contract and smallest proposed routing
|
||||
|
||||
- Source-backed gap from planned Stage3 active cancellation, not a regression against existing queued-only tests. docs/performance-efficiency-plan.md Stage3 says pass abort/cancellation into active torrent probes and removed cards do not retain probes. Current gridHealth releaseProbe intentionally preserves running jobs; job.cancel only removes still-queued entries; webtorrent.probePeers accepts no cancellation owner and retains timeout/poll/temp torrent until wire/error/timeout. Missing signal alone is not proof: actual disposal retains measured resources and delays live queued progress.
|
||||
- Proposed product boundary ONLY js/gridHealth.js ownership/cancelled record/inflight/cache guards and js/webtorrent.js probePeers optional lifecycle plumbing, plus unique focused tests/spec/integrity notes AFTER explicit dispatch routing. Do not change limits/TTLs/magnet hints/tracker policy, shared probe client or playback/service-worker/engine/app behavior.
|
||||
- Mark final-owner cancelled/unavailable synchronously, detach active probe hooks/timers and schedule safe temporary-torrent destruction; settle cancelled work and advance queue exactly once AFTER destruction, preserving existing event-unwind/teardown-before-slot-release safety. Do not destroy the shared probeClient or other owners' torrents. Same-source replacement cannot share cancelled record or be deleted/cached/painted by old continuations. Preserve ownerless standalone helper callers and live/shared wire/error/timeout/cache behavior.
|
||||
- Spec correction needed before altering tests/grid-health-lifecycle.test.mjs scenarios at367(active probe retained across last-owner gap/rejoin) and417(disposed active success caches). Old contract permits these; proposed new contract requires fresh request after final-owner cancellation/no obsolete cache. Preserve separate shared-with-live-owner/live-completion/cache/TTL controls and replace only with equally strict lifecycle/resource assertions under routed Test Integrity Note. No test changes made now.
|
||||
- Future acceptance must include pre-start/at-add cancellation, synchronous/late wire/error callbacks, missing/ignored abort cooperation, same-turn/later replacement identity, cross-feed ownership, repeated release, queue error/cancellation cleanup, live timeout and deferred teardown exception/event-unwind safety. Current scratch does NOT establish these future cases, real WebTorrent network byte/resource behavior, browser/peer playback, CPU improvement, HLS/RangeGET or wholeStage3.
|
||||
- DONE for this investigation: current-HEAD measured gap, preserved failed baseline+controls, smallest boundary/contract proposal. WAITING for dispatch routing/independent reproduction before any implementation or retained tests. Item9 remains open; evidence commit is local, not landed/qualified product.
|
||||
@@ -0,0 +1,16 @@
|
||||
# Client9 focused implementation validation (in progress)
|
||||
|
||||
- Explicit dispatch17:38 routing read before implementation; baseline independently reproduced, no repeat. Spec change + integrity note written before old367/417 expectation edits.
|
||||
- `env -u NODE_TEST_CONTEXT -u UNIT_TEST_SHARD node tests/grid-health-active-torrent.test.mjs > /tmp/client9-torrent-focused-20261026-1.log 2>&1`: naturalexit0,24/24,0fail/0cancelled (~355ms).
|
||||
- `env -u NODE_TEST_CONTEXT -u UNIT_TEST_SHARD node tests/grid-health-lifecycle.test.mjs > /tmp/client9-grid-corrected-20261026-1.log 2>&1`: naturalexit0,11/11,0fail/0cancelled (~263ms); explicit active sharing/cache spec correction only, live/cache controls retained.
|
||||
- `env -u NODE_TEST_CONTEXT -u UNIT_TEST_SHARD node scripts/run-targeted-tests.mjs tests/grid-health-active-torrent.test.mjs tests/grid-health-lifecycle.test.mjs tests/grid-health.test.mjs tests/webtorrent-regression.test.mjs tests/webtorrent-handlers.test.mjs tests/minimal-webtorrent.test.mjs > /tmp/client9-torrent-targeted-20261026-2.log 2>&1`: naturalexit0,6files/58scenarios=25+11+6+3+12+1 pass,0fail/0cancelled. Includes added late error after cancellation inside add() and add-throw/shared-client reuse. No broad runner or browser involved.
|
||||
- Later small test additions strengthen queued prune helper invocation and bound reentrant add callback in fixture; final focused rerun pending. Mutants and targeted lint pending. No full/build/browser/submission/landing or actual peers/network/CPU/wholeStage3 qualification.
|
||||
|
||||
## Restart handoff after focused boundary
|
||||
|
||||
- Final targeted command (same6files as above) output `/tmp/client9-torrent-targeted-20261026-final.log`: naturalexit0,60/60=27+11+6+3+12+1,0fail/0cancelled. Added strict queued-helper prune assertion, missing AbortController cancellation and error+throwing teardown queued progress. No tests weakened. Product delta remains2routed files, old suite only2explicit spec corrections.
|
||||
- Mutants: `env -u NODE_TEST_CONTEXT -u UNIT_TEST_SHARD python3 /tmp/client9-torrent-mutants-20261026.py > /tmp/client9-torrent-mutants-20261026.log 2>&1`: naturalexit0 harness;19/19unique mutations killed by actual assertion failures, no survivors; each mutated new/old suite run exits naturally and reports cancelled0. Initial25new scenarios all covered. Additional isolated skip-destroy run with final27scenario file: naturalexit1,25assertion failures/0cancelled, including both added cases. Total19unique mutants/20variant executions; all27newscenarios have a recorded killing variant. No worktree mutation/restoration required: copies under/tmp only. Product formatting after initial mutation run changed no behavior; final focused60 covers current source.
|
||||
- Targeted lint: `env -u NODE_TEST_CONTEXT -u UNIT_TEST_SHARD npm run lint:file-size`, `lint:innerhtml`, `lint:pool-access` each exit0 (separate logs). Same `lint:hex` first exits2 because spawnSync git EPERM; unchanged host-escalated command approved and exits0. Both results retained. No full npm run lint/all11 claim; remaining checks and unchanged full/build/browser/normal gate require later resource grant.
|
||||
- Current upstream unstable04c8c7cf is4kit/setup commits ahead; read its newest2026-10-26 RULE-CHANGES via git show. Runtime js/tests/config/package inputs diff0 versus current committedHEAD; our workingtree changes remain local and uncommitted. No merge before this requested restart. Updated rules make swap occupancy advisory with ampleRAM/no swap-in; old blanket holds are superseded, but no heavy grant inferred.
|
||||
- Durable copy: `/home/user/.local/state/agent-fleet/bitvid/evidence/client9-torrent-impl-20261026/` contains final4candidate files, original+finalfocused outputs, all targeted lint outputs incl EPERM+host pass, mutant runner/19variant copies+logs/summary, extended variant, per-new-scenario mutant map and manifest.sha256. Existing failure baselines and earlier evidence commits retained. No new commit/submission/landing; HEAD remainsab1ea6dee1da7eebd2b5c08f3d5935dfbfb045d0.
|
||||
- STOP/SAFE TO RESTART per interpreter185753/190045 mails. Next resumed step: read live rules/mailbox/task and saved snapshots; inspect final2-file delta and tests, finish decision/integrity/READY notes, then commit named paths usingfleet tool and merge currentunstable (neverrebase). Focused60 need not repeat unless source changes; merged-tree/runtime equality determines remainingcheck need. Explicit resource grant still required before full/build/browser/submission. Real peers/browser/CPU/wholeStage3 unverified. No process stops, discard/stash/reset or kit work by client.
|
||||
@@ -0,0 +1,8 @@
|
||||
# Client9 resumed candidate review
|
||||
|
||||
- Restart HEAD `ab1ea6dee1da7eebd2b5c08f3d5935dfbfb045d0`; unstable `04c8c7cf61eed29424f561297ef49c6cdfa91a0a`. Reclaimed existing item9; mailbox empty. Read local rules and newer unstable rules (2026-10-26) plus saved dispatch torrent routing. No repeated baseline or new scope.
|
||||
- Review: final-owner synchronous cancellation/inflight removal, optional probePeers hook, prompt timer cleanup and deferred temporary-torrent destruction before slot release; shared client/owners and identity guards preserved. Product files only gridHealth.js and webtorrent.js; two explicitly authorized old expectations corrected. No product edits this turn.
|
||||
- `sha256sum` of four candidate source/test files matches durable manifest exactly. Existing six-file focused evidence remains 60/60, fail0/cancelled0; 19 unique killed mutants, 20 variant executions/all27 new scenarios covered. This is preserved prior-run evidence, not a new test run.
|
||||
- `git diff --check`: exit0. `git diff ab1ea6de unstable -- js tests config package.json package-lock.json`: exit0, empty. Upstream kit changes do not change those validation inputs. No duplicate focused run justified.
|
||||
- Fresh MCP health19:07:29UTC: TCPU97.1C exceeds85C threshold; load11.58, availableRAM64.2%, swapInPages0. Heavy work held; no test/build/browser/submission launched. Hold owner dispatch; next allowed light work candidate commit/merge/runtime comparison. Need fresh resource clearance and explicit slot.
|
||||
- Remaining full lint, merged-tree normal full gate, build/browser qualification and durable landing/ancestry. No live peer/network teardown, playback, CPU improvement or whole Stage3 qualification.
|
||||
@@ -0,0 +1,7 @@
|
||||
# Merged candidate READY checkpoint
|
||||
|
||||
- Fleet commit produced `1b2362d2341414e69f67352e17529ed5dfedd45e` with named paths and no submission.
|
||||
- Initial sandbox `git merge --no-edit unstable` failed: ORIG_HEAD.lock read-only filesystem. No reset/rewrite attempted. Same merge approved via host escalation, exit0, resulting `1bf50c1f7a55740aa1d1582045cd88033a8de56e` against unstable `04c8c7cf61eed29424f561297ef49c6cdfa91a0a`.
|
||||
- Postmerge `git status --short --branch`: clean area/client-reliability. `git diff 1b2362d2 HEAD -- js tests config package.json package-lock.json`: exit0/empty. `git diff --check`: exit0. No new validation input changes; saved focused and mutant evidence preserved without repeat.
|
||||
- Dispatch blocker/READY message delivered via MCP at19:08:37; hold recorded and progress checkpoint written. Heavy clearance pending fresh health and explicit serialized slot; no full/build/browser/submission/landing.
|
||||
- Prior focused60/60 and19unique killed mutants are saved evidence, not new runs. Full lint and live/real transport qualification remain open.
|
||||
@@ -0,0 +1,7 @@
|
||||
# Client9 exclusive lint stage
|
||||
|
||||
- Dispatch exclusive slot19:10:24 after QA RELEASE19:09:25.820; source review accepted. HEADc20635bc (product1b2362d2, merged1bf50c1f), source unchanged. No baseline/focused repeat.
|
||||
- `env -u NODE_TEST_CONTEXT -u UNIT_TEST_SHARD npm run lint > state/evidence/client9-torrent-validation-20261026/lint-sandbox-1911.log 2>&1`: exit2 after css pass at hex subprocess spawnSync git EPERM. Preserved environmental failure.
|
||||
- Identical approved host command with output lint-host-1911.log: natural exit0; all11 lint checks pass. No lint suppression/baseline/config changes.
|
||||
- Fresh health19:11:15 OK/max<75C/load0.9/RAM64.1%/swap-in0;19:11:48 OK/max76.1C/load1.16/RAM65.1%/swap-in0. Earlier transient91.1C19:10:39 retained; no work ran then. Hold cleared, START delivered19:11:24.
|
||||
- Submit current committedc20635bc to unchanged normal queue; this uncommitted log is evidence only and does not change candidate. No build/browser grant; live peers/byte teardown/playback/CPU/wholeStage3 unverified.
|
||||
@@ -0,0 +1,10 @@
|
||||
# Client9 normal-gate thermal interruption
|
||||
|
||||
- Committed candidate/submission `c20635bc8539328ca1677f3cf63cbe870adeb9c2`; product1b2362d2/merge1bf50c1f, normal queue START19:12:41. No runner/gate/assertion changes or separate full run.
|
||||
- Required full lint11/11 passed via approved host route; original sandbox hex spawnSync git EPERM retained. See lint-stage log.
|
||||
- Health sequence during gate:19:13:35 max87.1C;19:13:51 max87.1C;19:14:05 max83.8C;19:14:40 max90.1C/load12.77;19:15:18 max86C/load13.5. Final two hot samples span38s; thermal threshold85C sustained. No claim heat is caused exclusively by this job or other apps.
|
||||
- Host inspection verified gate process group204078, parentlander204041, cwd bitvid-lander and timeout1800s. Approved SIGINT only that group19:15:32. No unrelated process stopped. This is an interrupted run, not a natural full pass or a product assertion failure.
|
||||
- Durable `land_status c20635bc`: rejected/red, unchanged normal gate exit1 at19:15:32.782; lander false. `git rev-parse HEAD unstable`: c20635bc /04c8c7cf61eed29424f561297ef49c6cdfa91a0a. Not landed; no ancestry-success claim.
|
||||
- Independent runner-equivalent discovery438; partial starts275/unique275/unexpected0, last tests/settings-restore-prompt.test.mjs,163neverstarted. Last started file has no completion evidence. No actual not-ok or nonzero fail/cancelled lines in preserved partial log; not an all-suite result.
|
||||
- Complete partial log/result copied before any next run to state/evidence/client9-torrent-validation-20261026/test_unit-interrupted-1915.log and result-c20635bc-interrupted-1915.json, hashed in manifest-1915.sha256. Both lint logs also retained there.
|
||||
- RELEASE and material hold delivered dispatch19:15:58. New explicit hold: dispatch fresh thermal/resource clearance and renewed exclusive slot; NO automatic resubmission. Next light work evidence/discovery review; source unchanged. No build/browser grant, real transport/peer/playback/CPU/wholeStage3 qualification.
|
||||
@@ -0,0 +1,435 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { EventEmitter } from 'node:events';
|
||||
import { JSDOM } from 'jsdom';
|
||||
import './test-helpers/setup-localstorage.mjs';
|
||||
const root = new URL('../', import.meta.url).href;
|
||||
const { torrentClient } = await import(`${root}js/webtorrent.js`);
|
||||
const { attachHealthBadges } = await import(`${root}js/gridHealth.js`);
|
||||
const { readTorrentProbeFromStorage } = await import(`${root}js/utils/storage.js`);
|
||||
const { getCardLivenessPolicy, setCardLivenessPolicy } = await import(`${root}js/constants.js`);
|
||||
const turn = () => new Promise(resolve => setImmediate(resolve));
|
||||
let nextHash = 100000;
|
||||
const magnet = () => `magnet:?xt=urn:btih:${(nextHash++).toString(16).padStart(40, '0')}`;
|
||||
|
||||
class Clock {
|
||||
now = 0; next = 1; jobs = new Map();
|
||||
add(fn, ms, interval = false) {
|
||||
const id = this.next++;
|
||||
this.jobs.set(id, { fn, ms, at: this.now + ms, interval });
|
||||
return id;
|
||||
}
|
||||
tick(ms) {
|
||||
const end = this.now + ms;
|
||||
let steps = 0;
|
||||
while (true) {
|
||||
const entry = [...this.jobs].filter(([, job]) => job.at <= end)
|
||||
.sort((a, b) => a[1].at - b[1].at || a[0] - b[0])[0];
|
||||
if (!entry) break;
|
||||
assert.ok(++steps < 1000, 'fake clock must remain bounded');
|
||||
const [id, job] = entry;
|
||||
this.now = job.at;
|
||||
if (job.interval) job.at += job.ms;
|
||||
else this.jobs.delete(id);
|
||||
job.fn();
|
||||
}
|
||||
this.now = end;
|
||||
}
|
||||
counts() {
|
||||
const all = [...this.jobs.values()];
|
||||
return { timeout: all.filter(x => !x.interval && x.ms > 0).length,
|
||||
poll: all.filter(x => x.interval).length,
|
||||
teardown: all.filter(x => !x.interval && x.ms === 0).length };
|
||||
}
|
||||
}
|
||||
|
||||
function fixture(t) {
|
||||
const dom = new JSDOM('<!doctype html><body></body>', { url: 'https://example.invalid' });
|
||||
Object.defineProperty(dom.window, 'innerHeight', { value: 20 });
|
||||
const names = ['window', 'document', 'HTMLElement', 'localStorage', 'IntersectionObserver',
|
||||
'setTimeout', 'clearTimeout', 'setInterval', 'clearInterval', 'fetch', 'WebSocket', 'AbortController'];
|
||||
const saved = new Map(names.map(key => [key, Object.getOwnPropertyDescriptor(globalThis, key)]));
|
||||
const previousPolicy = getCardLivenessPolicy();
|
||||
const clock = new Clock();
|
||||
const config = {};
|
||||
// Isolate persistence IO: JSDOM StorageImpl schedules unrelated storage-event timers.
|
||||
// Production read/writeTorrentProbeToStorage still executes against this map.
|
||||
const store = new Map();
|
||||
const writes = [];
|
||||
const memoryStorage = { getItem: key => store.get(key) ?? null,
|
||||
setItem: (key, value) => { writes.push([key, value]); store.set(String(key), String(value)); },
|
||||
removeItem: key => store.delete(key), clear: () => store.clear() };
|
||||
const observers = [], torrents = [], calls = [], disposals = [], updates = [], timeline = [];
|
||||
let networkAttempts = 0, peak = 0;
|
||||
class Observer {
|
||||
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, index) => ({ target, isIntersecting: true,
|
||||
intersectionRatio: 1, intersectionRect: { top: index * 20, width: 100, height: 20 } })));
|
||||
}
|
||||
}
|
||||
class FakeTorrent extends EventEmitter {
|
||||
constructor(source, options) {
|
||||
super(); this.source = source; this.options = options; this.numPeers = 0;
|
||||
this.destroyed = false; this.destroyCount = 0; this.emitting = false;
|
||||
}
|
||||
on(event, listener) {
|
||||
super.on(event, listener);
|
||||
if (config.syncEvent === event && !this.syncEmitted) {
|
||||
this.syncEmitted = true;
|
||||
if (event === 'wire') this.wire(); else this.error();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
destroy(options) {
|
||||
assert.deepEqual(options, { destroyStore: true });
|
||||
this.destroyedDuringEvent = this.emitting;
|
||||
this.destroyedDuringAdd = config.adding === true;
|
||||
this.destroyed = true; this.destroyCount++;
|
||||
if (config.throwDestroy) throw new Error('controlled destroy exception');
|
||||
this.removeAllListeners();
|
||||
timeline.push(['destroy', this.source, clock.now]);
|
||||
}
|
||||
wire() { this.numPeers = 1; this.emitting = true;
|
||||
try { this.emit('wire', {}); } finally { this.emitting = false; } }
|
||||
error() { this.emit('error', new Error('controlled fake torrent error')); }
|
||||
}
|
||||
class FakeClient {
|
||||
constructor() { config.onConstruct?.(); }
|
||||
setMaxListeners() {}
|
||||
destroy() { assert.fail('shared probe client must never be destroyed'); }
|
||||
add(source, options) {
|
||||
if (config.throwAdd) throw new Error('controlled add exception');
|
||||
const torrent = new FakeTorrent(source, options);
|
||||
torrents.push(torrent);
|
||||
peak = Math.max(peak, torrents.filter(x => !x.destroyed).length);
|
||||
timeline.push(['add', source, clock.now]);
|
||||
config.adding = true;
|
||||
try { config.onAdd?.(torrent); } finally { config.adding = false; }
|
||||
return torrent;
|
||||
}
|
||||
}
|
||||
Object.assign(globalThis, { window: dom.window, document: dom.window.document,
|
||||
HTMLElement: dom.window.HTMLElement, localStorage: memoryStorage,
|
||||
IntersectionObserver: Observer,
|
||||
setTimeout: (fn, ms = 0) => clock.add(fn, ms), clearTimeout: id => clock.jobs.delete(id),
|
||||
setInterval: (fn, ms) => clock.add(fn, ms, true), clearInterval: id => clock.jobs.delete(id),
|
||||
fetch: () => { networkAttempts++; throw new Error('network forbidden'); },
|
||||
WebSocket: class { constructor() { networkAttempts++; throw new Error('network forbidden'); } }
|
||||
});
|
||||
const savedClient = { probeClient: torrentClient.probeClient, WebTorrentClass: torrentClient.WebTorrentClass,
|
||||
probePeers: torrentClient.probePeers };
|
||||
torrentClient.probeClient = null;
|
||||
torrentClient.WebTorrentClass = FakeClient;
|
||||
// Transparent measurement wrapper: execute the actual production method, do not stub its logic.
|
||||
torrentClient.probePeers = function(source, options, lifecycle) {
|
||||
const call = { source, options, lifecycle, settled: false, result: undefined };
|
||||
calls.push(call);
|
||||
return savedClient.probePeers.call(this, source, options, lifecycle).then(result => {
|
||||
call.settled = true; call.result = result; return result;
|
||||
});
|
||||
};
|
||||
setCardLivenessPolicy('show-pending');
|
||||
function feed(sources) {
|
||||
const container = document.createElement('section');
|
||||
const cards = sources.map(source => {
|
||||
const card = document.createElement('article');
|
||||
card.className = 'card'; card.dataset.videoId = source; card.dataset.magnet = source;
|
||||
const badge = document.createElement('span'); badge.className = 'torrent-health-badge';
|
||||
card.append(badge); container.append(card); return card;
|
||||
});
|
||||
document.body.append(container);
|
||||
const events = [];
|
||||
const dispose = attachHealthBadges(container, { onUpdate: event => { events.push(event); updates.push(event); } });
|
||||
disposals.push(dispose);
|
||||
return { container, cards, dispose, events, observer: observers.at(-1),
|
||||
show() { this.observer.show(); } };
|
||||
}
|
||||
const cache = source => readTorrentProbeFromStorage(source.match(/btih:([a-f0-9]{40})/)[1]);
|
||||
const snapshot = source => ({ started: torrents.length, activeTorrents: torrents.filter(x => !x.destroyed).length,
|
||||
destroyed: torrents.filter(x => x.destroyed).length,
|
||||
listeners: torrents.reduce((n, t) => n + t.eventNames().reduce((m, e) => m + t.listenerCount(e), 0), 0),
|
||||
unsettled: calls.filter(x => !x.settled).length, timers: clock.counts(),
|
||||
cache: source ? cache(source) : undefined, peak, networkAttempts });
|
||||
async function drain() { await turn(); clock.tick(0); await turn(); }
|
||||
async function cleanup() {
|
||||
disposals.forEach(dispose => dispose());
|
||||
for (let i = 0; i < 10 && (calls.some(x => !x.settled) || clock.jobs.size); i++) {
|
||||
torrents.filter(x => !x.destroyed).forEach(t => t.wire());
|
||||
await drain();
|
||||
}
|
||||
const final = snapshot();
|
||||
|
||||
try {
|
||||
assert.equal(final.activeTorrents, 0); assert.equal(final.unsettled, 0);
|
||||
assert.equal(final.listeners, 0); assert.equal(clock.jobs.size, 0);
|
||||
assert.equal(networkAttempts, 0);
|
||||
assert.ok(torrents.every(x => x.destroyCount === 1), 'destroy exactly once');
|
||||
assert.ok(torrents.every(x => !x.destroyedDuringEvent && !x.destroyedDuringAdd), 'destroy after event/add unwind');
|
||||
assert.ok(calls.every(x => !x.lifecycle?.cancelActive), 'cancel hook removed');
|
||||
} finally {
|
||||
Object.assign(torrentClient, savedClient);
|
||||
setCardLivenessPolicy(previousPolicy);
|
||||
dom.window.close();
|
||||
for (const [key, desc] of saved) {
|
||||
if (desc) Object.defineProperty(globalThis, key, desc); else delete globalThis[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
t.after(cleanup);
|
||||
return { feed, cache, snapshot, drain, clock, torrents, calls, timeline, config, writes,
|
||||
probe: (source, lifecycle) => torrentClient.probePeers(source, { timeoutMs: 20000 }, lifecycle),
|
||||
get peak() { return peak; } };
|
||||
}
|
||||
|
||||
const cases = [
|
||||
['final-owner-cleanup', async f => {
|
||||
const source = magnet(), feed = f.feed([source]); feed.show();
|
||||
assert.equal(f.torrents.length, 1);
|
||||
feed.dispose(); await f.drain();
|
||||
assert.equal(f.snapshot().activeTorrents, 0, 'last owner must not retain an active torrent');
|
||||
assert.equal(f.snapshot().unsettled, 0, 'last owner must settle without original timeout');
|
||||
assert.equal(f.calls[0].result, null);
|
||||
assert.equal(f.calls[0].lifecycle.cancelActive, null);
|
||||
assert.equal(f.clock.jobs.size, 0);
|
||||
assert.equal(f.cache(source), null);
|
||||
}],
|
||||
['disposed-late-success-no-cache', async f => {
|
||||
const source = magnet(), feed = f.feed([source]); feed.show(); feed.dispose();
|
||||
f.torrents[0].wire(); await f.drain();
|
||||
assert.equal(feed.cards[0].dataset.streamHealthState, 'checking', 'stale badge guard retained');
|
||||
assert.equal(f.cache(source), null, 'obsolete active completion must not populate persistent cache');
|
||||
}],
|
||||
['same-turn-fresh-rejoin', async f => {
|
||||
const source = magnet(), old = f.feed([source]); old.show(); old.dispose();
|
||||
const replacement = f.feed([source]); replacement.show();
|
||||
await f.drain();
|
||||
assert.equal(f.calls.length, 2, 'new same-turn owner must start a fresh probe');
|
||||
}],
|
||||
['later-fresh-rejoin', async f => {
|
||||
const source = magnet(), old = f.feed([source]); old.show(); old.dispose(); await f.drain();
|
||||
const replacement = f.feed([source]); replacement.show(); await f.drain();
|
||||
assert.equal(f.calls.length, 2, 'new later owner must start a fresh probe');
|
||||
}],
|
||||
['disposal-releases-fifo-capacity', async f => {
|
||||
const active = Array.from({ length: 4 }, () => f.feed([magnet()])); active.forEach(x => x.show());
|
||||
const queued = f.feed([magnet()]); queued.show(); assert.equal(f.calls.length, 4);
|
||||
active[0].dispose(); await f.drain();
|
||||
assert.equal(f.calls.length, 5, 'queued live owner must gain released obsolete active capacity');
|
||||
assert.ok(f.peak <= 4);
|
||||
}],
|
||||
['shared-live-owner', async f => {
|
||||
const source = magnet(), old = f.feed([source]), live = f.feed([source]); old.show(); live.show();
|
||||
assert.equal(f.calls.length, 1); old.dispose(); await f.drain();
|
||||
assert.equal(f.snapshot().activeTorrents, 1); assert.equal(f.snapshot().timers.timeout, 1);
|
||||
f.torrents[0].wire(); await f.drain();
|
||||
assert.equal(live.cards[0].dataset.streamHealthState, 'healthy');
|
||||
assert.equal(old.cards[0].dataset.streamHealthState, 'checking'); assert.ok(f.cache(source));
|
||||
}],
|
||||
['live-wire-cache', async f => {
|
||||
const source = magnet(), live = f.feed([source]); live.show(); f.torrents[0].wire(); await f.drain();
|
||||
assert.equal(live.cards[0].dataset.streamHealthState, 'healthy'); assert.ok(f.cache(source));
|
||||
const cached = f.feed([source]); cached.show(); assert.equal(f.calls.length, 1);
|
||||
assert.equal(cached.cards[0].dataset.streamHealthState, 'healthy');
|
||||
}],
|
||||
['live-error-cleanup-cache', async f => {
|
||||
const source = magnet(), live = f.feed([source]); live.show(); f.torrents[0].error(); await f.drain();
|
||||
assert.equal(live.cards[0].dataset.streamHealthState, 'unhealthy');
|
||||
assert.equal(f.calls[0].result.reason, 'error'); assert.ok(f.cache(source));
|
||||
assert.equal(f.snapshot().activeTorrents, 0); assert.equal(f.clock.jobs.size, 0);
|
||||
}],
|
||||
['live-timeout-cleanup-cache', async f => {
|
||||
const source = magnet(), live = f.feed([source]); live.show();
|
||||
f.clock.tick(19999); await turn(); assert.equal(f.calls[0].settled, false);
|
||||
f.clock.tick(1); await turn();
|
||||
assert.equal(live.cards[0].dataset.streamHealthState, 'unhealthy');
|
||||
assert.equal(f.calls[0].result.reason, 'timeout'); assert.ok(f.cache(source));
|
||||
assert.equal(f.snapshot().activeTorrents, 0); assert.equal(f.clock.jobs.size, 0);
|
||||
}],
|
||||
['queued-cancel-priority-fifo', async f => {
|
||||
const active = f.feed(Array.from({ length: 4 }, magnet)); active.show();
|
||||
const firstSource = magnet(), lowSource = magnet(), equalSource = magnet(), cancelledSource = magnet();
|
||||
const batch = f.feed([firstSource, lowSource]); batch.show();
|
||||
const equal = f.feed([equalSource]); equal.show();
|
||||
const cancelled = f.feed([cancelledSource]); cancelled.show(); cancelled.dispose();
|
||||
for (const [slot, expected] of [[0, firstSource], [4, equalSource], [5, lowSource]]) {
|
||||
f.torrents[slot].wire(); await f.drain();
|
||||
assert.ok(f.torrents.at(-1).source.includes(expected.slice(-40)));
|
||||
}
|
||||
assert.equal(f.torrents.some(x => x.source.includes(cancelledSource.slice(-40))), false);
|
||||
assert.equal(f.calls.some(x => x.source === cancelledSource), false, 'pruned queued job never invokes helper');
|
||||
assert.ok(f.peak <= 4); assert.equal(f.cache(cancelledSource), null);
|
||||
}],
|
||||
['deferred-teardown-precedes-next-slot', async f => {
|
||||
const active = f.feed(Array.from({ length: 4 }, magnet)); active.show();
|
||||
const queued = f.feed([magnet()]); queued.show();
|
||||
f.torrents[0].wire(); await turn();
|
||||
assert.equal(f.calls.length, 4, 'slot retained until delayed destroy');
|
||||
assert.equal(f.snapshot().timers.teardown, 1);
|
||||
f.clock.tick(0); await turn(); assert.equal(f.calls.length, 5); assert.equal(f.peak, 4);
|
||||
const destroyIndex = f.timeline.findIndex(x => x[0] === 'destroy');
|
||||
assert.equal(f.timeline[destroyIndex + 1][0], 'add');
|
||||
}],
|
||||
['late-observer-disposed-guard', async f => {
|
||||
const old = f.feed([magnet()]); old.show(); old.dispose();
|
||||
const before = old.events.length; old.observer.show(); f.torrents[0].wire(); await f.drain();
|
||||
assert.equal(old.events.length, before); assert.equal(f.calls.length, 1);
|
||||
assert.equal(old.cards[0].dataset.streamHealthState, 'checking');
|
||||
}],
|
||||
['old-callbacks-cannot-affect-replacement', async f => {
|
||||
const source = magnet(), old = f.feed([source]); old.show();
|
||||
const wire = f.torrents[0].listeners('wire')[0];
|
||||
const error = f.torrents[0].listeners('error')[0];
|
||||
const timers = [...f.clock.jobs.values()].map(job => job.fn);
|
||||
old.dispose();
|
||||
assert.equal(f.calls[0].lifecycle.cancelled, true, 'cancelled identity unavailable synchronously');
|
||||
const replacement = f.feed([source]); replacement.show();
|
||||
await f.drain();
|
||||
wire(); error(new Error('late')); timers.forEach(fn => fn()); await f.drain();
|
||||
const shared = f.feed([source]); shared.show();
|
||||
assert.equal(f.calls.length, 2, 'old finalizer must not delete replacement identity');
|
||||
assert.equal(f.cache(source), null);
|
||||
assert.equal(replacement.cards[0].dataset.streamHealthState, 'checking');
|
||||
f.torrents[1].wire(); await f.drain();
|
||||
assert.equal(f.writes.length, 1, 'only the new live result is cached');
|
||||
assert.equal(replacement.cards[0].dataset.streamHealthState, 'healthy');
|
||||
assert.equal(shared.cards[0].dataset.streamHealthState, 'healthy');
|
||||
assert.equal(old.cards[0].dataset.streamHealthState, 'checking');
|
||||
}],
|
||||
['repeated-release-and-noncooperative-io', async f => {
|
||||
const live = f.feed([magnet()]); live.show();
|
||||
const cancel = f.calls[0].lifecycle.cancelActive;
|
||||
const lateWire = f.torrents[0].listeners('wire')[0];
|
||||
live.dispose(); live.dispose(); cancel(); cancel();
|
||||
// No fake transport completion or abort event is required to settle.
|
||||
await f.drain();
|
||||
assert.equal(f.calls[0].settled, true); assert.equal(f.calls[0].result, null);
|
||||
assert.equal(f.torrents[0].destroyCount, 1);
|
||||
lateWire(); cancel(); await f.drain();
|
||||
assert.equal(f.calls[0].result, null); assert.equal(f.torrents[0].destroyCount, 1);
|
||||
}],
|
||||
['cancel-before-probe-start', async f => {
|
||||
const lifecycle = { cancelled: true, owners: new Set() };
|
||||
const promise = f.probe(magnet(), lifecycle); await f.drain();
|
||||
assert.equal(f.calls[0].settled, true); assert.equal(await promise, null);
|
||||
assert.equal(f.torrents.length, 0); assert.equal(torrentClient.probeClient, null);
|
||||
assert.equal(f.clock.jobs.size, 0);
|
||||
}],
|
||||
['cancel-during-client-construction', async f => {
|
||||
const live = f.feed([magnet()]);
|
||||
f.config.onConstruct = () => live.dispose(); live.show(); await f.drain();
|
||||
assert.equal(f.calls[0].settled, true); assert.equal(f.calls[0].result, null);
|
||||
assert.equal(f.torrents.length, 0); assert.equal(f.clock.jobs.size, 0);
|
||||
assert.equal(f.calls[0].lifecycle.cancelActive, null);
|
||||
}],
|
||||
['cancel-during-add-defers-destroy', async f => {
|
||||
const source = magnet(), live = f.feed([source]);
|
||||
f.config.onAdd = () => live.dispose(); live.show();
|
||||
assert.equal(f.calls[0].lifecycle.cancelled, true);
|
||||
assert.equal(f.calls[0].settled, false);
|
||||
assert.equal(f.torrents[0].destroyCount, 0);
|
||||
assert.equal(f.clock.counts().timeout, 0); assert.equal(f.clock.counts().poll, 0);
|
||||
f.torrents[0].error(); // Noncooperative IO can report an error before deferred destruction.
|
||||
await f.drain();
|
||||
assert.equal(f.calls[0].settled, true); assert.equal(f.calls[0].result, null);
|
||||
assert.equal(f.torrents[0].destroyCount, 1);
|
||||
assert.equal(f.torrents[0].destroyedDuringAdd, false); assert.equal(f.cache(source), null);
|
||||
}],
|
||||
['same-source-owner-registers-during-add', async f => {
|
||||
const source = magnet(), first = f.feed([source]);
|
||||
let shared;
|
||||
f.config.onAdd = () => {
|
||||
f.config.onAdd = null;
|
||||
shared = f.feed([source]); shared.show(); first.dispose();
|
||||
};
|
||||
first.show(); assert.equal(f.calls.length, 1);
|
||||
assert.equal(f.calls[0].lifecycle.cancelled, false);
|
||||
f.torrents[0].wire(); await f.drain();
|
||||
assert.equal(shared.cards[0].dataset.streamHealthState, 'healthy');
|
||||
assert.equal(first.cards[0].dataset.streamHealthState, 'checking');
|
||||
}],
|
||||
['cancel-on-wire-unwind-before-destroy', async f => {
|
||||
const live = f.feed([magnet()]); live.show();
|
||||
f.torrents[0].on('wire', () => live.dispose());
|
||||
f.torrents[0].wire();
|
||||
assert.equal(f.torrents[0].destroyCount, 0);
|
||||
await f.drain();
|
||||
assert.equal(f.calls[0].settled, true); assert.equal(f.calls[0].result, null);
|
||||
assert.equal(f.torrents[0].destroyedDuringEvent, false); assert.equal(f.writes.length, 0);
|
||||
}],
|
||||
['cancel-on-error-unwind-before-destroy', async f => {
|
||||
const live = f.feed([magnet()]); live.show();
|
||||
f.torrents[0].on('error', () => live.dispose());
|
||||
f.torrents[0].error();
|
||||
assert.equal(f.torrents[0].destroyCount, 0);
|
||||
await f.drain();
|
||||
assert.equal(f.calls[0].result, null); assert.equal(f.writes.length, 0);
|
||||
assert.equal(f.torrents[0].destroyedDuringEvent, false);
|
||||
}],
|
||||
['throwing-destroy-still-settles-and-cleans-own-listeners', async f => {
|
||||
const live = f.feed([magnet()]); live.show();
|
||||
f.config.throwDestroy = true; live.dispose(); await f.drain();
|
||||
assert.equal(f.calls[0].settled, true); assert.equal(f.calls[0].result, null);
|
||||
assert.equal(f.torrents[0].destroyCount, 1);
|
||||
assert.equal(f.torrents[0].listenerCount('wire'), 0);
|
||||
assert.equal(f.torrents[0].listenerCount('error'), 0);
|
||||
assert.equal(f.calls[0].lifecycle.cancelActive, null);
|
||||
}],
|
||||
['standalone-ownerless-caller-stays-live', async f => {
|
||||
const source = magnet(), promise = f.probe(source);
|
||||
assert.equal(f.calls[0].lifecycle, undefined);
|
||||
f.torrents[0].wire(); await f.drain(); assert.equal(f.calls[0].settled, true);
|
||||
const result = await promise;
|
||||
assert.equal(result.healthy, true); assert.equal(result.reason, 'peer');
|
||||
assert.equal(f.torrents[0].destroyCount, 1);
|
||||
}],
|
||||
['synchronous-wire-listener-registration', async f => {
|
||||
f.config.syncEvent = 'wire'; const promise = f.probe(magnet());
|
||||
assert.equal(f.torrents[0].destroyCount, 0);
|
||||
await f.drain(); assert.equal(f.calls[0].settled, true);
|
||||
assert.equal((await promise).healthy, true); assert.equal(f.clock.jobs.size, 0);
|
||||
}],
|
||||
['synchronous-error-listener-registration', async f => {
|
||||
f.config.syncEvent = 'error'; const promise = f.probe(magnet());
|
||||
await f.drain(); assert.equal(f.calls[0].settled, true);
|
||||
assert.equal((await promise).reason, 'error'); assert.equal(f.clock.jobs.size, 0);
|
||||
}],
|
||||
['add-error-cleans-hook-and-keeps-shared-client', async f => {
|
||||
const lifecycle = { cancelled: false, owners: new Set([{}]) };
|
||||
f.config.throwAdd = true; const promise = f.probe(magnet(), lifecycle);
|
||||
await f.drain(); assert.equal(f.calls[0].settled, true);
|
||||
assert.equal((await promise).reason, 'error'); assert.equal(lifecycle.cancelActive, null);
|
||||
assert.equal(f.torrents.length, 0); assert.equal(f.clock.jobs.size, 0);
|
||||
const client = torrentClient.probeClient;
|
||||
f.config.throwAdd = false; const next = f.probe(magnet());
|
||||
f.torrents[0].wire(); await f.drain(); assert.equal(f.calls[1].settled, true);
|
||||
assert.equal((await next).healthy, true); assert.equal(torrentClient.probeClient, client);
|
||||
}],
|
||||
['missing-AbortController-still-cancels', async f => {
|
||||
globalThis.AbortController = undefined;
|
||||
const source = magnet(), live = f.feed([source]); live.show(); live.dispose();
|
||||
await f.drain(); assert.equal(f.calls[0].settled, true);
|
||||
assert.equal(f.calls[0].result, null); assert.equal(f.cache(source), null);
|
||||
assert.equal(f.clock.jobs.size, 0); assert.equal(f.torrents[0].destroyCount, 1);
|
||||
}],
|
||||
['error-and-throwing-teardown-release-queued-capacity-once', async f => {
|
||||
const active = f.feed(Array.from({ length: 4 }, magnet)); active.show();
|
||||
const source = magnet(), queued = f.feed([source]); queued.show();
|
||||
f.config.throwDestroy = true;
|
||||
f.torrents[0].error(); await turn();
|
||||
assert.equal(f.calls.length, 4, 'error result still holds slot until teardown');
|
||||
f.clock.tick(0); await turn();
|
||||
assert.equal(f.calls.length, 5); assert.equal(f.torrents[0].destroyCount, 1);
|
||||
assert.equal(f.torrents[0].listenerCount('error'), 0);
|
||||
f.config.throwDestroy = false;
|
||||
f.torrents[4].wire(); await f.drain();
|
||||
assert.equal(queued.cards[0].dataset.streamHealthState, 'healthy'); assert.equal(f.peak, 4);
|
||||
}]
|
||||
];
|
||||
|
||||
for (const [name, scenario] of cases) {
|
||||
test(name, async t => { await scenario(fixture(t)); });
|
||||
}
|
||||
@@ -369,13 +369,13 @@ test("active probe remains shared while disposed owners and late observer entrie
|
||||
const source = magnet();
|
||||
const first = f.feed([source]);
|
||||
first.observer.show();
|
||||
first.dispose();
|
||||
const liveUpdates = [];
|
||||
const live = f.feed([source], {
|
||||
onUpdate: (event) => liveUpdates.push(event)
|
||||
});
|
||||
live.observer.show(); // Retain a live owner BEFORE releasing the first.
|
||||
first.dispose();
|
||||
first.observer.show(); // An already-delivered entry may arrive after disconnect.
|
||||
live.observer.show();
|
||||
assert.equal(
|
||||
liveUpdates.some((event) => event.card === first.cards[0]),
|
||||
false,
|
||||
@@ -414,7 +414,7 @@ test("repeated old disposal cannot cancel a reattached live registration", async
|
||||
assert.equal(renewed.cards[0].dataset.streamHealthState, "healthy");
|
||||
});
|
||||
|
||||
test("disposed active completion remains cached for a later live owner", async (t) => {
|
||||
test("disposed active completion cannot cache and a later owner requests fresh work", async (t) => {
|
||||
const f = fixture(t);
|
||||
const source = magnet();
|
||||
const old = f.feed([source]);
|
||||
@@ -427,10 +427,15 @@ test("disposed active completion remains cached for a later live owner", async (
|
||||
live.observer.show();
|
||||
assert.equal(
|
||||
f.calls.length,
|
||||
1,
|
||||
"ownerless active completion must retain its healthy cache result"
|
||||
2,
|
||||
"ownerless active completion must not replace a fresh probe"
|
||||
);
|
||||
assert.equal(readTorrentProbeFromStorage(source.slice(-40)), null);
|
||||
assert.equal(live.cards[0].dataset.streamHealthState, "checking");
|
||||
f.calls[1].finish();
|
||||
await flush();
|
||||
assert.equal(live.cards[0].dataset.streamHealthState, "healthy");
|
||||
assert.equal(readTorrentProbeFromStorage(source.slice(-40)).value.healthy, true);
|
||||
});
|
||||
|
||||
test("completed healthy and negative probes retain existing cache TTL behavior", async (t) => {
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
- [x] Current rules/mailbox/HEAD applicability and existing item claim.
|
||||
- [x] Verify runtime equality, diagnostic syntax and four dist pins without executing browser.
|
||||
- [ ] Fresh dispatch scope/slot grant and cooling clearance.
|
||||
- [ ] Authorized bounded Firefox stage diagnostic; preserve exact failure/timeline/cleanup and release slot.
|
||||
- [ ] Classify prerequisite before any fixture or product changes; item9 stays open.
|
||||
@@ -0,0 +1,6 @@
|
||||
- [x] Read current rules/PROGRAM/brief/area TODO/task/mail and reclaim item9.
|
||||
- [x] Verify current source equals landed HLS boundary and prior raw prefix evidence hash.
|
||||
- [x] Reconcile original failures and separate controlled browser results without rerun.
|
||||
- [x] Recommend bounded active torrent owner lifecycle measurements; no defect asserted from source.
|
||||
- [ ] Dispatch route next offline investigation; product changes require demonstrated defect and explicit routing.
|
||||
- [ ] Remaining item9 torrent qualification; real playback/segments/RangeGET/live scope unverified.
|
||||
@@ -0,0 +1,8 @@
|
||||
- [x] Read mailbox/current rules and write source plan.
|
||||
- [x] Measure actual current-HEAD grid/probe lifecycle offline.
|
||||
- [x] Preserve first fixture result; classify JSDOM storage timer confound; corrected IO-isolated fixture12=7pass/5fail.
|
||||
- [x] Preserve old-contract suite11/11 and enumerate required spec correction.
|
||||
- [x] Save scripts/rawlogs/hashes and narrow fix proposal.
|
||||
- [ ] Independent dispatch reproduction/explicit implementation routing.
|
||||
- [ ] Product fix, strict regressions+mutants+lint, later resource-routed full/build/browser/normal landing.
|
||||
- [ ] Remaining live/network/wholeStage3 qualification.
|
||||
@@ -0,0 +1,7 @@
|
||||
- [x] Read exact dispatch routing and newest rules/current tree.
|
||||
- [x] Write explicit spec correction and integrity note before superseded expectations.
|
||||
- [ ] Implement narrow grid ownership and optional helper lifecycle.
|
||||
- [ ] Focused real-chain/helper tests, edge callbacks, shared/live/legacy/queue controls.
|
||||
- [ ] Every new scenario rejects a deliberate restored mutant.
|
||||
- [ ] Targeted lint and evidence/READY handoff.
|
||||
- [ ] Later full/build/browser/normal landing under separate resource grant.
|
||||
@@ -0,0 +1,6 @@
|
||||
- [x] Resume rules/mailbox/item9 claim and saved candidate review.
|
||||
- [x] Four candidate hashes exactly match saved focused/mutant evidence; no repeat baseline/tests.
|
||||
- [x] Record thermal hold and READY scope/validation limits.
|
||||
- [ ] Commit named files via fleet; merge unstable without rebase; inspect runtime equality.
|
||||
- [ ] Dispatch fresh resource grant for remaining lint/full/build/browser/submission.
|
||||
- [ ] Normal queue durable landing plus unstable ancestry before closing item9.
|
||||
@@ -0,0 +1,5 @@
|
||||
- [x] Candidate committed1b2362d2 and unstable04c8c7cf merged at1bf50c1f; source inputs unchanged.
|
||||
- [x] Saved60focused/19distinct killed mutants hashes verified; no baseline/test repeat.
|
||||
- [x] Thermal hold/progress and dispatch blocker delivered; backlog and area TODO current.
|
||||
- [ ] Explicit fresh resource grant for remaining full lint/full/build/browser/submission.
|
||||
- [ ] Durable normal-gate landing and target ancestry before closing item9.
|
||||
@@ -0,0 +1,8 @@
|
||||
- [x] Narrow implementation and explicit spec/integrity correction preserved.
|
||||
- [x] Final focused6files60/60/0cancelled.
|
||||
- [x] 19unique mutants killed; all27newcases covered, no surviving variants.
|
||||
- [x] Targetedlint4pass; sandboxhexEPERM plus unchangedhostpass retained.
|
||||
- [x] Durable candidate/log/mutant snapshots+manifest saved; SAFE TO RESTART.
|
||||
- [ ] Resume: review final delta/notes; finish READY and commit named paths.
|
||||
- [ ] Merge currentunstable (never rebase), verify runtime equality/checks.
|
||||
- [ ] Explicit grant for remaining lint/full/build/browser/submission; durable landing/ancestry before closure.
|
||||
@@ -0,0 +1,5 @@
|
||||
- [x] Source-reviewed candidate and full lint11 pass.
|
||||
- [x] ONE normal queue attempt; preserve interrupted275/438 log and rejected result, no retry.
|
||||
- [x] RELEASE exclusive slot and record dispatch thermal hold.
|
||||
- [ ] Fresh thermal/resource clearance plus new dispatch gate grant.
|
||||
- [ ] Unchanged full gate durable landing/ancestry; then separately granted build/browser follow-up.
|
||||
Reference in new issue
Block a user