diff --git a/context/CONTEXT_2026-10-20_15-01-40_storage-cors-2.md b/context/CONTEXT_2026-10-20_15-01-40_storage-cors-2.md new file mode 100644 index 00000000..49f52ea5 --- /dev/null +++ b/context/CONTEXT_2026-10-20_15-01-40_storage-cors-2.md @@ -0,0 +1,6 @@ +# storage-cors-2 +- Current route: read-only remaining storage/delete-safety audit per dispatch14:58:59; former feed scope deferredstorage3. +- Actual real-service malformed cached bucket/base reproduction2rejects vs0required;6controls pass,existing focused11scenarios/3files pass. docs/testing/storage-cors-delete-audit.md has fidelity/callers/limits. +- Product/test read-only until routing. Proposed defensive primitive-string handling/structured skip in r2Service.deleteVideoStorage + focused new regression. s3UploadService contains no duplicate delete method; no upload/key changes proposed. +- Pending: dispatch fix routing;claim item2 after queued metadata visibility. QA owns full/normal/browser;no full/build/browser/cloud. +- Scratch /tmp/storage-cors-delete-audit.mjs;deferred feed invalid-seed scratch /tmp/storage-cors-feed-audit.mjs preserved. No secret/private-data use. diff --git a/decisions/DECISIONS_2026-10-20_15-09-00_storage-cors-2.md b/decisions/DECISIONS_2026-10-20_15-09-00_storage-cors-2.md new file mode 100644 index 00000000..8e51ee3c --- /dev/null +++ b/decisions/DECISIONS_2026-10-20_15-09-00_storage-cors-2.md @@ -0,0 +1,6 @@ +# storage-cors-2 +- Reject malformed connection fields as invalid-connection; no String(object) or arbitrary trim invocation for cleanup authorization/base/key material. +- Reject nonstring owner identifiers as invalid-owner before resolution. Null/non-object parameter options safely use existing no-videos result. +- Preserve existing owned-base/key collector,credential lock reasons,per-object failure summaries,provider-aware client branches and content-addressed upload semantics. +- s3UploadService contains no affected delete counterpart; no upload-copy edits per explicit dispatch ruling. Tests prove both provider branches instead. +- No broad catch; existing client-construction and each-delete failures retain structured results. Arbitrary throwing getters/proxies beyond serializable cache metadata are outside this proof. diff --git a/docs/TODO.md b/docs/TODO.md index ffd33e74..d466c81d 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -2,6 +2,9 @@ ## dispatch +- 2026-10-20 trial outputs complete:QA regression1bb3a4fc/closurebbf57388 verified,dispatch independent4/4/runtime unchanged so no extra build. Same3 workers continue:client6 real history test-only retention after independent current pass/historical-noop failure;storage2 method-only malformed metadata no-throw guard after independent2rejects/6controls;QA2 actual-app local core/mobile3viewports browser-only grant. Full slots on validated-ready after QA browser release;no feature/readiness/live-proof claim. + + - 2026-10-20 owner continuous-quality direction recorded;fleet/QUALITY.md tracks local vs real release evidence. QA full/normal slot active;client item6 profile-history wiring audit and storage item2 feed/view tombstone audit assigned now,read-only before fix routing. Remaining:actual coverage/reproduction decisions,current QA landing,local mobile/core-flow baselines and unqualified live gates. @@ -80,4 +83,7 @@ ## playback-qa +- 2026-10-20 item1 done:retained regression1bb3a4fc/closurebbf57388,full429/429/4focused/6mutants/lint11,dispatch independent4/4. Item2 nowactual-app local mock-relay/private-port core/mobile baseline,onebounded Chromium pass/noUIedits/browser-only allocation. Real peers/fullapp qualification stays open until actual evidence. + + - Parallel trial item1: local fallback qualification/regression; browser initially reserved to QA; product read-only. diff --git a/docs/agents/PROGRAM.md b/docs/agents/PROGRAM.md index 76be8d64..35d718ec 100644 --- a/docs/agents/PROGRAM.md +++ b/docs/agents/PROGRAM.md @@ -4,7 +4,7 @@ Last updated: 2026-10-19 ## Current milestone -Work through the existing TODO backlog and fix reproducible bugs that make the client less robust or usable. Validate old reports at current HEAD before assigning them; deliver small, checked changes through local unstable. +Prepare the integrated client for owner local review/testing:finish current storage2/history6 scope,faithful actual-app/core/mobile baseline and current preview with exact evidence. Retain broader robustness/usability backlog and live gates;do not start optional work beyond this milestone. Validate old reports at HEAD and land checked changes through local unstable. ## Priorities @@ -13,6 +13,13 @@ Work through the existing TODO backlog and fix reproducible bugs that make the c ## Rulings log (newest first) +### 2026-10-20 — Explicit local-review stopping milestone,relayed by interpreter + +> "Ready for me to review and test locally" — owner, relayed by interpreter;not represented as typed in dispatch. + +Finish current storage2/history6 gates/landings,faithful local actual-app/core/mobile baseline and preview refresh,then provide local-review handoff before declaring this milestone met. Do not clear optional roadmap or claim production/live Amber/cloud/peer proof. No new restricted-action authority. + + ### 2026-10-20 — Continuing quality objective, relayed by interpreter > "ok try to keep them working until the project is in a good place." — owner, relayed by interpreter; not represented as typed in dispatch. diff --git a/docs/agents/backlog/playback-qa-2.json b/docs/agents/backlog/playback-qa-2.json index 30af8d01..6ea2d9f0 100644 --- a/docs/agents/backlog/playback-qa-2.json +++ b/docs/agents/backlog/playback-qa-2.json @@ -1,9 +1,10 @@ { - "id": "playback-qa-2", - "area": "playback-qa", - "status": "open", - "title": "Capture actual-app local core-flow/mobile baseline before UI changes", - "blockedOn": null, - "addedOn": "2026-10-20", - "filedAt": "1bb3a4fc452969f060c09a286b72b8cd7ac4123e" + "id": "playback-qa-2", + "area": "playback-qa", + "status": "blocked", + "title": "Capture actual-app local core-flow/mobile baseline before UI changes", + "blockedOn": "Await explicit second browser-only grant after storage release; approved genuine mirror/frozen actual built app ready.", + "addedOn": "2026-10-20", + "filedAt": "1bb3a4fc452969f060c09a286b72b8cd7ac4123e", + "notes": "First actual source-app pass blocked360x800(15s harness timeout,CDNBuffer denied+CSS404); no UI/viewports success claim. Dispatch approved preparation/doc-only normal submission15:14:36. Copied real prepared Buffer6.0.3 mirror SHA256507a593508c51d1397a9a5818ad0b8dbf7e3ad810feaa87cabff87ff56cf3984 plus verified genuine tarballs/provenance into own read-only /tmp/bitvid-playback-qa-2-snapshot; own Buffer APIprobes4/4 exit0. Frozen dispatch actual verifieddist811files/16145338B,manifestSHA2569997b48049fd01d7c7627c009373296c9a4203683522d31bc5ee82ff98e8599b;CSS283d0464…;465runtime/buildinputsidentical. No QA build/browser/install/fetch/productchanges; not byte-identicalCDN or independentlyrebundled/metafileverified. Only exactBufferURL localfulfillment proposed next; allothernonlocalblocked;actualapp remainsBLOCKEDpending2ndgrant." } diff --git a/docs/agents/backlog/storage-cors-2.json b/docs/agents/backlog/storage-cors-2.json index 430c07cd..eecd307d 100644 --- a/docs/agents/backlog/storage-cors-2.json +++ b/docs/agents/backlog/storage-cors-2.json @@ -1,9 +1,10 @@ { - "id": "storage-cors-2", - "area": "storage-cors", - "status": "open", - "title": "Audit best-effort storage/delete safety and remaining R2 followups offline", - "blockedOn": null, - "addedOn": "2026-10-20", - "filedAt": "7f99c0456931c917082d6a6680c79655221dcc82" + "id": "storage-cors-2", + "area": "storage-cors", + "status": "done", + "title": "Audit best-effort storage/delete safety and remaining R2 followups offline", + "blockedOn": null, + "addedOn": "2026-10-20", + "filedAt": "7f99c0456931c917082d6a6680c79655221dcc82", + "closedOn": "2026-10-20" } diff --git a/docs/testing/playback-qa-2-assets-plan.md b/docs/testing/playback-qa-2-assets-plan.md new file mode 100644 index 00000000..e1b03a00 --- /dev/null +++ b/docs/testing/playback-qa-2-assets-plan.md @@ -0,0 +1,216 @@ +# playback-qa-2: genuine cached asset plan, not a browser pass + +Read-only preparation authorized by dispatch mailbox20261020T151044; no browser/build/download performed. +Inspection HEAD: `8d7e60ebf1761cda479d18a011dc382af5704f7b` (QA2 blocker evidence commit). +Actual app/mobile outcome remains blocked; no further viewport or core-flow validation claimed. + +## Found exact source packages, no install needed + +`npm cache ls buffer`, `npm cache ls base64-js`, `npm cache ls ieee754` found exact tarballs in +`/home/user/.npm/_cacache`. Offline `cacache.get` using existing `/usr/share/nodejs/cacache` +read/verified cached content. Recomputed SHA512 matched each cache integrity string AND the corresponding +version's `dist.integrity` in the cached registry packument. No registry/CDN request was made. +This establishes consistency with local cached registry metadata, not new online verification. +Tar package.json confirms names/versions/main/dependencies; genuine source index.js hashes are below. + +| Package | Tarball bytes | Tarball SHA256 | index.js SHA256 | +| --- | ---: | --- | --- | +|buffer@6.0.3 |23108 |`555b6f49224afcf6b2c5a9aea7cd34a7f443f395b10532bc5580022d57aa51f5` |`c25853fd31addfce188b01061fe85bfe667d5fb6c7a7bbb1c83d0ddfd8627acc` | +|base64-js@1.5.1 |4109 |`b1b7a945b52685269083425216d6597e33d97bf21699d656e92fdb3eb5210a85` |`829eadd8a1a441d25be0cb93b00e16a0d0c20fd294db95d8f2ed87e6954b7182` | +|ieee754@1.2.1 |2886 |`8ef14b9b397e339db89db97881fb714f49319d8f0eb1275901f45567b28f9dac` |`fe3e2b20c1ebb57c3b390c8f685ad1e04e6f493517fa7e432f4351bb77a1a4d0` | + +Buffer6.0.3 declares base64-js^1.3.1 and ieee754^1.2.1; cached1.5.1/1.2.1 satisfy these ranges. +Neither dependency declares further runtime dependencies. Package license files must accompany scratch extraction. +No installed top-level Buffer package is required; tarballs provide genuine source without `npm install`. +No exact cached jsDelivr `+esm` response was found in the npm-cache inventory; npm source is CommonJS. + +## Proposed zero-network scratch adapter, approval still required + +1. Re-read exact cached tarballs via the cache inspection script below; verify all hashes/integrities before extraction. +2. Extract only safe regular files under their package prefixes into a private scratch node_modules tree + (`buffer`, `base64-js`, `ieee754`), rejecting absolute/traversal/symlink/hardlink members. +3. Create scratch entry `export { Buffer } from './node_modules/buffer/index.js';`. +4. Use existing esbuild0.27.3 only to bundle that real entry/source/dependency graph as browser ESM. + Example command, **not run during preparation**: + +```sh +/home/user/Documents/GitHub/bitvid/node_modules/.bin/esbuild /tmp/bitvid-playback-qa-2-assets/entry.mjs --bundle --platform=browser --format=esm --outfile=/tmp/bitvid-playback-qa-2-assets/buffer-6.0.3.mjs +``` + +5. Record output SHA256 and esbuild metafile; require exactly the pinned three genuine package inputs. + Validate Buffer.from/toString/base64/float/read-write round trips against real Node Buffer before browser use. + These are adapter sanity checks, not replacements for actual app behavior. +6. With a new explicit browser grant, map **only** original import URL + `https://cdn.jsdelivr.net/npm/buffer@6.0.3/+esm` to those local reconstructed ESM bytes using request fulfillment. + Every other non-local HTTP/WebSocket request stays blocked/logged; never call route.continue for the mapped CDN. + Product files/import graph/global setup remain unchanged. No fake Buffer/toolkit or no-op module. +7. If another hard offline dependency appears, stop/report/release again. No speculative substitution or public fetching. + +Fidelity: exact npm Buffer6.0.3 implementation and pinned genuine dependencies, repackaged by local esbuild; +**not byte-identical to jsDelivr's generated ESM, nor CDN availability/loader parity qualification**. +Actual app/bootstrap/UI can then be exercised with a documented cached-library transport adapter only if approved. +No claim yet that this plan makes the full app boot; the first pass stopped before subsequent dependencies ran. + +## CSS: byte-verified existing output candidate + +Existing dispatch build asset (read-only): +`/home/user/Documents/GitHub/bitvid-dispatch/dist/css/tailwind.generated.css`,398371 bytes, +SHA256 `283d04642d201edba1e9bd5cbc3ede8d74fbd11b50ff0cd2732d463847d2adf3`. +Dispatch HEAD read as `8abbaa77b499cbf054ed55147815fb9403f702c3`. +Compared465 input files byte-for-byte against this QA checkout: **0 different inputs**. +Compared tailwind config content globs(index/embed/components/views/js/torrent), all source CSS except generated +output,postcss config,tailwind config,package.json and package-lock.json. The sorted[path,ownSHA256,dispatchSHA256] +JSON manifest SHA256 is `58743f25fc1ff7333c2bbc5b2aa9a60b3b833428b81e772bbbf3fe3e7b1b4177`. +`git diff 7f99c045..HEAD -- css tailwind.config.cjs postcss.config.cjs index.html embed.html components views js torrent package.json package-lock.json` +was empty; dispatch's prior verified preview build at7f99c045 has identical current relevant inputs. +No inference from commit names alone: this check compared actual files on disk. + +Dispatch installed versions read: postcss8.5.6,postcss-cli11.0.1,postcss-import15.1.0,tailwindcss3.4.19, +autoprefixer10.4.24; esbuild0.27.3 also available in existing main dependencies. +No build was run. After approval, copy only this genuine CSS output into scratch after re-verifying output hash +and input equality, then serve the missing `/css/tailwind.generated.css` path from scratch. Do not modify +or regenerate another area's output. Alternative: later authorized own local CSS build; not needed if approved copy. +This is output reuse, not an independent fresh-build/release qualification. + +## Existing harness and remaining dependencies + +`tests/e2e/helpers/bitvidTestFixture.ts` supplies relay overrides,programmatic test-only login and synthetic event +seeding; no dedicated Buffer/CDN offline mapping was found by: +`rg -n 'route\(|routeWebSocket|buffer@6|cdn.jsdelivr' tests/e2e/helpers scripts/playwright` (no matches). +Do not rewrite helpers/config. `vendor/nostr-tools.bundle.min.js` is already local(110545 bytes), +SHA256 `2b063a02d0f449d715fc3bb892364c7a859f7c2e443af15321a2bcbd561c4e02`. +`js/nostrToolsBootstrap.js` attempts this local bundle and has public fallback imports; all unmatched fallbacks +must remain blocked. No toolkit mutation or claim that later dynamic dependency loading was verified. + +## Integrity records + +```json +[ + { + "name": "buffer", + "version": "6.0.3", + "key": "make-fetch-happen:request-cache:https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "bytes": 23108, + "cacheIntegrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "sha256": "555b6f49224afcf6b2c5a9aea7cd34a7f443f395b10532bc5580022d57aa51f5", + "registryIntegrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "registryMatch": true, + "file": "/tmp/bitvid-playback-qa-2-assets/buffer-6.0.3.tgz", + "package": { + "name": "buffer", + "version": "6.0.3", + "main": "index.js", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + }, + "license": "MIT" + }, + "indexSha256": "c25853fd31addfce188b01061fe85bfe667d5fb6c7a7bbb1c83d0ddfd8627acc" + }, + { + "name": "base64-js", + "version": "1.5.1", + "key": "make-fetch-happen:request-cache:https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "bytes": 4109, + "cacheIntegrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "sha256": "b1b7a945b52685269083425216d6597e33d97bf21699d656e92fdb3eb5210a85", + "registryIntegrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "registryMatch": true, + "file": "/tmp/bitvid-playback-qa-2-assets/base64-js-1.5.1.tgz", + "package": { + "name": "base64-js", + "version": "1.5.1", + "main": "index.js", + "dependencies": null, + "license": "MIT" + }, + "indexSha256": "829eadd8a1a441d25be0cb93b00e16a0d0c20fd294db95d8f2ed87e6954b7182" + }, + { + "name": "ieee754", + "version": "1.2.1", + "key": "make-fetch-happen:request-cache:https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "bytes": 2886, + "cacheIntegrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "sha256": "8ef14b9b397e339db89db97881fb714f49319d8f0eb1275901f45567b28f9dac", + "registryIntegrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "registryMatch": true, + "file": "/tmp/bitvid-playback-qa-2-assets/ieee754-1.2.1.tgz", + "package": { + "name": "ieee754", + "version": "1.2.1", + "main": "index.js", + "dependencies": null, + "license": "BSD-3-Clause" + }, + "indexSha256": "fe3e2b20c1ebb57c3b390c8f685ad1e04e6f493517fa7e432f4351bb77a1a4d0" + } +] +``` + +## Offline cache-inspection script + +Requires only existing system cacache; writes tarball copies/report to own scratch, no extraction/install/build. + +```js +const cache = require('/usr/share/nodejs/cacache'); +const fs=require('node:fs/promises'); +const crypto=require('node:crypto'); +(async()=>{ + const results=[]; + for(const [name,version] of [['buffer','6.0.3'],['base64-js','1.5.1'],['ieee754','1.2.1']]){ + const key=`make-fetch-happen:request-cache:https://registry.npmjs.org/${name}/-/${name}-${version}.tgz`; + const r=await cache.get('/home/user/.npm/_cacache',key); + const actual='sha512-'+crypto.createHash('sha512').update(r.data).digest('base64'); + if(r.integrity!==actual)throw Error('cache integrity mismatch '+name); + const file=`/tmp/bitvid-playback-qa-2-assets/${name}-${version}.tgz`;await fs.writeFile(file,r.data); + let registryIntegrity=null; + try{const p=await cache.get('/home/user/.npm/_cacache',`make-fetch-happen:request-cache:https://registry.npmjs.org/${name}`);registryIntegrity=JSON.parse(p.data).versions?.[version]?.dist?.integrity||null;}catch{} + results.push({name,version,key,bytes:r.data.length,cacheIntegrity:r.integrity,sha256:crypto.createHash('sha256').update(r.data).digest('hex'),registryIntegrity,registryMatch:registryIntegrity===actual,file}); + } + await fs.writeFile('/tmp/bitvid-playback-qa-2-assets/cache.json',JSON.stringify(results,null,2));console.log(JSON.stringify(results,null,2)); +})().catch(e=>{console.error(e);process.exitCode=1;}); + +``` + +Current action: retain evidence/plan, no browser/full/build while storage holds the slot. Request dispatch review +of exact local fulfillment+verified CSS reuse, then a new bounded browser grant before executing any adapter/build. + +## Approved prepared mirror and frozen snapshot (latest checkpoint) + +Dispatch15:14:36 mailbox approved preparation/copy of its genuine prepared mirror and verified dist, plus +docs-only normal submission. **No second browser grant yet.** This supersedes the earlier request to build +our own adapter: no esbuild/build/install was run by QA. Root's prepared bundle/provenance are copied as approved. + +Own snapshot: `/tmp/bitvid-playback-qa-2-snapshot`, files chmod0444 after copy. +Dispatch-owned assets were only read; no other worktree/process was changed. No symlink assets were followed. +The exact mirror and all3 tarball SHA256 values were independently verified against the earlier cache report. +Copied root provenance states esbuild0.27.3,pins buffer6.0.3/base64-js1.5.1/ieee7541.2.1,no network/hooks. +QA did not independently reproduce that bundler output or receive an esbuild input metafile; the earlier +metafile-based proposal was not executed. Actual copied bytes are bound by the approved SHA256 below. + +Independent local mirror API sanity checks: `node --input-type=module` importing copied real Buffer, exit0,4/4: +UTF8 bytes equal native Node Buffer; base64 round-trip; float64 read/write bytes equal native Buffer; subarray +shares backing memory. No app/toolkit behavior replacement was used, and these probes do not qualify app boot. + +```json +{ + "source": "/home/user/Documents/GitHub/bitvid-dispatch/dist", + "files": 811, + "totalBytes": 16145338, + "manifestSha256": "9997b48049fd01d7c7627c009373296c9a4203683522d31bc5ee82ff98e8599b", + "indexSha256": "13b12280ffea5529172d081ba30aab64bb6f7140145fa17af407a09fb60786b4", + "bufferPolyfillSha256": "9c6412a7c3a7bda9d19ba420a0fad122b4d0e60a3af68ceb43d8eb98b68e6bff", + "cssSha256": "283d04642d201edba1e9bd5cbc3ede8d74fbd11b50ff0cd2732d463847d2adf3", + "mirrorSha256": "507a593508c51d1397a9a5818ad0b8dbf7e3ad810feaa87cabff87ff56cf3984" +} +``` + +The full sorted path→SHA256 manifest remains in own scratch `dist-manifest.json`; hashed summary above is durable. +Next granted pass serves this frozen **actual built app** instead of incomplete source checkout; existing local +relay/harness is retained. Only exact public Buffer import URL is fulfilled from local approved bytes and the +mirror hit logged. Every other non-local HTTP/WebSocket stays aborted/logged. No CDN fetch/pass-through,crypto +stub,toolkit override,product import rewrite or preview3000 use. Stop/release at the next hard prerequisite. + +QA2 stays BLOCKED pending resource grant/actual app result; all unrun viewport/UI checks remain unverified. diff --git a/docs/testing/playback-qa-2-boot-blocker.png b/docs/testing/playback-qa-2-boot-blocker.png new file mode 100644 index 00000000..51830806 Binary files /dev/null and b/docs/testing/playback-qa-2-boot-blocker.png differ diff --git a/docs/testing/playback-qa-2-evidence.md b/docs/testing/playback-qa-2-evidence.md new file mode 100644 index 00000000..af0b8a84 --- /dev/null +++ b/docs/testing/playback-qa-2-evidence.md @@ -0,0 +1,138 @@ +# playback-qa-2: actual-app offline baseline blocked + +Baseline HEAD: `ac99ae9a6bdbeae555a30f659b0720ae67e777e8`. +Assignment `filedAt`: `1bb3a4fc452969f060c09a286b72b8cd7ac4123e`. +`git diff 1bb3a4fc..HEAD -- js index.html config scripts/agent/simple-relay.mjs tests/e2e/helpers/bitvidTestFixture.ts` +was empty. Relevant actual app/harness behavior is unchanged; no product/CSS/test/helper/config edits. + +## Plan and authority + +- Use actual repository index/app modules with existing simple-relay and documented test harness relay overrides. +- One Chromium pass at360×800,390×844,1280×720; stop at the first hard offline boot blocker. +- Block/log non-local HTTP and WebSocket attempts, use only synthetic local notes/test identities if needed. +- Inspect feed/nav/Profile/Upload/required fields/overflow/focus only after successful actual app boot. +- Preserve precise failure evidence; no module substitution, public downloads, build/full suite or product edits. + +Browser-only slot granted in dispatch mailbox20261020T150619, consumed before preparation. +START sent to dispatch15:08:13Z **before** browser/server launch; reservation was in `.task`. +Actual execution15:08:16.718Z→15:08:32.952Z (16.234s); no browser/gate contention observed during this pass, +no resource waiting between START and execution, no claim about unrelated host work. + +## Actual result + +`node /tmp/bitvid-playback-qa-2/baseline.mjs`: process exit0, **scenario blocked**, not a passing app scenario. +Chromium154.0.8037.97, fresh context, first viewport360×800. Existing app is served directly from this worktree +on127.0.0.1:31847; existing simple-relay runs on private31848/31849 using its unchanged API. +Temporary own node_modules symlink reused existing installed dependencies read-only and was removed afterward. +Service workers were disabled to keep interception deterministic; no application methods/toolkit were substituted. + +`page.waitForFunction(() => typeof window.__bitvidTest__ === 'object', null, {timeout:15000})` +timed out. This is the documented fixture harness-ready bound, not a retry or relaxed assertion. +Static import chain `js/index.js:4` → `js/bufferPolyfill.js:1` requires: + +```js +import { Buffer } from "https://cdn.jsdelivr.net/npm/buffer@6.0.3/+esm"; +``` + +The network guard blocked that request. Static dependency loading cannot complete, so the app's early +`installTestHarness()` statement is never reached. Exactly1 blocked non-local HTTP attempt was observed; +0 WebSocket attempts reached the non-local guard,0 pageerror events. This is an offline boot/fixture constraint, +not a demonstrated mobile-layout product defect. Missing assets are a separate environment limitation: +`http://127.0.0.1:31847/css/tailwind.generated.css` returned404; own worktree has no generated CSS/dist, +and building is excluded from this pass. Screenshot shows unstyled static shell/search/logo, not a hydrated app. + +[Captured360×800 screenshot](playback-qa-2-boot-blocker.png) + +| Viewport / flow | Outcome | +| --- | --- | +|360×800 actual app boot / harness |BLOCKED:15s harness timeout | +|390×844 /1280×720 |Not run after first hard blocker | +| Feed initial hydration/local seeded cards |Unverified; no synthetic events needed/seeded | +| Home/Explore/ForYou navigation |Unverified | +| Profile/Upload open-close / required fields |Unverified | +| Card controls/touch target sizes/overflow/focus |Unverified; unstyled shell cannot qualify layout | + +No real signer,peers,buckets,public media,production data or external publishing was used. +No successful non-local page requests were allowed. Actual local page bootstrap was attempted; this is not +module-harness success, full app/mobile qualification, or a Firefox/KNOWN_ISSUES hydration-flake diagnosis. + +## Cleanup and routing + +Own browser/context and app/relay servers closed in finally at15:08:32.952Z. Browser slot RELEASE explicitly +sent to dispatch15:08:53Z; .task updated. Preview3000 was untouched; no other processes stopped. +Blocker sent with exact trigger and evidence; waiting on dispatch to route an offline asset/harness strategy. +Do not permit public CDN traffic or substitute Buffer/app/modules and silently call this an actual-app pass. +No full/normal queue submission without dispatch's explicit slot for this new item. + +## Observed browser errors + +```text +Failed to load resource: the server responded with a status of 404 (Not Found) +Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector +``` + +## Reproduction script + +Create `/tmp/bitvid-playback-qa-2`; temporarily link existing installed node_modules into this worktree if absent, +then save/run this script using the granted browser-only slot. It closes its own resources on the first blocker. +Remove that own symlink after the process exits. Requires existing main checkout Playwright/dependencies; +no installing or downloading assets is part of this reproduction. + +```js +import { createServer } from 'node:http'; +import { readFile } from 'node:fs/promises'; +import { resolve, extname } from 'node:path'; +import { chromium } from '/home/user/Documents/GitHub/bitvid/node_modules/playwright/index.mjs'; +import { startRelay } from '/home/user/Documents/GitHub/bitvid-playback-qa/scripts/agent/simple-relay.mjs'; +const root='/home/user/Documents/GitHub/bitvid-playback-qa'; +const out='/tmp/bitvid-playback-qa-2'; +const {writeFile}=await import('node:fs/promises'); +const result={started:new Date().toISOString(),viewports:[],blockedHttp:[],blockedWs:[],failedLocal:[],pageErrors:[],consoleErrors:[]}; +const server=createServer(async(req,res)=>{ + const url=new URL(req.url,'http://127.0.0.1:31847'); + let path=decodeURIComponent(url.pathname);if(path==='/')path='/index.html'; + const file=resolve(root,'.'+path); + if(!file.startsWith(root+'/')||path.includes('/.')||!['.html','.js','.mjs','.json','.css','.svg','.png','.jpg','.jpeg','.webp','.ico','.woff','.woff2','.ttf','.wasm','.map','.webmanifest'].includes(extname(file))){res.writeHead(404);res.end();return;} + try{const data=await readFile(file);res.setHeader('Content-Type',({'.html':'text/html','.js':'text/javascript','.mjs':'text/javascript','.json':'application/json','.css':'text/css','.svg':'image/svg+xml','.wasm':'application/wasm','.png':'image/png','.webmanifest':'application/manifest+json'})[extname(file)]||'application/octet-stream');res.end(data);} + catch{res.writeHead(404);res.end();} +}); +let relay,browser; +try{ + await new Promise((ok,no)=>{server.once('error',no);server.listen(31847,'127.0.0.1',ok);}); + relay=startRelay(31848,{httpPort:31849}); + browser=await chromium.launch({headless:true,executablePath:'/usr/bin/google-chrome',args:['--no-sandbox']}); + result.browserVersion=browser.version(); + const context=await browser.newContext({viewport:{width:360,height:800},serviceWorkers:'block'}); + await context.route('**/*',route=>{const u=new URL(route.request().url());if(['127.0.0.1','localhost','[::1]'].includes(u.hostname))return route.continue();result.blockedHttp.push(route.request().url());return route.abort('blockedbyclient');}); + await context.routeWebSocket('**/*',ws=>{const u=new URL(ws.url());if(['127.0.0.1','localhost','[::1]'].includes(u.hostname))ws.connectToServer();else{result.blockedWs.push(ws.url());ws.close();}}); + await context.addInitScript(()=>{localStorage.setItem('hasSeenDisclaimer','true');localStorage.setItem('__bitvidTestMode__','1');localStorage.setItem('__bitvidTestRelays__',JSON.stringify(['ws://127.0.0.1:31848']));window.__bitvidTestRelays__=['ws://127.0.0.1:31848'];localStorage.setItem('bitvid_admin_whitelist_mode','false');}); + const page=await context.newPage(); + page.on('pageerror',e=>result.pageErrors.push(e.message)); + page.on('console',m=>{if(m.type()==='error')result.consoleErrors.push(m.text().slice(0,700));}); + page.on('response',r=>{if(r.status()>=400&&new URL(r.url()).hostname==='127.0.0.1')result.failedLocal.push({url:r.url(),status:r.status()});}); + for(const viewport of [{width:360,height:800},{width:390,height:844},{width:1280,height:720}]){ + await page.setViewportSize(viewport); + await page.goto('http://127.0.0.1:31847/?__test__=1&__testRelays__=ws%3A%2F%2F127.0.0.1%3A31848',{waitUntil:'domcontentloaded'}); + const v={viewport,flows:{}};result.viewports.push(v); + try{ + await page.waitForFunction(()=>typeof window.__bitvidTest__==='object',null,{timeout:15000}); + await page.waitForFunction(async()=>{const m=await import('/js/applicationContext.js');return !!m.getApplication();},null,{timeout:15000}); + await page.locator('[data-testid="video-list"]').waitFor({state:'visible',timeout:15000}); + v.flows.boot='passed'; + }catch(e){v.flows.boot='blocked';v.blocker=e.message.split('\n')[0];v.dom=await page.locator('body').innerText();await page.screenshot({path:root+'/docs/testing/playback-qa-2-boot-blocker.png',fullPage:true});result.firstHardBlocker=true;break;} + v.layout=await page.evaluate(()=>({clientWidth:document.documentElement.clientWidth,scrollWidth:document.documentElement.scrollWidth,focus:document.activeElement?.tagName})); + // Stop at a new blocker rather than modifying product/fixture behavior. + v.flows.remaining='actual app booted; further actions require live harness state inspection'; + await page.screenshot({path:out+`/${viewport.width}.png`,fullPage:true}); + } + await context.close(); +} catch(e){result.runError=e.stack;process.exitCode=1;} +finally{ + if(browser)await browser.close(); + if(relay)await relay.close(); + await new Promise(ok=>server.close(ok)); + result.ended=new Date().toISOString();result.resourcesClosed=true; + await writeFile(out+'/result.json',JSON.stringify(result,null,2)); + console.log(JSON.stringify({started:result.started,ended:result.ended,browserVersion:result.browserVersion,viewports:result.viewports.map(v=>({viewport:v.viewport,flows:v.flows,blocker:v.blocker})),blockedHttp:result.blockedHttp,blockedWs:result.blockedWs,failedLocal:result.failedLocal,pageErrors:result.pageErrors,runError:result.runError,resourcesClosed:result.resourcesClosed})); +} +``` diff --git a/docs/testing/storage-cors-delete-audit.md b/docs/testing/storage-cors-delete-audit.md new file mode 100644 index 00000000..8d81b954 --- /dev/null +++ b/docs/testing/storage-cors-delete-audit.md @@ -0,0 +1,21 @@ +# storage-cors-2 current delete-safety audit + +Started2026-10-20T14:59:46UTC after dispatch priority correction. HEADd88716e3c6351468a53bc7fdb84fe912c01697b5. Product/existing tests read-only. Assignment metadata queued behind QA normal gate; dispatch authorizes read-only audit before claim visibility. + +Plan: inspect actual service/caller/normalization and existing scenarios; drive real service with fake SDK/transport and synthetic persistence records; establish owned/external/locked/partial controls; report demonstrated loss/smallest fix scope before writes. + +`env -u NODE_TEST_CONTEXT NODE_ENV=test node /tmp/storage-cors-delete-audit.mjs`: exit1, six passing controls and two contract failures (expected0 rejected calls, actual2), networkAttempts0. Real R2Service/deleteVideoStorage/resolveConnection, actual StorageService.listConnections/isUnlocked, actual SDK wrapper/key collector/deleteObject; remote SDK classes replaced locally, S3Client.send fake, persistence `_getAccount` returns synthetic account record. No real account/credentials/cloud/delete operations. Fixture strings are invented, example.invalid URLs. + +Controls: owned media+(.torrent)+thumbnail3 requests/deleted3; external host+base-prefix sibling0 clients/deletes; locked-owned skip storage-locked; locked-external skip no-matching-objects; partial transport failure2success/1failed/3requests/no rejection; valid cached locked connection resolves storage-locked. + +Failure: synthetic cached R2 metadata retains numeric bucket17 or object publicBaseUrl through real resolveConnection mapping. deleteVideoStorage then calls `.trim()` outside catches and rejects TypeError in2/2 malformed cases,0clients/0deleteIO. SDK was successfully loaded in the same process; success control proves the transport is usable. This is a never-throw contract violation, not a demonstrated blocked note deletion: app.handleDeleteModalConfirm and editModalController already catch cleanup exceptions. The malformed locked connection can fail before returning a storage-locked summary/warning. + +Proposed route: defensively validate primitive string fields in R2Service.deleteVideoStorage and return structured skip without interpreting malformed object metadata as permission/bucket names; add focused real-wrapper transport regression + integrity/mutants. s3UploadService has no deleteVideoStorage implementation; mirrored upload/key code remains unchanged. Await dispatch scope/semantics before product or committed test edits. + +Existing evidence scope: tests/services/r2-delete-video-storage.test.mjs covers five service scenarios with overridden client/delete I/O; tests/delete-storage-locked.test.mjs covers three locked/generic controls. Neither covers malformed persistent metadata. Existing helpers implement owner-base prefix exclusion. Full delete caller passes only targetVideo, so historical all-version object enumeration remains followup. Current edit caller DOES compare changed nonempty thumbnails and queues old thumbnail cleanup; dated TODO “thumbnail cleanup skipped” is stale for this path. No new retention/deletion semantics proposed. + +Deferred feed3: /tmp/storage-cors-feed-audit.mjs preserved; attempted real client/buffer/service/engine/coordinator/view scratch failed its initial visible-seed control0/2; no deletion finding or full feed proof. No product/test edit made from that invalid fixture. + +No browser/build/full gate used. No tool/gate wait blocking read-only work; actual cloud/provider IAM/serialization and real malformed user records unverified. No production data or signer used. + +Routed15:06:18UTC after independent dispatch confirmation. Implemented bounded metadata guard and13 retained real-service scenarios; original method fails9/13;7 restored mutants rejected. Focused4files/24scenarios,lint11/build pass. See storage-cors-delete-integrity.md. Awaiting full/submit slot requested15:08:52UTC. Current HEAD/baseac99ae9a;service unchanged from filedAt before fix,claim2 now visible/in-progress. No upload-copy deletion counterpart exists; no unrelated copy edits. diff --git a/docs/testing/storage-cors-delete-integrity.md b/docs/testing/storage-cors-delete-integrity.md new file mode 100644 index 00000000..9cba3fa1 --- /dev/null +++ b/docs/testing/storage-cors-delete-integrity.md @@ -0,0 +1,49 @@ +# storage-cors-2 best-effort delete metadata guard + +Dispatch independently reproduced2 rejected calls vs0 required with6 controls, then routed15:06:18UTC. Only deleteVideoStorage in r2Service.js changes. Input shape/owner identifiers and string connection fields are validated before trimming or SDK client construction. Nonstring metadata returns invalid-connection; nonstring owners return invalid-owner; absent/null/non-object options return existing no-videos skip. Primitive string trimming/default behavior remains. No coercion of arbitrary objects. + +Actual affected counterpart check: `s3UploadService.js` has NO deleteVideoStorage counterpart. Shared provider-aware R2Service delete already chooses makeR2Client versus makeS3Client. Both real wrapper branches are exercised with transport fakes; forcePathStyle/endpoint/region preserved. No duplicated upload/key/magnet logic changes in either service. + +```yaml +test_integrity_note: + change_type: ["new_tests"] + scenarios: + - id: SCN-storage-delete-malformed-cache + given: "synthetic persisted locked R2 metadata with numeric bucket or object base URL" + when: "actual StorageService listing and R2Service connection resolution feed deleteVideoStorage" + then: "structured invalid-connection skip, no client construction or object requests" + - id: SCN-storage-delete-direct-types + given: "nonstring direct credentials fields/owners or null/non-object options" + when: "real cleanup method is invoked" + then: "safe structured skip with zero IO and no object trimming/coercion" + - id: SCN-storage-delete-provider-safety + given: "valid R2 or generic-S3 connection with owned/external/prefix-sibling URLs, locked storage or partial delete failure" + when: "actual cleanup/key collector/SDK wrapper/deleteObject run" + then: "only owned media/torrent/thumbnail delete; provider settings retained; lock reasons correct; partial errors recorded and later objects continue" + observable_outcomes: + - "exact summaries, zero SDK constructions/requests for unsafe input, exact deletion keys/bucket/config and per-object failure" + determinism_controls: + - "synthetic _getAccount persistence IO; real connection resolution/mapping" + - "local remote-SDK replacement/recording transport; actual key collector and deleteObject; no real credentials/network" + - "no retries/sleeps/timeouts in retained regression" + anti_cheat_rationale: + prevents: + - "constant skip/no-op" + - "over-mocked service logic" + - "unsafe object coercion and cross-base deletion" + - "hidden partial failures or lost provider settings" + relaxation: + did_relax_any_assertion: false + if_true_explain_spec_basis: "" +``` + +Atac99ae9a6bdbeae555a30f659b0720ae67e777e8,13 new scenarios pass. Original method fails9/13 (each malformed cached field, each of5direct fields, owner types, null options). Seven restored mutants rejected: remove settings guard(scenarios1–7); remove owner guard(8); null destructure(9); force wrong provider(10); bypass owner key collector(10–13); bypass lock guard(12); rethrow partial error(13). Every scenario is killed. No existing assertions changed. + +Focused existing+new runner4/4 files,24scenarios; lint11/11 and build/deploy-artifact exit0. Product scope29-line diff in one method. Pre-existing note-deletion callers catch cleanup exceptions,so this does not claim note deletion was blocked. Raw cloud/IAM/SDK serialization,actual malformed user records and arbitrary throwing JS proxies/getters are unverified. No real bucket/delete/signer/private-data/preview action. Feed3 remains deferred with invalid seed fixture preserved; no feed finding. Full/normal landing awaits dispatch slot. + +Timing: storage-audit acknowledgement14:59:46UTC; evidence15:01:20UTC; fix route15:06:18UTC (298s routing wait); ready15:08:52UTC. Metadata claim followed verified unstable merge; no metadata wait blocked read-only investigation. + +Merged validation at020f0577ee5fb212a689bb06260f10abd47e4868:full430/430 files exit0 (NODE_TEST_CONTEXT unset,UNIT_TEST_TIMEOUT_MS60000),focused4files/24scenarios,lint11/build-artifact pass. Product7b8100a6 committed before required merge. Slot request15:08:52→grant15:09:29UTC37s;full confirmed15:15:59UTC. Normal queue pending. No product edits after mutant proof. + +## Durable completion +Submission1650b3439fee7d419b68a0badcee67cee0cfa9aa durably landed2026-10-20T15:22:48.611Z ase7b9afb309d822fca9fdc1cc31e79ce7c9c33fbe. Unchanged normal install/npm-test gate exit0. Both submission and landedSHA ancestry on unstable exit0. Newer dispatch docs tip1b3ef49c was already on unstable by final log read;product remains ancestor,not necessarily latest tip. Backlog closed only after durable result and ancestry. Resource release notification actual15:23:08UTC;normal queue wait/gate15:16:23.181→15:22:48.611(~385s). Final notes only;then idle per owner local-review milestone. No extra optional feed item starts;deferred feed3 remains queued for future routing. diff --git a/fleet/GOAL.md b/fleet/GOAL.md index 4fa75044..693963c8 100644 --- a/fleet/GOAL.md +++ b/fleet/GOAL.md @@ -28,3 +28,9 @@ Work through the existing TODO backlog, find and fix bugs, and make the client m > "ok try to keep them working until the project is in a good place." — owner, relayed by interpreter; not represented as typed in dispatch. Continue bounded robustness/usability/backlog work after the three-worker trial. Use fleet/QUALITY.md to track evidence and remaining gates. Passing units alone does not establish project readiness. Keep workers useful where scopes split safely; reduce idle staffing when useful independent work runs out. Stage reproductions/audits before product fixes, preserve normal local unstable landing and restricted-action limits. Amber/cloud/live peers/manual release qualification stays explicitly open until authorized real evidence exists. + +## Explicit stopping milestone — 2026-10-20 + +> "Ready for me to review and test locally" — owner, relayed by interpreter. + +Finish current storage-cors-2 and client-reliability-6 through unchanged full gates/durable landings;complete a faithful local actual-app/core-flow/mobile baseline sufficient to expose hard local-review blockers;refresh preview3000 from integrated unstable. Prepare exact branch/SHA/URL/start-health evidence,reviewable changes,manual flows and honest limits. Then stop new optional work for owner local review. This is not production readiness,all optional backlog completion,or authorization for real accounts/cloud/peers/spending/push/deploy/schedulers. Newly reconciled public-promised playback backlog remains explicit,not silently completed or pulled into a large feature batch. diff --git a/fleet/LOG.md b/fleet/LOG.md index e6b58ea0..494a0e5d 100644 --- a/fleet/LOG.md +++ b/fleet/LOG.md @@ -107,3 +107,14 @@ - Trial roster submission13:26:20→final prepared output landing15:02:17=95m57s. Parallel implementation evidence exists,but no controlled serial baseline/no speedup claim. Slot waits client10m57/storage15m49/QA65m43 to grant are overlapping metrics,not additive wall time.39m57 dispatch wake gap and repeated serial local/normal/admin full gates materially limited throughput. No scheduler/kit change. - Client6 independent actual wiring local/synced exit0,local clear+recreation and0publish;historical no-op local exit1 after cleanup,worker existing27 stillpass. Route test-only new real wiring regression,not product fix. Storage2 independent real service/cached metadata2rejects expected0,6controls/0network;route bounded primitive-field guard in deleteVideoStorage only. Actual S3UploadService has no deletion method;duplicated upload logic untouched,require provider-aware R2/S3 controls and decision note. - QA2 next actual-app local core/mobile baseline assigned,existing mock relay/private ports,3viewports,one Chromium pass/browser-only slot,all UI/product read-only. No speculative redesign. All same3 workers;full slots after validated-ready and browser release. + +- QA old resource release15:02:30/final closure7030d732->bbf57388 at15:03:27 independently verified;all prepared trial outputs complete. Supervisor independently also reports current-main fallback4/4/natural17ms,no redundant rerun. NextQA2 core/mobile browser-only grant sent15:06:19;client6 test-only retention andstorage2 method-only guard routing sent15:06:18,explicit scope/current evidence in landedac99ae9a. Await next-action acknowledgments;no worker held for a new owner approval. +- Mirroring clarification from interpreter15:03:20 agrees only matching implementations need parity;no delete method inS3UploadService means no duplicate to invent. Contract malformedsyntheticrecords robustness only,not real-user incident or note-delete-block proof;preserve owner boundary and avoid coercion/permission expansion. + +- Next-scope pickups verified:storage2 method-only fix15:06:34,QA2 baseline preparation15:06:36,client6 test-only retention15:06:53. QA supervisor limited read-only harness coverage prep is compatible with this assignment;do not duplicate existing specs or treat module smoke as app proof. QA must send START before browser and RELEASE;other workersfocused only untilready/fullslotgrant. + +## 2026-10-20 — Explicit local-review milestone and upstream documentation + +- Owner exact stopping quote "Ready for me to review and test locally" recorded GOAL/PROGRAM/QUALITY. Finish current bounded fixes/tests/baseline,then handoff current preview/branch/SHA/flows/limits;no optional feature churn or production claim. +- Inspected upstream8d4622bb:only12lines in canonicalprelaunchTODO,local110/remote1 divergent count. New public-promised HIGH1RangeGET/2health-persistence-hostscore/4warmup andotherplayback followups absent from local source used earlier. Staticrevalidation sees HEAD remains,URL localStorage persistence alreadyexists,torrent mapcache,viewport healthmetadata/orderedmirrors/SWcaches partial;no whole-item completion or new demonstrated hardreviewblocker. Non-destructive docs merge/normalqueue next,no push. +- Storage2 full430/430exit0 andnormal1650b343 gating;QA2 genuineasset planready anddocs-only25942a15 queuedbehindit. Client6ready9afe48a5 waitsfullafterstorage+shortQA2browser. No expensive successor started. diff --git a/fleet/QUALITY.md b/fleet/QUALITY.md index f1b98a2c..7a885069 100644 --- a/fleet/QUALITY.md +++ b/fleet/QUALITY.md @@ -1,6 +1,6 @@ # Quality evidence and next milestones -Updated2026-10-20. Goal: improve robustness/usability against pre-launch TODO and KNOWN_ISSUES. This is a local qualification matrix, not a project-ready/release declaration. +Updated2026-10-20. Owner stopping milestone:"Ready for me to review and test locally"(relayed). Finish storage2/history6,faithful actual-app baseline and integrated preview/handoff;stop new optional work for review. Broader robustness backlog remains. This is a local qualification matrix,not a production-ready declaration. | Area | Current local evidence | Next bounded work | Still unverified / release gate | |---|---|---|---| @@ -8,7 +8,7 @@ Updated2026-10-20. Goal: improve robustness/usability against pre-launch TODO an | Playback/fallback/magnets | Queued torrent/URL lifecycle fixes7d20a509/89ccb907; mirror guard0bd0b1c3; independent URL18 scenarios. QA actual coordinator/service scratch4 and local Chromium/Firefox3 each, literal hash/webseed checks | Retained QA1 landed1bb3a4fc;dispatch independently4/4,normal gate429 files | Actual WebTorrent/live peers; full-app fallback; local decoded mirror/substituted torrent does not prove real torrent | | Profile/session/history | Profile fixture hang fixedde6918ce;27 focused historical scenarios and current broad suite. History lazy-init test injects fake renderer | client-reliability-6 test-only real wiring regression after current pass/historical-noop failure, TODO2 regression gap | Amber NIP46 identity/switch/logout across refresh, real signers; KNOWN_ISSUES isolated logout UI failure | | Storage/upload safety | CORS productd3f4c215,closure7f99c045; independent9 CORS scenarios,worker focused13/10mutants/full428/lint/build; content-addressed/cleanup safeguards documented | storage-cors-2 narrow metadata no-throw guard after2 reproduced rejects/6controls | Live bucket/IAM/provider serialization; concurrent read-modify-write has no CAS; no credentials/cloud writes tested | -| Mobile usability | No fresh local mobile flow/visual qualification from trial | playback-qa-2 actual-app local Chromium core/mobile3-viewport baseline,existing mock-relay harness,private port,no UI edits | Real-device/manual accessibility and UX; visual baselines remain CI-canonical | +| Mobile usability | No fresh local mobile flow/visual qualification from trial | QA2 first360pass BLOCKED:static genuineBufferCDN guard+missing generatedCSS,remainingflows unverified. Genuinecached pinned mirror+verified builtartifact prep ready;secondpass afterstorage2 integration,no UI edits | Real-device/manual accessibility and UX; visual baselines remain CI-canonical | | Trustworthy validation | Unchanged normal gates and restored failing mutants; latest product gate428 files. QA retained regression429-file diagnostic+normal gate passed1bb3a4fc | Retain exact counts, ancestry, fixture boundaries, failed mutants and Known Issues classification | Full broad E2E/release/manual cross-browser checks; never weaken assertions or treat mocked boundaries as live proof | | Preview | Preserved localhost3000,storage integrated7f99c045; build/artifact pass,HTTP200/index matches dist | Refresh after each verified product landing; QA is test-only so inspect product delta before rebuilding | Production/live-site state not touched or qualified | @@ -21,3 +21,20 @@ Updated2026-10-20. Goal: improve robustness/usability against pre-launch TODO an ## Resource and operating limits QA currently owns full/normal gate; other investigations may use cheap focused local checks, no expensive full suite/build/browser without dispatch slot. Separate worktrees/branches; no competing product writers or app.js edits. Keep team at dispatch+3 existing workers while useful disjoint tasks exist. Clear inherited NODE_TEST_CONTEXT for CLI launches. Use mailbox/CLI/status recovery, actual send/consume times, no raw pane bodies or unattended-wakeup claim. No push/deploy/spend/real-wallet/production-data/scheduler/credentialed external operations. + +## Reconciled Oct2026 upstream playback promises + +Read-only origin fetch was performed by interpreter;upstream-only8d4622bb adds12 canonical TODO lines. Dispatch merges that docs-only source non-destructively through normal local queue. These items stay OPEN;static inspection below is not a live reproduction or completion claim. High-priority1/2/4 are explicitly publicly promised in the source. + +| Item | Current source/local evidence | Local-review implication | +|---|---|---| +|1 HIGH:RangeGET instead ofHEAD | urlHealthController.probeUrl still fetches HEAD;existing media confirmation fallback also present | Open compatibility report;no new hard local-review blocker demonstrated solely by static search | +|2 HIGH:persist health/per-host score | URL get/store already read/write localStorage;grid torrent health uses in-memory map;per-host score not found in inspected health modules | Partial existing implementation,not whole item done;reload/torrent/per-host promise remains | +|3 parallel hosted/P2P startup | Current service/coordinator has sequential fallback/watchdogs | Not requalified as parallel startup;do not change playback policy in this bounded milestone | +|4 HIGH:hover/viewport warmup | Viewport health probes/preload metadata exist and clean up;retained playback warming not established in inspected feed code | Do not equate probe ahead-of-scroll with fulfilling promised warmup;open | +|5 alternate copies/NIP71/Blossomhash | Retained actual service regression verifies ordered hosted mirror selection;Blossom hash cross-server behavior not qualified | Partially proven mirrors,whole promise stays open | +|6 downrank/hide unknown/timeout | Existing liveness policies present;exact default-feed behavior not revalidated here | Open;no new moderation/ranking product choice | +|7 per-video status/copy | Current coordinator has per-video error/fallback strings;banner/status refresh requirement not fully audited | Open copy/UX followup,not claimed complete | +|8 SW shell/thumbnails | Existing service-worker app/image caches present | Repeat-visit/offline production behavior not qualified;BufferCDN remains baseline prep constraint | + +No demonstrated new hard local-review blocker yet from the added reports. QA actual-app second pass may reveal one;report before calling milestone ready. Include these remaining promises and real Amber/cloud/live peers/manual release gates in handoff. diff --git a/fleet/RULE-CHANGES.md b/fleet/RULE-CHANGES.md index e83ed3c1..184b4908 100644 --- a/fleet/RULE-CHANGES.md +++ b/fleet/RULE-CHANGES.md @@ -9,6 +9,9 @@ a landing rule changes. ## 2026-10-20 +- Owner stopmilestone is Ready for me to review and test locally. Finish current storage2/history6/faithful actual-app baseline+preview/handoff,then no new optional tasks before review. Reconcile upstream docs-only8d4622bb via area merge+normal queue;new playback promises stay explicit/open,do not churn current scope or declare them done from partial/static evidence. Genuine cached offline dependency mirror is testprep only,not CDN/deployed/offline production proof. + + - QA1 retained regression64143f42 landed1bb3a4fc,dispatch4/4. New QA2 actual-app local core/mobile baseline has bounded browser-only slot,no UI changes/full E2E. Client6 test-only actual history wiring regression routed after current pass/historical no-op failure. Storage2 temporary ownership deleteVideoStorage-only in r2Service+new test after2 real cached-metadata rejects/6controls;upload-copy logic unchanged,s3UploadService has no deletion counterpart,prove shared provider-aware delete for R2/S3. Full slots allocated only when validated-ready after browser release. diff --git a/js/services/r2Service.js b/js/services/r2Service.js index f5833e33..cbb2d104 100644 --- a/js/services/r2Service.js +++ b/js/services/r2Service.js @@ -110,12 +110,13 @@ export class R2Service { * @param {object} [params.credentials] - pre-resolved connection settings * @returns {Promise<{deleted:string[],failed:Array,skipped:boolean,reason:string}>} */ - async deleteVideoStorage({ - videos = [], - npub = "", - pubkey = "", - credentials = null, - } = {}) { + async deleteVideoStorage(options = {}) { + const { + videos = [], + npub = "", + pubkey = "", + credentials = null, + } = options && typeof options === "object" ? options : {}; const list = (Array.isArray(videos) ? videos : [videos]).filter( (entry) => entry && typeof entry === "object" ); @@ -130,6 +131,12 @@ export class R2Service { return skip("no-videos"); } + if ( + (npub != null && typeof npub !== "string") || + (pubkey != null && typeof pubkey !== "string") + ) { + return skip("invalid-owner"); + } let resolvedNpub = (npub || "").trim(); if (!resolvedNpub && pubkey) { resolvedNpub = safeEncodeNpub(pubkey) || ""; @@ -150,6 +157,16 @@ export class R2Service { return skip("no-connection"); } + // Cached metadata is not guaranteed to contain strings. Reject malformed + // settings before trimming or constructing any client; never coerce objects + // into credentials or a bucket URL during best-effort cleanup. + if ( + typeof settings !== "object" || Array.isArray(settings) || + ["accessKeyId", "secretAccessKey", "bucket", "publicBaseUrl", "baseDomain"] + .some((key) => settings[key] != null && typeof settings[key] !== "string") + ) { + return skip("invalid-connection"); + } const accessKeyId = (settings.accessKeyId || "").trim(); const secretAccessKey = (settings.secretAccessKey || "").trim(); const bucket = (settings.bucket || "").trim(); diff --git a/test_logs/TEST_LOG_2026-10-20_15-01-40_storage-cors-2.md b/test_logs/TEST_LOG_2026-10-20_15-01-40_storage-cors-2.md new file mode 100644 index 00000000..f381ff1a --- /dev/null +++ b/test_logs/TEST_LOG_2026-10-20_15-01-40_storage-cors-2.md @@ -0,0 +1,6 @@ +# storage-cors-2 read-only evidence +- HEADd88716e3c6351468a53bc7fdb84fe912c01697b5; current route is storage-delete audit, feed deferred3. +- `env -u NODE_TEST_CONTEXT NODE_ENV=test node /tmp/storage-cors-delete-audit.mjs`:exit1;6controls pass,2/2 malformed synthetic cached records reject TypeError(expected0);0network attempts,0SDK clients/deletes for malformed cases. Actual service+connection mapping/storage metadata IO+key collector+delete helper, fake SDK/transport. +- `env -u NODE_TEST_CONTEXT node scripts/run-unit-tests.mjs r2-delete-video-storage delete-storage-locked storage-key-collision`:exit0,3/3 files,11 scenarios. No new product/committed tests. +- Earlier deferred feed scratch `env -u NODE_TEST_CONTEXT NODE_ENV=test timeout25s node /tmp/storage-cors-feed-audit.mjs`:exit1 initial seed control0/2; fixture invalid, no feed deletion finding. +- Dispatch evidence/scope request sent15:01:20UTC. Awaiting routing;metadata still queued behind QA. No full/build/browser/cloud/production/signer activity. diff --git a/test_logs/TEST_LOG_2026-10-20_15-09-00_storage-cors-2.md b/test_logs/TEST_LOG_2026-10-20_15-09-00_storage-cors-2.md new file mode 100644 index 00000000..e94da126 --- /dev/null +++ b/test_logs/TEST_LOG_2026-10-20_15-09-00_storage-cors-2.md @@ -0,0 +1,8 @@ +# storage-cors-2 pre-full checks +- Baseac99ae9a6bdbeae555a30f659b0720ae67e777e8; item2 claimed,git diff filedAt7f99c045→HEAD r2Service empty before implementation. +- `env -u NODE_TEST_CONTEXT NODE_ENV=test node tests/storage-delete-malformed-settings.test.mjs`:exit0,13/13 scenarios. Original HEAD method via scratch harness:exit1,9/13 failed. +- `python3 /tmp/storage-cors-delete-mutants.py`:exit0,all7 broken method variants rejected(exit1) and restored. Every retained scenario fails under at least one. +- `env -u NODE_TEST_CONTEXT node scripts/run-unit-tests.mjs storage-delete-malformed-settings r2-delete-video-storage delete-storage-locked storage-key-collision`:exit0,4/4 files,24scenarios. +- `env -u NODE_TEST_CONTEXT npm run lint`:exit0,11 checks(sandbox permission for read-only Git child as before). +- `env -u NODE_TEST_CONTEXT npm run build`:exit0 incl deploy-artifact verification. +- `git diff --check`:exit0. Full/browser not run; full/normal slot requested15:08:52UTC. No real delete/network/cloud/signer/private-data use. diff --git a/test_logs/TEST_LOG_2026-10-20_15-16-00_storage-cors-2.md b/test_logs/TEST_LOG_2026-10-20_15-16-00_storage-cors-2.md new file mode 100644 index 00000000..bd85423c --- /dev/null +++ b/test_logs/TEST_LOG_2026-10-20_15-16-00_storage-cors-2.md @@ -0,0 +1,8 @@ +# storage-cors-2 merged gate +- Product7b8100a6a1c9d401104e3038a7314b58e3a1c453,required unstable merge020f0577ee5fb212a689bb06260f10abd47e4868 (incoming docs only). +- Slot requested15:08:52UTC,granted15:09:29UTC,37s resource wait;start acknowledgement15:09:58UTC;full confirmed15:15:59UTC. +- `env -u NODE_TEST_CONTEXT UNIT_TEST_TIMEOUT_MS=60000 npm test`:exit0,430/430 files,normal runner with60s/file diagnostic. +- Merged `env -u NODE_TEST_CONTEXT node scripts/run-unit-tests.mjs storage-delete-malformed-settings r2-delete-video-storage delete-storage-locked storage-key-collision`:exit0,4/4files,24scenarios. +- Merged `env -u NODE_TEST_CONTEXT npm run lint`:exit0,11checks;`env -u NODE_TEST_CONTEXT npm run build`:exit0 including dist-artifact verification. +- No product edits after13scenario/7mutant proof. Full normal queue still pending;no landing claim yet. +- No browser/cloud/private-data/signer/use;owner-base and provider paths preserved. Existing caller catches mean note-deletion blocking was never demonstrated. Feed3 deferred. diff --git a/test_logs/TEST_LOG_2026-10-20_15-23-00_storage-cors-2.md b/test_logs/TEST_LOG_2026-10-20_15-23-00_storage-cors-2.md new file mode 100644 index 00000000..be9d7ac0 --- /dev/null +++ b/test_logs/TEST_LOG_2026-10-20_15-23-00_storage-cors-2.md @@ -0,0 +1,9 @@ +# storage-cors-2 completion +- Submission1650b3439fee7d419b68a0badcee67cee0cfa9aa:durable landed e7b9afb309d822fca9fdc1cc31e79ce7c9c33fbe at15:22:48.611UTC;unchanged normal install+unit gate exit0. +- `git merge-base --is-ancestor 1650b3439fee7d419b68a0badcee67cee0cfa9aa unstable`:exit0. +- `git merge-base --is-ancestor e7b9afb309d822fca9fdc1cc31e79ce7c9c33fbe unstable`:exit0. +- `git log -1 e7b9afb3`:correct storage landing. Latest unstable log1b3ef49c is newer dispatch docs;submission/landing ancestry establishes integration despite later tip. +- `backlog.mjs close storage-cors-2`:success after proof. Actual release notification15:23:08UTC. +- Qualification:430/430fullfiles,24focusedscenarios/4files,13newregressions(original9fail),7rejected/restoredmutants,lint11/build-artifact pass. +- Timings:start14:59:46UTC;routing298s;fullslotwait37s;full15:09:58–15:15:59;normalgate~385s;closure notes15:23UTC. No browser/tool contention requiring idle waits beyond dispatch slots;no new dependency install. +- Limits:no livecloud/IAM/provider serialization,actual malformed private records,arbitrary throwing proxies/getters,real signer/release/manual qualification. No app/uploadcopy/key/provider-policy changes. Feed3 deferred,no finding from invalidseedfixture. diff --git a/tests/storage-delete-malformed-settings.test.mjs b/tests/storage-delete-malformed-settings.test.mjs new file mode 100644 index 00000000..edfdc48f --- /dev/null +++ b/tests/storage-delete-malformed-settings.test.mjs @@ -0,0 +1,154 @@ +// Actual R2Service/StorageService resolution, SDK wrapper and deleteObject; +// only persistence IO and remote SDK transport are virtualized. Integrity note: +// docs/testing/storage-cors-delete-integrity.md +import test from "node:test"; +import assert from "node:assert/strict"; +import { registerHooks } from "node:module"; +import "./test-helpers/setup-localstorage.mjs"; + +let requests = []; +let configurations = []; +let failTorrent = false; +const transportKey = Symbol.for("bitvid.storage-delete-test-transport"); +globalThis[transportKey] = { + make(config) { configurations.push(config); }, + async send(command) { + assert.equal(command.constructor.name, "DeleteObjectCommand"); + requests.push(command.input); + if (failTorrent && command.input.Key.endsWith(".torrent")) { + throw new Error("fixture delete failure"); + } + return {}; + }, +}; +const hooks = registerHooks({ + resolve(specifier, context, nextResolve) { + if (specifier.startsWith("https://esm.sh/@aws-sdk/client-s3@")) { + return { url: "offline:storage-delete-sdk", shortCircuit: true }; + } + if (/^https?:/.test(specifier)) throw new Error("Network forbidden in storage delete fixture"); + return nextResolve(specifier, context); + }, + load(url, context, nextLoad) { + if (url === "offline:storage-delete-sdk") return { + format: "module", shortCircuit: true, + source: `const transport = globalThis[Symbol.for("bitvid.storage-delete-test-transport")]; + export class S3Client { constructor(config) { transport.make(config); } + send(command) { return transport.send(command); } } + export class DeleteObjectCommand { constructor(input) { this.input = input; } }`, + }; + return nextLoad(url, context); + }, +}); +globalThis.__BITVID_DISABLE_NETWORK_IMPORTS__ = false; +const { ensureS3SdkLoaded } = await import("../js/storage/s3-client.js"); +await ensureS3SdkLoaded(); +const { R2Service } = await import("../js/services/r2Service.js"); +const { default: storageService } = await import("../js/services/storageService.js"); +hooks.deregister(); +globalThis.__BITVID_DISABLE_NETWORK_IMPORTS__ = true; +const base = "https://bucket.example.invalid/owned"; +const owner = "a".repeat(64); +const settings = { provider: "cloudflare_r2", accountId: "fixture-account", bucket: "owned-bucket", publicBaseUrl: base, accessKeyId: "fixture-access", secretAccessKey: "fixture-key" }; +const videos = [{ url: `${base}/clip.mp4`, thumbnail: `${base}/thumb.jpg` }]; +const reset = () => { requests = []; configurations = []; failTorrent = false; }; +const noIo = () => { assert.deepEqual(requests, []); assert.deepEqual(configurations, []); }; +const assertSkipped = (result, reason) => { + if (reason) assert.equal(result.reason, reason); + assert.equal(result.skipped, true); + assert.ok(result.reason); + assert.deepEqual(result.deleted, []); + assert.deepEqual(result.failed, []); +}; + +for (const override of [{ bucket: 17 }, { publicBaseUrl: { url: base } }]) { + const key = Object.keys(override)[0]; + test(`malformed cached ${key} metadata resolves to a skip without IO`, async () => { + const originalRead = storageService._getAccount; + try { + reset(); + storageService._getAccount = async () => ({ connections: { fixture: { + id: "fixture", provider: "cloudflare_r2", + meta: { defaultForUploads: true, accountId: "fixture-account", bucket: settings.bucket, publicBaseUrl: base, ...override }, + } } }); + const service = new R2Service(); + const resolved = await service.resolveConnection(owner); + assert.deepEqual(resolved[key], override[key]); + assertSkipped(await service.deleteVideoStorage({ npub: owner, videos }), "invalid-connection"); + noIo(); + } finally { storageService._getAccount = originalRead; } + }); +} + +for (const key of ["accessKeyId", "secretAccessKey", "bucket", "publicBaseUrl", "baseDomain"]) { + test(`malformed direct ${key} is skipped without object coercion or IO`, async () => { + for (const value of [17, true, [], { trim() { throw new Error("must not invoke object methods"); }, toString() { throw new Error("must not coerce"); } }]) { + reset(); + assertSkipped(await new R2Service().deleteVideoStorage({ credentials: { ...settings, [key]: value }, videos }), "invalid-connection"); + noIo(); + } + }); +} + +test("malformed direct owner identifiers are skipped before resolving a connection", async () => { + for (const field of ["npub", "pubkey"]) { + reset(); + const service = new R2Service(); + service.resolveConnection = async () => { throw new Error("must not resolve malformed owner"); }; + assertSkipped(await service.deleteVideoStorage({ [field]: { trim() { throw new Error("must not trim an object"); } }, credentials: settings, videos }), "invalid-owner"); + noIo(); + } +}); + +test("missing, null, or primitive parameter objects safely skip", async () => { + for (const options of [undefined, null, 17, "bad", false]) { + reset(); + const result = await new R2Service().deleteVideoStorage(options); + assertSkipped(result); + assert.equal(result.reason, "no-videos"); + noIo(); + } +}); + +test("valid R2 and generic S3 delete all owned objects with provider settings intact", async () => { + for (const generic of [false, true]) { + reset(); + const credentials = { ...settings, provider: generic ? "generic_s3" : "cloudflare_r2", accountId: generic ? "" : settings.accountId, endpoint: "https://s3.example.invalid", region: "fixture-region", forcePathStyle: false }; + const result = await new R2Service().deleteVideoStorage({ credentials, videos }); + assert.equal(result.skipped, false); + assert.deepEqual(result.failed, []); + assert.deepEqual(new Set(result.deleted), new Set(["clip.mp4", "clip.torrent", "thumb.jpg"])); + assert.deepEqual(requests.map(request => request.Bucket), [settings.bucket, settings.bucket, settings.bucket]); + assert.equal(configurations.length, 1); + assert.equal(configurations[0].forcePathStyle, !generic); + assert.equal(configurations[0].endpoint, credentials.endpoint); + assert.equal(configurations[0].region, credentials.region); + } +}); + +test("external hosts and base-prefix siblings cannot cause deletes", async () => { + reset(); + const result = await new R2Service().deleteVideoStorage({ credentials: settings, videos: [{ url: "https://external.example.invalid/clip.mp4" }, { url: `${base}-sibling/clip.mp4` }] }); + assertSkipped(result); + assert.equal(result.reason, "no-matching-objects"); + noIo(); +}); + +test("locked storage distinguishes owned objects from external URLs", async () => { + for (const owned of [false, true]) { + reset(); + const result = await new R2Service().deleteVideoStorage({ credentials: { ...settings, accessKeyId: "", secretAccessKey: "" }, videos: owned ? videos : [{ url: "https://external.example.invalid/clip.mp4" }] }); + assertSkipped(result); + assert.equal(result.reason, owned ? "storage-locked" : "no-matching-objects"); + noIo(); + } +}); + +test("partial delete failures are reported while later owned objects still delete", async () => { + reset(); failTorrent = true; + const result = await new R2Service().deleteVideoStorage({ credentials: settings, videos }); + assert.equal(result.skipped, false); + assert.deepEqual(new Set(result.deleted), new Set(["clip.mp4", "thumb.jpg"])); + assert.deepEqual(result.failed, [{ key: "clip.torrent", error: "fixture delete failure" }]); + assert.equal(requests.length, 3); +}); diff --git a/todo/TODO_2026-06-20_pre-launch.md b/todo/TODO_2026-06-20_pre-launch.md index c92b85fd..cb9a552f 100644 --- a/todo/TODO_2026-06-20_pre-launch.md +++ b/todo/TODO_2026-06-20_pre-launch.md @@ -2178,3 +2178,15 @@ bitvid's EXISTING NWC pipeline; keep manual URI entry as the backup/advanced pat (PLATFORM_FEE_PERCENT=30, preimage verify, no pay_invoice auto-resend) untouched. - [ ] Phased + flag-gated: Phase 0 flag+vendor, Phase 1 connect button, Phase 2 polish (balance/reconnect), Phase 3 optional WebLN path for Alby-extension/Hub. + +## Playback reliability (from Nostr feedback, Oct 2026) +Items 1, 2 and 4 are **HIGH PRIORITY** — publicly promised in bitvid's Nostr replies. + +- [ ] **HIGH PRIORITY** — 1. Probe URLs with a small Range GET (first ~1 KB) instead of HEAD; many hosts/CDNs mishandle HEAD. File: `js/ui/urlHealthController.js` (`probeUrl`). +- [ ] **HIGH PRIORITY** — 2. Persist URL and torrent health in localStorage across visits with a per-host failure score; today the 45-minute cache is lost on reload. Files: `js/state/cache.js` (`storeUrlHealth`), `js/gridHealth.js`. +- [ ] 3. Start direct URL and WebTorrent in parallel instead of waiting out the 8-second stall. File: `js/services/playbackService.js` (`registerUrlPlaybackWatchdogs`). +- [ ] **HIGH PRIORITY** — 4. Warm up videos on hover or scroll-into-view by loading metadata or a small first chunk. Files: `js/app/feedCoordinator.js` and card components. +- [ ] 5. Auto-try alternate copies: NIP-71 fallback URLs and the same Blossom sha256 on other servers. Files: `js/nostr/nip71.js`, `js/services/blossomService.js`, `js/ui/urlHealthController.js`. +- [ ] 6. Rank lower or hide videos with timeout/unknown health in default feeds. Files: `js/gridHealth.js`, `js/app/feedCoordinator.js`. +- [ ] 7. Replace the 10-60s banner with per-video status (e.g. "host slow, trying P2P…") and a clear failure message. Files: `components/disclaimer.html:84`, `js/app/playbackCoordinator.js`. +- [ ] 8. Service worker caches app shell and thumbnails (not videos) for faster repeat visits. Files: `sw.min.js` source and `_headers`. diff --git a/todo/TODO_2026-10-20_15-09-00_storage-cors-2.md b/todo/TODO_2026-10-20_15-09-00_storage-cors-2.md new file mode 100644 index 00000000..1dfd2cb5 --- /dev/null +++ b/todo/TODO_2026-10-20_15-09-00_storage-cors-2.md @@ -0,0 +1,7 @@ +# storage-cors-2 +- [x] Real cached malformed metadata defect reproduced2/2;6controls,independent dispatch confirmation/routing. +- [x] Method-only guard/new13scenarios;original fails9;7restored mutants rejected. +- [x] Focused24scenarios/4files,lint11,build/artifact pass. +- [ ] Commit/merge unstable;merged relevant checks. +- [ ] Dispatch full/submit slot;full normal gate,queue/durable landing/ancestry before close. +- Feed3 deferred; cloud/provider IAM/serialization/live user data unverified; no product scope expansion. diff --git a/todo/TODO_2026-10-20_15-23-00_storage-cors-2.md b/todo/TODO_2026-10-20_15-23-00_storage-cors-2.md new file mode 100644 index 00000000..76058cfa --- /dev/null +++ b/todo/TODO_2026-10-20_15-23-00_storage-cors-2.md @@ -0,0 +1,7 @@ +# storage-cors-2 done +- [x] Actual cached metadata no-throw failure2/2 plus6controls;independent dispatch confirmation/routing. +- [x] Method-only guard+13newscenarios;original9fail,7restoredmutants rejected. +- [x] Focused24scenarios/4files,lint11/build,full430files pass. +- [x] Normal gateexit0,durable product landing e7b9afb3;submission+landedSHA unstable ancestry0;backlog closed;slot released15:23:08UTC. +- [ ] Livecloud/provider IAM/serialization/manual release qualification remains unverified/outside authorization. +- Deferredfeed3 retained;do not start optional item under finite local-review milestone.