diff --git a/context/CONTEXT_2026-10-24_19-46-17_client-reliability-9-resume.md b/context/CONTEXT_2026-10-24_19-46-17_client-reliability-9-resume.md new file mode 100644 index 00000000..3c498ed7 --- /dev/null +++ b/context/CONTEXT_2026-10-24_19-46-17_client-reliability-9-resume.md @@ -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. diff --git a/context/CONTEXT_2026-10-26_13-45-00_client-reliability-9-source-only.md b/context/CONTEXT_2026-10-26_13-45-00_client-reliability-9-source-only.md new file mode 100644 index 00000000..5b2f702d --- /dev/null +++ b/context/CONTEXT_2026-10-26_13-45-00_client-reliability-9-source-only.md @@ -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. diff --git a/context/CONTEXT_2026-10-26_15-25-00_client-reliability-9-torrent.md b/context/CONTEXT_2026-10-26_15-25-00_client-reliability-9-torrent.md new file mode 100644 index 00000000..247ad3bd --- /dev/null +++ b/context/CONTEXT_2026-10-26_15-25-00_client-reliability-9-torrent.md @@ -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. diff --git a/context/CONTEXT_2026-10-26_18-55-00_client-reliability-9-torrent-implementation.md b/context/CONTEXT_2026-10-26_18-55-00_client-reliability-9-torrent-implementation.md new file mode 100644 index 00000000..d32ab81d --- /dev/null +++ b/context/CONTEXT_2026-10-26_18-55-00_client-reliability-9-torrent-implementation.md @@ -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. diff --git a/decisions/DECISIONS_2026-10-26_13-45-00_client-reliability-9-source-only.md b/decisions/DECISIONS_2026-10-26_13-45-00_client-reliability-9-source-only.md new file mode 100644 index 00000000..5fc1d4e0 --- /dev/null +++ b/decisions/DECISIONS_2026-10-26_13-45-00_client-reliability-9-source-only.md @@ -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. diff --git a/decisions/DECISIONS_2026-10-26_15-25-00_client-reliability-9-torrent.md b/decisions/DECISIONS_2026-10-26_15-25-00_client-reliability-9-torrent.md new file mode 100644 index 00000000..4047f7cd --- /dev/null +++ b/decisions/DECISIONS_2026-10-26_15-25-00_client-reliability-9-torrent.md @@ -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. diff --git a/decisions/DECISIONS_2026-10-26_19-15-00_client-reliability-9-torrent.md b/decisions/DECISIONS_2026-10-26_19-15-00_client-reliability-9-torrent.md new file mode 100644 index 00000000..6f2974eb --- /dev/null +++ b/decisions/DECISIONS_2026-10-26_19-15-00_client-reliability-9-torrent.md @@ -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. diff --git a/docs/TODO.md b/docs/TODO.md index 9083df4d..3063057f 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -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*. diff --git a/docs/agents/backlog/client-reliability-9.json b/docs/agents/backlog/client-reliability-9.json index 5b8f7972..e8a8d2da 100644 --- a/docs/agents/backlog/client-reliability-9.json +++ b/docs/agents/backlog/client-reliability-9.json @@ -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" } diff --git a/docs/spec_changes/2026-10-26-client-reliability-9-active-torrent.md b/docs/spec_changes/2026-10-26-client-reliability-9-active-torrent.md new file mode 100644 index 00000000..21f59cfa --- /dev/null +++ b/docs/spec_changes/2026-10-26-client-reliability-9-active-torrent.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. diff --git a/js/gridHealth.js b/js/gridHealth.js index 8d59c06c..e4b4e707 100644 --- a/js/gridHealth.js +++ b/js/gridHealth.js @@ -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) => { diff --git a/js/webtorrent.js b/js/webtorrent.js index 6edb580d..3eb1be7a 100644 --- a/js/webtorrent.js +++ b/js/webtorrent.js @@ -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(() => { diff --git a/test_logs/TEST_INTEGRITY_2026-10-26_client-reliability-9-active-torrent.md b/test_logs/TEST_INTEGRITY_2026-10-26_client-reliability-9-active-torrent.md new file mode 100644 index 00000000..188fad5d --- /dev/null +++ b/test_logs/TEST_INTEGRITY_2026-10-26_client-reliability-9-active-torrent.md @@ -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. diff --git a/test_logs/TEST_LOG_2026-10-24_02-51-00_client-reliability-9-resume.md b/test_logs/TEST_LOG_2026-10-24_02-51-00_client-reliability-9-resume.md new file mode 100644 index 00000000..344b450c --- /dev/null +++ b/test_logs/TEST_LOG_2026-10-24_02-51-00_client-reliability-9-resume.md @@ -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. diff --git a/test_logs/TEST_LOG_2026-10-24_02-53-00_client-reliability-9-chromium-blocked.md b/test_logs/TEST_LOG_2026-10-24_02-53-00_client-reliability-9-chromium-blocked.md new file mode 100644 index 00000000..5d5d62aa --- /dev/null +++ b/test_logs/TEST_LOG_2026-10-24_02-53-00_client-reliability-9-chromium-blocked.md @@ -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. diff --git a/test_logs/TEST_LOG_2026-10-24_02-54-00_client-reliability-9-chromium.md b/test_logs/TEST_LOG_2026-10-24_02-54-00_client-reliability-9-chromium.md new file mode 100644 index 00000000..aa6915f7 --- /dev/null +++ b/test_logs/TEST_LOG_2026-10-24_02-54-00_client-reliability-9-chromium.md @@ -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. diff --git a/test_logs/TEST_LOG_2026-10-24_02-55-00_client-reliability-9-browser-checkpoint.md b/test_logs/TEST_LOG_2026-10-24_02-55-00_client-reliability-9-browser-checkpoint.md new file mode 100644 index 00000000..e6713ebf --- /dev/null +++ b/test_logs/TEST_LOG_2026-10-24_02-55-00_client-reliability-9-browser-checkpoint.md @@ -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. diff --git a/test_logs/TEST_LOG_2026-10-24_19-46-17_client-reliability-9-resume.md b/test_logs/TEST_LOG_2026-10-24_19-46-17_client-reliability-9-resume.md new file mode 100644 index 00000000..47c4d614 --- /dev/null +++ b/test_logs/TEST_LOG_2026-10-24_19-46-17_client-reliability-9-resume.md @@ -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. diff --git a/test_logs/TEST_LOG_2026-10-24_19-55-20_client-reliability-9-diagnostic-hold.md b/test_logs/TEST_LOG_2026-10-24_19-55-20_client-reliability-9-diagnostic-hold.md new file mode 100644 index 00000000..4f5319d8 --- /dev/null +++ b/test_logs/TEST_LOG_2026-10-24_19-55-20_client-reliability-9-diagnostic-hold.md @@ -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. diff --git a/test_logs/TEST_LOG_2026-10-24_22-05-00_client-reliability-9-firefox-diagnostic.md b/test_logs/TEST_LOG_2026-10-24_22-05-00_client-reliability-9-firefox-diagnostic.md new file mode 100644 index 00000000..66cd2eba --- /dev/null +++ b/test_logs/TEST_LOG_2026-10-24_22-05-00_client-reliability-9-firefox-diagnostic.md @@ -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. diff --git a/test_logs/TEST_LOG_2026-10-24_22-07-00_client-reliability-9-prefix-proposal.md b/test_logs/TEST_LOG_2026-10-24_22-07-00_client-reliability-9-prefix-proposal.md new file mode 100644 index 00000000..452836fc --- /dev/null +++ b/test_logs/TEST_LOG_2026-10-24_22-07-00_client-reliability-9-prefix-proposal.md @@ -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. diff --git a/test_logs/TEST_LOG_2026-10-25_03-27-00_client-reliability-9-prefix-result.md b/test_logs/TEST_LOG_2026-10-25_03-27-00_client-reliability-9-prefix-result.md new file mode 100644 index 00000000..492b65fa --- /dev/null +++ b/test_logs/TEST_LOG_2026-10-25_03-27-00_client-reliability-9-prefix-result.md @@ -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. diff --git a/test_logs/TEST_LOG_2026-10-26_13-45-00_client-reliability-9-reconciled.md b/test_logs/TEST_LOG_2026-10-26_13-45-00_client-reliability-9-reconciled.md new file mode 100644 index 00000000..a79803d7 --- /dev/null +++ b/test_logs/TEST_LOG_2026-10-26_13-45-00_client-reliability-9-reconciled.md @@ -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. diff --git a/test_logs/TEST_LOG_2026-10-26_15-25-00_client-reliability-9-torrent-investigation.md b/test_logs/TEST_LOG_2026-10-26_15-25-00_client-reliability-9-torrent-investigation.md new file mode 100644 index 00000000..5dab06a9 --- /dev/null +++ b/test_logs/TEST_LOG_2026-10-26_15-25-00_client-reliability-9-torrent-investigation.md @@ -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. diff --git a/test_logs/TEST_LOG_2026-10-26_19-05-00_client-reliability-9-torrent-focused.md b/test_logs/TEST_LOG_2026-10-26_19-05-00_client-reliability-9-torrent-focused.md new file mode 100644 index 00000000..208d65bc --- /dev/null +++ b/test_logs/TEST_LOG_2026-10-26_19-05-00_client-reliability-9-torrent-focused.md @@ -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. diff --git a/test_logs/TEST_LOG_2026-10-26_19-07-50_client-reliability-9-resumed-ready.md b/test_logs/TEST_LOG_2026-10-26_19-07-50_client-reliability-9-resumed-ready.md new file mode 100644 index 00000000..9c62fbe4 --- /dev/null +++ b/test_logs/TEST_LOG_2026-10-26_19-07-50_client-reliability-9-resumed-ready.md @@ -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. diff --git a/test_logs/TEST_LOG_2026-10-26_19-08-50_client-reliability-9-merged-ready.md b/test_logs/TEST_LOG_2026-10-26_19-08-50_client-reliability-9-merged-ready.md new file mode 100644 index 00000000..6cdbe88b --- /dev/null +++ b/test_logs/TEST_LOG_2026-10-26_19-08-50_client-reliability-9-merged-ready.md @@ -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. diff --git a/test_logs/TEST_LOG_2026-10-26_19-12-00_client-reliability-9-lint.md b/test_logs/TEST_LOG_2026-10-26_19-12-00_client-reliability-9-lint.md new file mode 100644 index 00000000..3ff9a7ef --- /dev/null +++ b/test_logs/TEST_LOG_2026-10-26_19-12-00_client-reliability-9-lint.md @@ -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. diff --git a/test_logs/TEST_LOG_2026-10-26_19-16-00_client-reliability-9-thermal-interruption.md b/test_logs/TEST_LOG_2026-10-26_19-16-00_client-reliability-9-thermal-interruption.md new file mode 100644 index 00000000..4608d3d1 --- /dev/null +++ b/test_logs/TEST_LOG_2026-10-26_19-16-00_client-reliability-9-thermal-interruption.md @@ -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. diff --git a/tests/grid-health-active-torrent.test.mjs b/tests/grid-health-active-torrent.test.mjs new file mode 100644 index 00000000..9d5ef6a8 --- /dev/null +++ b/tests/grid-health-active-torrent.test.mjs @@ -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('', { 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)); }); +} diff --git a/tests/grid-health-lifecycle.test.mjs b/tests/grid-health-lifecycle.test.mjs index f6bd78bb..f757f17a 100644 --- a/tests/grid-health-lifecycle.test.mjs +++ b/tests/grid-health-lifecycle.test.mjs @@ -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) => { diff --git a/todo/TODO_2026-10-24_19-46-17_client-reliability-9-resume.md b/todo/TODO_2026-10-24_19-46-17_client-reliability-9-resume.md new file mode 100644 index 00000000..ec1bb173 --- /dev/null +++ b/todo/TODO_2026-10-24_19-46-17_client-reliability-9-resume.md @@ -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. diff --git a/todo/TODO_2026-10-26_13-45-00_client-reliability-9-source-only.md b/todo/TODO_2026-10-26_13-45-00_client-reliability-9-source-only.md new file mode 100644 index 00000000..83482ac2 --- /dev/null +++ b/todo/TODO_2026-10-26_13-45-00_client-reliability-9-source-only.md @@ -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. diff --git a/todo/TODO_2026-10-26_15-25-00_client-reliability-9-torrent.md b/todo/TODO_2026-10-26_15-25-00_client-reliability-9-torrent.md new file mode 100644 index 00000000..146f0d6e --- /dev/null +++ b/todo/TODO_2026-10-26_15-25-00_client-reliability-9-torrent.md @@ -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. diff --git a/todo/TODO_2026-10-26_18-55-00_client-reliability-9-torrent-implementation.md b/todo/TODO_2026-10-26_18-55-00_client-reliability-9-torrent-implementation.md new file mode 100644 index 00000000..917a5ac9 --- /dev/null +++ b/todo/TODO_2026-10-26_18-55-00_client-reliability-9-torrent-implementation.md @@ -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. diff --git a/todo/TODO_2026-10-26_19-07-50_client-reliability-9-ready.md b/todo/TODO_2026-10-26_19-07-50_client-reliability-9-ready.md new file mode 100644 index 00000000..0a3f9100 --- /dev/null +++ b/todo/TODO_2026-10-26_19-07-50_client-reliability-9-ready.md @@ -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. diff --git a/todo/TODO_2026-10-26_19-08-50_client-reliability-9-merged-ready.md b/todo/TODO_2026-10-26_19-08-50_client-reliability-9-merged-ready.md new file mode 100644 index 00000000..5fcbb992 --- /dev/null +++ b/todo/TODO_2026-10-26_19-08-50_client-reliability-9-merged-ready.md @@ -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. diff --git a/todo/TODO_2026-10-26_19-15-00_client-reliability-9-restart.md b/todo/TODO_2026-10-26_19-15-00_client-reliability-9-restart.md new file mode 100644 index 00000000..fd142e62 --- /dev/null +++ b/todo/TODO_2026-10-26_19-15-00_client-reliability-9-restart.md @@ -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. diff --git a/todo/TODO_2026-10-26_19-16-00_client-reliability-9-thermal-hold.md b/todo/TODO_2026-10-26_19-16-00_client-reliability-9-thermal-hold.md new file mode 100644 index 00000000..95573339 --- /dev/null +++ b/todo/TODO_2026-10-26_19-16-00_client-reliability-9-thermal-hold.md @@ -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.