From b62f80750d85db35b765c675c3866f2037adc5a8 Mon Sep 17 00:00:00 2001 From: Affaan Mustafa Date: Tue, 19 May 2026 08:59:55 -0400 Subject: [PATCH] chore: add release video visual qa --- .../publication-evidence-2026-05-19.md | 17 ++-- .../2.0.0-rc.1/video-suite-production.md | 2 + scripts/platform-audit.js | 2 +- scripts/release-video-suite.js | 82 +++++++++++++++++-- .../operator-readiness-dashboard.test.js | 2 +- tests/scripts/platform-audit.test.js | 2 +- tests/scripts/release-video-suite.test.js | 7 ++ 7 files changed, 95 insertions(+), 19 deletions(-) diff --git a/docs/releases/2.0.0-rc.1/publication-evidence-2026-05-19.md b/docs/releases/2.0.0-rc.1/publication-evidence-2026-05-19.md index 9eff4949..c2519037 100644 --- a/docs/releases/2.0.0-rc.1/publication-evidence-2026-05-19.md +++ b/docs/releases/2.0.0-rc.1/publication-evidence-2026-05-19.md @@ -8,9 +8,9 @@ social announcement. | Field | Evidence | | --- | --- | -| Upstream main | `f3cd00625222fceedca00164b828db8803fe52d6` | +| Upstream main | `855e8c8336e1c18523cbb31cb29f4ce96d7518a7` | | Git remote | `https://github.com/affaan-m/ECC.git` | -| Evidence scope | Current `main` after PR #1990 harness-audit GitHub integration scoring, PR #1991 canonical ECC identity gate, PR #1992 release video-suite gate, PR #1993 growth outreach pack, PR #1994 May 19 publication evidence refresh, PR #1995 operator dashboard refresh, and PR #1996 primary render self-eval gate | +| Evidence scope | Current `main` after PR #1990 harness-audit GitHub integration scoring, PR #1991 canonical ECC identity gate, PR #1992 release video-suite gate, PR #1993 growth outreach pack, PR #1994 May 19 publication evidence refresh, PR #1995 operator dashboard refresh, PR #1996 primary render self-eval gate, and PR #1997 publish-candidate gate | | Local status caveat | `git status --short --branch` was clean after pulling `origin/main`; generated evidence files are committed after the source snapshot they describe | The release operator must repeat all publish-facing checks from the exact final @@ -45,6 +45,7 @@ Tracked repositories in the platform audit were: | PR #1994 | Merged the May 19 publication-evidence refresh, platform-audit evidence gate, preview-pack smoke evidence gate, and URL/readiness/roadmap references | | PR #1995 | Merged the May 19 operator dashboard refresh with the `$1,728/mo` MRR baseline, `$10,000/mo` target, and release/video/outbound top actions | | PR #1996 | Merged the primary launch render self-eval gate for duration, size, resolution, video stream, and audio stream checks | +| PR #1997 | Merged the publish-candidate gate for the primary launch MP4/captions plus five short clips in wide and vertical formats | ## Release And Growth Evidence @@ -53,9 +54,9 @@ Tracked repositories in the platform audit were: | Release-surface tests | `node tests/docs/ecc2-release-surface.test.js` | 25 passed, 0 failed | | Preview-pack smoke | `npm run preview-pack:smoke -- --format json` | Ready true; digest `bc2bf157616e`; 30 required artifacts; 5 passed, 0 failed | | Operator dashboard | `npm run operator:dashboard -- --markdown --write docs/releases/2.0.0-rc.1/operator-readiness-dashboard-2026-05-19.md` | Generated May 19 dashboard with platform audit ready true, 0 tracked PRs, 0 tracked issues, 0 discussion gaps, `$1,728/mo` current MRR, `$10,000/mo` target MRR, and top actions for plugin publication, notifications, release video, outbound approval, AgentShield, and ECC Tools billing | -| Release video suite | `npm run release:video-suite -- --format json --summary` with `ECC_VIDEO_SOURCE_ROOT` and `ECC_VIDEO_RELEASE_SUITE_ROOT` | Ready true; 15/15 source assets present; 13/13 render, timeline, caption, EDL, and segment artifacts present; 12/12 publish-candidate outputs present; primary rough render self-eval passed at 144.759 seconds, 1920x1080, 1 audio stream, and 106.78 MB | -| Full local suite | `node tests/run-all.js` | 2544 passed, 0 failed | -| PR #1996 CI | GitHub Actions run `26096847138` | Completed successfully for `f3cd00625222fceedca00164b828db8803fe52d6`; all reported checks passed, including lint, validation, security scan, coverage, GitGuardian, CodeRabbit, Cubic, and the macOS/Ubuntu/Windows test matrix | +| Release video suite | `npm run release:video-suite -- --format json --summary` with `ECC_VIDEO_SOURCE_ROOT` and `ECC_VIDEO_RELEASE_SUITE_ROOT` | Ready true; 15/15 source assets present; 13/13 render, timeline, caption, EDL, and segment artifacts present; 12/12 publish-candidate outputs present with zero detected black-frame segments; primary rough render self-eval passed at 144.759 seconds, 1920x1080, 1 audio stream, and 106.78 MB | +| Full local suite | `node tests/run-all.js` | 2545 passed, 0 failed | +| PR #1997 CI | GitHub Actions run `26097832795` | Completed successfully for `855e8c8336e1c18523cbb31cb29f4ce96d7518a7`; all reported checks passed, including lint, validation, security scan, coverage, GitGuardian, CodeRabbit, Cubic, and the macOS/Ubuntu/Windows test matrix | | Public-path sanitization | `node scripts/ci/validate-no-personal-paths.js` through local suite and CI | Passed | | Markdown and whitespace | `markdownlint` focused release docs plus `git diff --check` before PR #1993 | Passed | @@ -65,7 +66,7 @@ Tracked repositories in the platform audit were: | --- | --- | | Canonical repo identity | Public URLs and release docs now use `https://github.com/affaan-m/ECC` where public links are needed | | Release claim | Release notes and launch collateral frame ECC as the harness-native operator system for agentic work, not a Claude-only config pack | -| Video proof | `video-suite-production.md` gates the local rough render, timeline, captions, source inventory, publish-candidate clip set, self-eval, and no-private-path publication rules | +| Video proof | `video-suite-production.md` gates the local rough render, timeline, captions, source inventory, publish-candidate clip set, self-eval, black-frame QA, and no-private-path publication rules | | Growth proof | `partner-sponsor-talks-pack.md` provides approval-gated copy for sponsors, partners, consulting, talks, podcasts, GitHub Discussion, and video CTAs | | Business baseline | Hypergrowth command center and partner pack use `$1,728/mo` current MRR, `$10,000/mo` target MRR, and `$8,272/mo` gap | | Operator dashboard | `operator-readiness-dashboard-2026-05-19.md` pulls the growth baseline into the same queue, publication, video, outbound, AgentShield, ECC Tools, Linear, and supply-chain control surface | @@ -94,8 +95,8 @@ Tracked repositories in the platform audit were: The tracked public PR queue, issue queue, discussion queue, canonical ECC identity, release video suite, preview pack, and growth outreach packet are current on May 19, 2026 for `main` through -`f3cd00625222fceedca00164b828db8803fe52d6`, with the publish-candidate -video gate staged for the next merge. +`855e8c8336e1c18523cbb31cb29f4ce96d7518a7`, with the visual video QA gate +staged for the next merge. This improves publication readiness but does not replace the approval-gated release, package, plugin, billing, Discord, and announcement steps in diff --git a/docs/releases/2.0.0-rc.1/video-suite-production.md b/docs/releases/2.0.0-rc.1/video-suite-production.md index ee2dc24e..19ea68aa 100644 --- a/docs/releases/2.0.0-rc.1/video-suite-production.md +++ b/docs/releases/2.0.0-rc.1/video-suite-production.md @@ -178,6 +178,8 @@ Then manually check the final render for: 1280x720, video stream present, audio stream present, and non-empty output; - validator self-eval passes for the publish-candidate set: primary MP4 plus captions and five short clips in both wide and vertical formats; +- validator visual QA reports zero detected black-frame segments for every + publish-candidate MP4; - no blank frames or accidental desktop exposure; - no stale repo name, pivot, rename, or Claude-only framing in captions; - no captions that rewrite speech into a false claim; diff --git a/scripts/platform-audit.js b/scripts/platform-audit.js index 5cc17b0c..0125a5d8 100644 --- a/scripts/platform-audit.js +++ b/scripts/platform-audit.js @@ -472,7 +472,7 @@ function buildLocalEvidenceChecks(rootDir) { ), buildCheck( 'release-evidence-current', - includesAll(evidence, ['Release video suite', 'growth outreach', 'Operator dashboard', 'GitGuardian', 'macOS/Ubuntu/Windows test matrix', '2544 passed']) ? 'pass' : 'fail', + includesAll(evidence, ['Release video suite', 'growth outreach', 'Operator dashboard', 'GitGuardian', 'macOS/Ubuntu/Windows test matrix', '2545 passed']) ? 'pass' : 'fail', 'rc.1 evidence includes current release, video, growth, and CI artifacts', { path: 'docs/releases/2.0.0-rc.1/publication-evidence-2026-05-19.md' } ), diff --git a/scripts/release-video-suite.js b/scripts/release-video-suite.js index d4abb8d3..d4975d5a 100644 --- a/scripts/release-video-suite.js +++ b/scripts/release-video-suite.js @@ -310,7 +310,11 @@ const REQUIRED_PUBLISH_CANDIDATES = [ minSizeMb: 2, requiresAudio: true, }, -]; +].map(candidate => ( + candidate.kind === 'video' + ? { noBlackFrames: true, ...candidate } + : candidate +)); function usage() { console.log([ @@ -556,6 +560,55 @@ function probeMedia(filePath, skipProbe) { return result; } +function detectBlackSegments(filePath, skipProbe) { + if (skipProbe) { + return { + blackFrameProbe: 'skipped', + blackSegments: null, + }; + } + + const result = { + blackFrameProbe: 'unavailable', + blackSegments: null, + }; + const probe = spawnSync('ffmpeg', [ + '-hide_banner', + '-nostats', + '-i', + filePath, + '-vf', + 'blackdetect=d=0.5:pix_th=0.10', + '-an', + '-f', + 'null', + '-', + ], { + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'pipe'], + timeout: 120000, + }); + + if (probe.error) { + result.blackFrameProbe = `error: ${probe.error.message}`; + return result; + } + + if (probe.status !== 0) { + result.blackFrameProbe = `failed: ${(probe.stderr || '').trim() || `exit ${probe.status}`}`; + return result; + } + + const output = `${probe.stdout || ''}\n${probe.stderr || ''}`; + result.blackSegments = output + .split('\n') + .filter(line => line.includes('black_start')) + .length; + result.blackFrameProbe = 'ok'; + + return result; +} + function resolveSourceAssetPath(sourceRoot, fileName) { const candidates = [ path.join(sourceRoot, fileName), @@ -656,6 +709,14 @@ function validateVideoArtifact(artifact, media, skipProbe) { failures.push('audio stream missing'); } + if (artifact.noBlackFrames) { + if (media.blackFrameProbe !== 'ok') { + failures.push(`blackdetect ${media.blackFrameProbe}`); + } else if (Number.isFinite(media.blackSegments) && media.blackSegments > 0) { + failures.push(`${media.blackSegments} black frame segment(s)`); + } + } + return failures; } @@ -680,12 +741,17 @@ function inspectArtifactCollection(rootDir, artifacts, skipProbe) { }; } - const media = artifact.kind === 'video' ? probeMedia(filePath, skipProbe) : { - sizeBytes: fs.statSync(filePath).size, - sizeMb: formatBytes(fs.statSync(filePath).size), - durationSeconds: null, - probe: 'not-media', - }; + const media = artifact.kind === 'video' + ? { + ...probeMedia(filePath, skipProbe), + ...(artifact.noBlackFrames ? detectBlackSegments(filePath, skipProbe) : {}), + } + : { + sizeBytes: fs.statSync(filePath).size, + sizeMb: formatBytes(fs.statSync(filePath).size), + durationSeconds: null, + probe: 'not-media', + }; const validationFailures = validateVideoArtifact(artifact, media, skipProbe); return { @@ -860,7 +926,7 @@ function buildReport(options = {}) { 'video-publish-candidates-present', missingPublishCandidates.length === 0 ? 'pass' : 'fail', missingPublishCandidates.length === 0 - ? `${publishCandidates.length} publish-candidate MP4/caption artifacts are present and self-evaluable` + ? `${publishCandidates.length} publish-candidate MP4/caption artifacts are present, self-evaluable, and free of detected black-frame segments` : `missing or invalid publish candidates: ${missingPublishCandidates.map(candidate => { const reason = candidate.validationFailures && candidate.validationFailures.length > 0 ? ` (${candidate.validationFailures.join(', ')})` diff --git a/tests/scripts/operator-readiness-dashboard.test.js b/tests/scripts/operator-readiness-dashboard.test.js index d5599a49..6f057403 100644 --- a/tests/scripts/operator-readiness-dashboard.test.js +++ b/tests/scripts/operator-readiness-dashboard.test.js @@ -178,7 +178,7 @@ function seedRepo(rootDir, overrides = {}) { 'Operator dashboard', 'GitGuardian', 'macOS/Ubuntu/Windows test matrix', - '2544 passed', + '2545 passed', 'Business baseline', '$1,728/mo', '$8,272/mo' diff --git a/tests/scripts/platform-audit.test.js b/tests/scripts/platform-audit.test.js index c61026e1..fc63ff10 100644 --- a/tests/scripts/platform-audit.test.js +++ b/tests/scripts/platform-audit.test.js @@ -68,7 +68,7 @@ function seedRepo(rootDir, overrides = {}) { 'Operator dashboard', 'GitGuardian', 'macOS/Ubuntu/Windows test matrix', - '2544 passed' + '2545 passed' ].join('\n'), 'docs/releases/2.0.0-rc.1/operator-readiness-dashboard-2026-05-19.md': [ 'This dashboard is generated by `npm run operator:dashboard`', diff --git a/tests/scripts/release-video-suite.test.js b/tests/scripts/release-video-suite.test.js index a39f2f8c..25a41cdc 100644 --- a/tests/scripts/release-video-suite.test.js +++ b/tests/scripts/release-video-suite.test.js @@ -194,6 +194,13 @@ function runTests() { } })) passed++; else failed++; + if (test('publish candidate videos require visual blank-frame QA', () => { + const publishVideos = REQUIRED_PUBLISH_CANDIDATES.filter(candidate => candidate.kind === 'video'); + + assert.ok(publishVideos.length > 0); + assert.ok(publishVideos.every(candidate => candidate.noBlackFrames === true)); + })) passed++; else failed++; + if (test('missing local roots keep the release video gate blocked', () => { const rootDir = createTempDir('release-video-missing-roots-');