chore: add release video visual qa

This commit is contained in:
Affaan Mustafa
2026-05-19 08:59:55 -04:00
committed by Affaan Mustafa
parent 855e8c8336
commit b62f80750d
7 changed files with 95 additions and 19 deletions

View File

@@ -8,9 +8,9 @@ social announcement.
| Field | Evidence | | Field | Evidence |
| --- | --- | | --- | --- |
| Upstream main | `f3cd00625222fceedca00164b828db8803fe52d6` | | Upstream main | `855e8c8336e1c18523cbb31cb29f4ce96d7518a7` |
| Git remote | `https://github.com/affaan-m/ECC.git` | | 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 | | 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 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 #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 #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 #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 ## 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 | | 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 | | 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 | | 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 | | 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` | 2544 passed, 0 failed | | Full local suite | `node tests/run-all.js` | 2545 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 The tracked public PR queue, issue queue, discussion queue, canonical ECC
identity, release video suite, preview pack, and growth outreach packet are identity, release video suite, preview pack, and growth outreach packet are
current on May 19, 2026 for `main` through current on May 19, 2026 for `main` through
`f3cd00625222fceedca00164b828db8803fe52d6`, with the publish-candidate `855e8c8336e1c18523cbb31cb29f4ce96d7518a7`, with the visual video QA gate
video gate staged for the next merge. staged for the next merge.
This improves publication readiness but does not replace the approval-gated This improves publication readiness but does not replace the approval-gated
release, package, plugin, billing, Discord, and announcement steps in release, package, plugin, billing, Discord, and announcement steps in

View File

@@ -178,6 +178,8 @@ Then manually check the final render for:
1280x720, video stream present, audio stream present, and non-empty output; 1280x720, video stream present, audio stream present, and non-empty output;
- validator self-eval passes for the publish-candidate set: primary MP4 plus - validator self-eval passes for the publish-candidate set: primary MP4 plus
captions and five short clips in both wide and vertical formats; 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 blank frames or accidental desktop exposure;
- no stale repo name, pivot, rename, or Claude-only framing in captions; - no stale repo name, pivot, rename, or Claude-only framing in captions;
- no captions that rewrite speech into a false claim; - no captions that rewrite speech into a false claim;

View File

@@ -472,7 +472,7 @@ function buildLocalEvidenceChecks(rootDir) {
), ),
buildCheck( buildCheck(
'release-evidence-current', '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', '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' } { path: 'docs/releases/2.0.0-rc.1/publication-evidence-2026-05-19.md' }
), ),

View File

@@ -310,7 +310,11 @@ const REQUIRED_PUBLISH_CANDIDATES = [
minSizeMb: 2, minSizeMb: 2,
requiresAudio: true, requiresAudio: true,
}, },
]; ].map(candidate => (
candidate.kind === 'video'
? { noBlackFrames: true, ...candidate }
: candidate
));
function usage() { function usage() {
console.log([ console.log([
@@ -556,6 +560,55 @@ function probeMedia(filePath, skipProbe) {
return result; 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) { function resolveSourceAssetPath(sourceRoot, fileName) {
const candidates = [ const candidates = [
path.join(sourceRoot, fileName), path.join(sourceRoot, fileName),
@@ -656,6 +709,14 @@ function validateVideoArtifact(artifact, media, skipProbe) {
failures.push('audio stream missing'); 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; return failures;
} }
@@ -680,12 +741,17 @@ function inspectArtifactCollection(rootDir, artifacts, skipProbe) {
}; };
} }
const media = artifact.kind === 'video' ? probeMedia(filePath, skipProbe) : { const media = artifact.kind === 'video'
sizeBytes: fs.statSync(filePath).size, ? {
sizeMb: formatBytes(fs.statSync(filePath).size), ...probeMedia(filePath, skipProbe),
durationSeconds: null, ...(artifact.noBlackFrames ? detectBlackSegments(filePath, skipProbe) : {}),
probe: 'not-media', }
}; : {
sizeBytes: fs.statSync(filePath).size,
sizeMb: formatBytes(fs.statSync(filePath).size),
durationSeconds: null,
probe: 'not-media',
};
const validationFailures = validateVideoArtifact(artifact, media, skipProbe); const validationFailures = validateVideoArtifact(artifact, media, skipProbe);
return { return {
@@ -860,7 +926,7 @@ function buildReport(options = {}) {
'video-publish-candidates-present', 'video-publish-candidates-present',
missingPublishCandidates.length === 0 ? 'pass' : 'fail', missingPublishCandidates.length === 0 ? 'pass' : 'fail',
missingPublishCandidates.length === 0 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 => { : `missing or invalid publish candidates: ${missingPublishCandidates.map(candidate => {
const reason = candidate.validationFailures && candidate.validationFailures.length > 0 const reason = candidate.validationFailures && candidate.validationFailures.length > 0
? ` (${candidate.validationFailures.join(', ')})` ? ` (${candidate.validationFailures.join(', ')})`

View File

@@ -178,7 +178,7 @@ function seedRepo(rootDir, overrides = {}) {
'Operator dashboard', 'Operator dashboard',
'GitGuardian', 'GitGuardian',
'macOS/Ubuntu/Windows test matrix', 'macOS/Ubuntu/Windows test matrix',
'2544 passed', '2545 passed',
'Business baseline', 'Business baseline',
'$1,728/mo', '$1,728/mo',
'$8,272/mo' '$8,272/mo'

View File

@@ -68,7 +68,7 @@ function seedRepo(rootDir, overrides = {}) {
'Operator dashboard', 'Operator dashboard',
'GitGuardian', 'GitGuardian',
'macOS/Ubuntu/Windows test matrix', 'macOS/Ubuntu/Windows test matrix',
'2544 passed' '2545 passed'
].join('\n'), ].join('\n'),
'docs/releases/2.0.0-rc.1/operator-readiness-dashboard-2026-05-19.md': [ 'docs/releases/2.0.0-rc.1/operator-readiness-dashboard-2026-05-19.md': [
'This dashboard is generated by `npm run operator:dashboard`', 'This dashboard is generated by `npm run operator:dashboard`',

View File

@@ -194,6 +194,13 @@ function runTests() {
} }
})) passed++; else failed++; })) 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', () => { if (test('missing local roots keep the release video gate blocked', () => {
const rootDir = createTempDir('release-video-missing-roots-'); const rootDir = createTempDir('release-video-missing-roots-');