This commit is contained in:
Miguel Ángel
2026-08-31 01:15:17 +07:00
committed by GitHub
4 changed files with 533 additions and 0 deletions
@@ -0,0 +1,48 @@
name: macOS treated-media real-pixel gate
on:
pull_request:
paths:
- "scripts/issue3329-macos-real-pixel.mjs"
- ".github/workflows/issue3329-macos-real-pixel.yml"
- "packages/core/src/runtime/colorGrading.ts"
- "packages/engine/src/services/frameCapture.ts"
- "packages/engine/src/services/screenshotService.ts"
permissions:
contents: read
jobs:
macos-real-pixels:
runs-on: macos-14
timeout-minutes: 45
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
lfs: true
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: 22
- name: Install FFmpeg
run: brew install ffmpeg
- name: Install dependencies
run: bash scripts/ci/install-workspace-dependencies.sh
- name: Build local CLI render stack
run: |
bun run --filter '@hyperframes/{parsers,lint,studio-server}' build
bun run --cwd packages/core build
bun run --filter '@hyperframes/{engine,producer,studio}' build
bun run --filter @hyperframes/cli build
- name: Run real-pixel boundary matrix
env:
HF_ISSUE3329_ARTIFACT_DIR: ${{ github.workspace }}/.artifacts/issue3329-macos
run: node scripts/issue3329-macos-real-pixel.mjs
- name: Upload boundary evidence
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: issue3329-macos-real-pixels-${{ runner.arch }}
path: .artifacts/issue3329-macos
if-no-files-found: error
include-hidden-files: true
+55
View File
@@ -0,0 +1,55 @@
import { existsSync, realpathSync } from "node:fs";
import { homedir } from "node:os";
import { basename, dirname, isAbsolute, parse, relative, resolve, sep } from "node:path";
function rejectTraversal(raw) {
if (raw.split(/[\\/]+/).includes("..")) {
throw new Error("Artifact root must not contain traversal segments");
}
}
function isForbiddenRoot(target, repo, artifactSpace) {
return new Set([parse(target).root, resolve(homedir()), repo, artifactSpace]).has(target);
}
function isDedicatedDescendant(target, artifactSpace) {
const rel = relative(artifactSpace, target);
return rel !== "" && rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
}
function canonicalizeTarget(target) {
const suffix = [];
let ancestor = target;
while (!existsSync(ancestor)) {
const parent = dirname(ancestor);
if (parent === ancestor) {
throw new Error("Artifact root has no canonical ancestor");
}
suffix.unshift(basename(ancestor));
ancestor = parent;
}
return resolve(realpathSync(ancestor), ...suffix);
}
function rejectUnsafeTarget(target, repo, artifactSpace) {
if (
isForbiddenRoot(target, repo, artifactSpace) ||
!isDedicatedDescendant(artifactSpace, repo) ||
!isDedicatedDescendant(target, artifactSpace)
) {
throw new Error(
"Artifact root must be a dedicated descendant of the repository artifact space",
);
}
}
export function validateArtifactRoot(requestedPath, repoRoot) {
const raw = String(requestedPath ?? "");
rejectTraversal(raw);
const repo = realpathSync(resolve(repoRoot));
const artifactSpace = canonicalizeTarget(resolve(repo, ".artifacts"));
const requestedTarget = resolve(repo, raw);
const target = canonicalizeTarget(requestedTarget);
rejectUnsafeTarget(target, repo, artifactSpace);
return requestedTarget;
}
+358
View File
@@ -0,0 +1,358 @@
import { createHash } from "node:crypto";
import { execFileSync, spawnSync } from "node:child_process";
import { copyFileSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { validateArtifactRoot } from "./issue3329-artifact-safety.mjs";
const repo = process.cwd();
const artifactRoot = validateArtifactRoot(
process.env.HF_ISSUE3329_ARTIFACT_DIR ?? join(".artifacts", "issue3329-macos"),
repo,
);
const projectsRoot = join(artifactRoot, "projects");
const outputsRoot = join(artifactRoot, "outputs");
rmSync(artifactRoot, { recursive: true, force: true });
mkdirSync(projectsRoot, { recursive: true });
mkdirSync(outputsRoot, { recursive: true });
function run(binary, args, options = {}) {
const result = spawnSync(binary, args, {
cwd: repo,
encoding: "utf8",
timeout: 180_000,
...options,
});
if (result.status !== 0) {
throw new Error(
`${binary} ${args.join(" ")} failed (${result.status})\n${result.stdout}\n${result.stderr}`,
);
}
return { stdout: result.stdout.trim(), stderr: result.stderr.trim() };
}
function ffmpeg(args) {
return run("ffmpeg", ["-hide_banner", "-loglevel", "error", ...args]);
}
function cli(args) {
return run("bun", ["packages/cli/src/cli.ts", ...args], {
env: { ...process.env, HYPERFRAMES_NO_TELEMETRY: "1" },
});
}
function sha256(path) {
return createHash("sha256").update(readFileSync(path)).digest("hex");
}
function firstFile(dir, suffix) {
const name = readdirSync(dir).find((entry) => entry.endsWith(suffix));
if (!name) throw new Error(`No ${suffix} output under ${dir}`);
return join(dir, name);
}
function pixelStats(path) {
const result = spawnSync(
"ffmpeg",
[
"-hide_banner",
"-loglevel",
"error",
"-i",
path,
"-frames:v",
"1",
"-vf",
"scale=160:90",
"-pix_fmt",
"rgba",
"-f",
"rawvideo",
"-",
],
{ encoding: null },
);
if (result.status !== 0) throw new Error(`Pixel decode failed for ${path}`);
const pixels = result.stdout;
let sum = 0;
let sumSq = 0;
let alphaMin = 255;
let alphaMax = 0;
const count = pixels.length / 4;
for (let offset = 0; offset + 3 < pixels.length; offset += 4) {
const luma =
0.2126 * pixels[offset] + 0.7152 * pixels[offset + 1] + 0.0722 * pixels[offset + 2];
sum += luma;
sumSq += luma * luma;
alphaMin = Math.min(alphaMin, pixels[offset + 3]);
alphaMax = Math.max(alphaMax, pixels[offset + 3]);
}
const mean = sum / count;
const variance = Math.max(0, sumSq / count - mean * mean);
const stddev = Math.sqrt(variance);
return {
mean: Number(mean.toFixed(3)),
stddev: Number(stddev.toFixed(3)),
alphaMin,
alphaMax,
solid: stddev < 2,
};
}
function ffprobe(path) {
const output = execFileSync(
"ffprobe",
[
"-v",
"error",
"-show_entries",
"stream=codec_name,codec_tag_string,pix_fmt,color_space,color_transfer,color_primaries",
"-of",
"json",
"--",
path,
],
{ encoding: "utf8" },
);
return JSON.parse(output).streams?.[0] ?? {};
}
const sourceDir = join(artifactRoot, "source");
mkdirSync(sourceDir, { recursive: true });
const framePath = join(sourceDir, "frame.png");
const videoPath = join(sourceDir, "frame.mp4");
ffmpeg(["-f", "lavfi", "-i", "testsrc2=size=160x90:rate=1", "-frames:v", "1", "-y", framePath]);
ffmpeg([
"-loop",
"1",
"-i",
framePath,
"-t",
"1",
"-r",
"30",
"-c:v",
"libx264",
"-pix_fmt",
"yuv420p",
"-y",
videoPath,
]);
const gradings = {
ungraded: null,
adjustment: { version: 2, colorSpace: "rec709", adjust: { saturation: -0.3 } },
kuwahara: { version: 2, colorSpace: "rec709", effects: { kuwahara: 0.8 } },
};
const ledger = {
environment: {
platform: process.platform,
arch: process.arch,
runnerOs: process.env.RUNNER_OS ?? null,
uname: run("uname", ["-a"]).stdout,
osVersion: process.platform === "darwin" ? run("sw_vers", []).stdout : null,
ffmpeg: run("ffmpeg", ["-version"]).stdout.split("\n")[0],
node: process.version,
bun: run("bun", ["--version"]).stdout,
eligibleMacArm64: process.platform === "darwin" && process.arch === "arm64",
},
source: { frame: sha256(framePath), video: sha256(videoPath) },
rows: [],
};
const requestedCases = new Set(
(process.env.HF_ISSUE3329_CASES ?? "")
.split(",")
.map((value) => value.trim())
.filter(Boolean),
);
for (const mediaKind of ["img", "video"]) {
for (const [gradingName, grading] of Object.entries(gradings)) {
const caseName = `${mediaKind}-${gradingName}`;
if (requestedCases.size > 0 && !requestedCases.has(caseName)) continue;
const projectDir = join(projectsRoot, caseName);
mkdirSync(projectDir, { recursive: true });
const sourceName = mediaKind === "img" ? "frame.png" : "frame.mp4";
copyFileSync(mediaKind === "img" ? framePath : videoPath, join(projectDir, sourceName));
const gradingAttr = grading ? ` data-color-grading='${JSON.stringify(grading)}'` : "";
const media =
mediaKind === "img"
? `<img id="media" src="${sourceName}"${gradingAttr} style="position:absolute;inset:0;width:160px;height:90px;object-fit:fill">`
: `<video id="media" src="${sourceName}" data-start="0" data-duration="1" muted playsinline${gradingAttr} style="position:absolute;inset:0;width:160px;height:90px;object-fit:fill"></video>`;
writeFileSync(
join(projectDir, "index.html"),
`<!doctype html><html><body style="margin:0;background:#123"><div id="root" data-composition-id="${caseName}" data-root="true" data-start="0" data-duration="1" data-width="160" data-height="90" data-fps="1">${media}</div></body></html>`,
);
const caseOut = join(outputsRoot, caseName);
const runtimeDir = join(caseOut, "runtime");
const rawDir = join(caseOut, "raw");
mkdirSync(runtimeDir, { recursive: true });
cli([
"snapshot",
projectDir,
"--at",
"0.5",
"--no-end",
"--output",
runtimeDir,
"--describe",
"false",
]);
cli([
"render",
projectDir,
"--format",
"png-sequence",
"--output",
rawDir,
"--fps",
"1",
"--workers",
"1",
"--quiet",
]);
const h264Path = join(caseOut, "h264.mp4");
const proresPath = join(caseOut, "prores.mov");
cli([
"render",
projectDir,
"--format",
"mp4",
"--output",
h264Path,
"--fps",
"1",
"--workers",
"1",
"--quiet",
]);
cli([
"render",
projectDir,
"--format",
"mov",
"--output",
proresPath,
"--fps",
"1",
"--workers",
"1",
"--quiet",
]);
const runtimePath = firstFile(runtimeDir, ".png");
const rawPath = firstFile(rawDir, ".png");
const hevcPath = join(caseOut, "hevc.mp4");
ffmpeg([
"-loop",
"1",
"-i",
rawPath,
"-t",
"1",
"-r",
"1",
"-c:v",
"libx265",
"-tag:v",
"hvc1",
"-pix_fmt",
"yuv420p",
"-y",
hevcPath,
]);
const boundaries = {};
for (const [name, path] of Object.entries({
runtime: runtimePath,
raw: rawPath,
h264: h264Path,
prores: proresPath,
hevc: hevcPath,
})) {
boundaries[name] = {
path,
sha256: sha256(path),
bytes: readFileSync(path).length,
pixels: pixelStats(path),
...(name === "h264" || name === "prores" || name === "hevc"
? { stream: ffprobe(path) }
: {}),
};
}
const firstCorrupt = ["runtime", "raw", "h264", "prores", "hevc"].find(
(name) => boundaries[name].pixels.solid,
);
ledger.rows.push({
caseName,
mediaKind,
gradingName,
firstCorrupt: firstCorrupt ?? null,
boundaries,
});
}
}
const controls = new Map(
ledger.rows.filter((row) => row.gradingName === "ungraded").map((row) => [row.mediaKind, row]),
);
const infrastructureFailures = ledger.rows
.filter((row) => row.gradingName === "ungraded")
.flatMap((row) =>
Object.entries(row.boundaries)
.filter(([, value]) => value.pixels.solid)
.map(([boundary]) => `${row.caseName}@${boundary}`),
);
const productRed = ledger.rows
.filter((row) => row.gradingName !== "ungraded")
.flatMap((row) => {
const control = controls.get(row.mediaKind);
if (!control) return [];
return Object.entries(row.boundaries)
.filter(
([boundary, value]) =>
value.pixels.solid && control.boundaries[boundary]?.pixels.solid === false,
)
.map(([boundary]) => `${row.caseName}@${boundary}`);
});
ledger.classification = !ledger.environment.eligibleMacArm64
? "ineligible_environment"
: infrastructureFailures.length > 0
? "fixture_or_capture_infrastructure_failure"
: productRed.length > 0
? "product_red"
: "no_reproduction";
ledger.infrastructureFailures = infrastructureFailures;
ledger.productRed = productRed;
writeFileSync(join(artifactRoot, "ledger.json"), `${JSON.stringify(ledger, null, 2)}\n`);
console.log(
JSON.stringify(
{
artifactRoot,
environment: ledger.environment,
rows: ledger.rows.map(({ caseName, firstCorrupt, boundaries }) => ({
caseName,
firstCorrupt,
stddev: Object.fromEntries(
Object.entries(boundaries).map(([name, value]) => [name, value.pixels.stddev]),
),
})),
},
null,
2,
),
);
if (!ledger.environment.eligibleMacArm64) {
throw new Error(
`INELIGIBLE_ENVIRONMENT: expected darwin/arm64, got ${process.platform}/${process.arch}`,
);
}
if (infrastructureFailures.length > 0) {
throw new Error(
`INFRASTRUCTURE_FAILURE: ungraded controls are solid at ${infrastructureFailures.join(", ")}`,
);
}
if (productRed.length > 0) {
throw new Error(
`PRODUCT_RED: graded output is solid while its matching control is non-solid at ${productRed.join(", ")}`,
);
}
@@ -0,0 +1,72 @@
import assert from "node:assert/strict";
import {
mkdirSync,
mkdtempSync,
readFileSync,
realpathSync,
rmSync,
symlinkSync,
unlinkSync,
} from "node:fs";
import { homedir, tmpdir } from "node:os";
import { dirname, join, parse, resolve } from "node:path";
import test from "node:test";
import { fileURLToPath } from "node:url";
const scriptsDir = dirname(fileURLToPath(import.meta.url));
const repo = realpathSync(resolve(scriptsDir, ".."));
let safetyModule = null;
try {
safetyModule = await import("./issue3329-artifact-safety.mjs");
} catch {
// RED: the safety owner does not exist yet.
}
test("cleanup accepts only a dedicated descendant of the repo artifact space", () => {
assert.ok(safetyModule, "artifact cleanup safety module must exist");
const safe = join(repo, ".artifacts", "issue3329-macos", "run-1");
assert.equal(safetyModule.validateArtifactRoot(safe, repo), safe);
});
test("cleanup rejects broad, traversing, home, and arbitrary external targets", () => {
assert.ok(safetyModule, "artifact cleanup safety module must exist");
const outside = mkdtempSync(join(tmpdir(), "hf-issue3329-external-"));
for (const target of [
parse(repo).root,
homedir(),
repo,
join(repo, ".artifacts"),
join(repo, ".artifacts", "issue3329-macos", "..", ".."),
outside,
]) {
assert.throws(() => safetyModule.validateArtifactRoot(target, repo), /artifact root/i);
}
});
test("cleanup rejects a descendant that escapes through a symlink", () => {
assert.ok(safetyModule, "artifact cleanup safety module must exist");
const artifactSpace = join(repo, ".artifacts");
const outside = mkdtempSync(join(tmpdir(), "hf-issue3329-symlink-external-"));
const escape = join(artifactSpace, `escape-${process.pid}-${Date.now()}`);
mkdirSync(artifactSpace, { recursive: true });
symlinkSync(outside, escape, "dir");
try {
assert.throws(
() => safetyModule.validateArtifactRoot(join(escape, "run"), repo),
/artifact root/i,
);
} finally {
unlinkSync(escape);
rmSync(outside, { recursive: true });
}
});
test("ffprobe terminates option parsing before its controlled input path", () => {
const source = readFileSync(join(scriptsDir, "issue3329-macos-real-pixel.mjs"), "utf8");
assert.match(
source,
/"-of",\s*"json",\s*"--",\s*path/,
"ffprobe input must follow the -- option terminator",
);
});