feat(media): shared geo, path-safety and profile loading helpers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0138YqDj3ttfayRwuMKKrqVj
This commit is contained in:
Claude 2026-10-03 17:34:04 +00:00
parent a663764ee8
commit 7ccc96ac11
No known key found for this signature in database
4 changed files with 322 additions and 31 deletions

View File

@ -1,28 +1,65 @@
import { notImplemented } from "./not-implemented.js";
const EARTH_RADIUS_KM = 6371.0088;
function toRadians(degrees: number): number {
return (degrees * Math.PI) / 180;
}
/** Great-circle distance in kilometres. */
export function haversineKm(
_a: { lat: number; lon: number },
_b: { lat: number; lon: number },
a: { lat: number; lon: number },
b: { lat: number; lon: number },
): number {
return notImplemented("haversineKm");
const dLat = toRadians(b.lat - a.lat);
const dLon = toRadians(b.lon - a.lon);
const h =
Math.sin(dLat / 2) ** 2 +
Math.cos(toRadians(a.lat)) * Math.cos(toRadians(b.lat)) * Math.sin(dLon / 2) ** 2;
return 2 * EARTH_RADIUS_KM * Math.asin(Math.min(1, Math.sqrt(h)));
}
/** Round a coordinate to two decimals (about 1 km) before storage or display. */
export function roundCoordinate(_value: number): number {
return notImplemented("roundCoordinate");
export function roundCoordinate(value: number): number {
return Math.round(value * 100) / 100;
}
/** Cosine similarity of two equal-length vectors. */
export function cosineSimilarity(_a: ArrayLike<number>, _b: ArrayLike<number>): number {
return notImplemented("cosineSimilarity");
/** Cosine similarity of two equal-length vectors; 0 when either is all zeros. */
export function cosineSimilarity(a: ArrayLike<number>, b: ArrayLike<number>): number {
if (a.length !== b.length) throw new Error("vector length mismatch");
let dot = 0;
let normA = 0;
let normB = 0;
for (let i = 0; i < a.length; i++) {
const x = a[i] as number;
const y = b[i] as number;
dot += x * y;
normA += x * x;
normB += y * y;
}
if (normA === 0 || normB === 0) return 0;
return dot / Math.sqrt(normA * normB);
}
/** Encode Float32 vectors as little-endian bytes for MediaAnalysis.vectors and centroids. */
export function encodeVectors(_vectors: number[][]): Uint8Array {
return notImplemented("encodeVectors");
export function encodeVectors(vectors: ArrayLike<number>[]): Uint8Array {
const dim = vectors[0]?.length ?? 0;
const bytes = new Uint8Array(vectors.length * dim * 4);
const view = new DataView(bytes.buffer);
vectors.forEach((vector, row) => {
if (vector.length !== dim) throw new Error("vector length mismatch");
for (let i = 0; i < dim; i++) view.setFloat32((row * dim + i) * 4, vector[i] as number, true);
});
return bytes;
}
export function decodeVectors(_bytes: Uint8Array, _dim: number): Float32Array[] {
return notImplemented("decodeVectors");
export function decodeVectors(bytes: Uint8Array, dim: number): Float32Array[] {
if (dim <= 0 || bytes.byteLength % (dim * 4) !== 0) throw new Error("invalid vector bytes");
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
const rows = bytes.byteLength / (dim * 4);
const out: Float32Array[] = [];
for (let row = 0; row < rows; row++) {
const vector = new Float32Array(dim);
for (let i = 0; i < dim; i++) vector[i] = view.getFloat32((row * dim + i) * 4, true);
out.push(vector);
}
return out;
}

View File

@ -1,18 +1,88 @@
import { lstat, readdir, readFile, realpath } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import type { PlatformProfile } from "@rakazo/contracts";
import { notImplemented } from "./not-implemented.js";
import type { ProfileLoadError } from "./profiles.js";
import { parseProfiles } from "./profiles.js";
/** Directory of the profile JSON files shipped with this package. */
export const BUILTIN_PROFILES_DIR = new URL("../profiles/", import.meta.url);
export const BUILTIN_PROFILES_DIR = fileURLToPath(new URL("../profiles/", import.meta.url));
/** Read every *.json in a directory; missing directory yields no profiles. */
/** Read every *.json in a directory; a missing directory yields no profiles. */
export async function loadProfilesFromDir(
_dir: string | URL,
dir: string,
): Promise<{ profiles: PlatformProfile[]; errors: ProfileLoadError[] }> {
return notImplemented("loadProfilesFromDir");
let names: string[];
try {
names = (await readdir(dir)).filter((name) => name.endsWith(".json")).sort();
} catch (error) {
if ((error as NodeJS.ErrnoException).code === "ENOENT") return { profiles: [], errors: [] };
throw error;
}
const documents: Array<{ file: string; json: unknown }> = [];
const errors: ProfileLoadError[] = [];
for (const name of names) {
const file = path.join(dir, name);
try {
documents.push({ file: name, json: JSON.parse(await readFile(file, "utf8")) });
} catch (error) {
errors.push({ file: name, message: error instanceof Error ? error.message : String(error) });
}
}
const parsed = parseProfiles(documents);
return { profiles: parsed.profiles, errors: [...errors, ...parsed.errors] };
}
/** Resolve a path inside root; rejects traversal and symlinks that escape it. */
export async function resolveInside(_root: string, _relative: string): Promise<string> {
return notImplemented("resolveInside");
/** Expand a leading "~/" against the given home directory. */
export function expandHome(input: string, home: string): string {
return input === "~" ? home : input.startsWith("~/") ? path.join(home, input.slice(2)) : input;
}
export class PathOutsideRootError extends Error {
constructor(readonly requested: string) {
super("path is outside the allowed root");
this.name = "PathOutsideRootError";
}
}
function isInside(root: string, candidate: string): boolean {
const relative = path.relative(root, candidate);
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
}
/**
* Resolve `relative` inside `root`. Rejects absolute inputs, traversal, NUL
* bytes, and any existing path whose real location (after symlinks) leaves the
* real root. A path that does not exist yet is checked through its nearest
* existing ancestor.
*/
export async function resolveInside(root: string, relative: string): Promise<string> {
if (relative.includes("\0") || path.isAbsolute(relative))
throw new PathOutsideRootError(relative);
const realRoot = await realpath(root);
const lexical = path.resolve(realRoot, relative);
if (!isInside(realRoot, lexical)) throw new PathOutsideRootError(relative);
let probe = lexical;
for (;;) {
try {
await lstat(probe);
break;
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
const parent = path.dirname(probe);
if (parent === probe) throw new PathOutsideRootError(relative);
probe = parent;
}
}
const real = await realpath(probe);
if (!isInside(realRoot, real)) throw new PathOutsideRootError(relative);
return lexical;
}
/** Like resolveInside for an absolute path that must lie within root. */
export async function assertInside(root: string, absolute: string): Promise<string> {
if (!path.isAbsolute(absolute)) throw new PathOutsideRootError(absolute);
const realRoot = await realpath(root);
const relative = path.relative(realRoot, path.resolve(absolute));
return resolveInside(realRoot, relative === "" ? "." : relative);
}

View File

@ -1,5 +1,5 @@
import type { PlatformProfile, PlatformProfileSummary } from "@rakazo/contracts";
import { notImplemented } from "./not-implemented.js";
import { PlatformProfileSchema } from "@rakazo/contracts";
export const PROFILE_STALE_DAYS = 90;
@ -14,25 +14,65 @@ export interface ProfileSet {
}
/** Parse raw JSON documents; invalid ones become errors and are skipped whole. */
export function parseProfiles(_documents: Array<{ file: string; json: unknown }>): {
export function parseProfiles(documents: Array<{ file: string; json: unknown }>): {
profiles: PlatformProfile[];
errors: ProfileLoadError[];
} {
return notImplemented("parseProfiles");
const profiles: PlatformProfile[] = [];
const errors: ProfileLoadError[] = [];
const seen = new Set<string>();
for (const document of documents) {
const parsed = PlatformProfileSchema.safeParse(document.json);
if (!parsed.success) {
errors.push({
file: document.file,
message: parsed.error.issues
.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`)
.join("; "),
});
continue;
}
if (seen.has(parsed.data.id)) {
errors.push({ file: document.file, message: `duplicate profile id ${parsed.data.id}` });
continue;
}
seen.add(parsed.data.id);
profiles.push(parsed.data);
}
return { profiles, errors };
}
/** Private profiles override builtin ones with the same id. */
export function mergeProfiles(
_builtin: PlatformProfile[],
_private: PlatformProfile[],
builtin: PlatformProfile[],
privateProfiles: PlatformProfile[],
): ProfileSet {
return notImplemented("mergeProfiles");
const profiles: ProfileSet["profiles"] = new Map();
for (const profile of builtin) profiles.set(profile.id, { profile, origin: "builtin" });
for (const profile of privateProfiles) profiles.set(profile.id, { profile, origin: "private" });
return { profiles, errors: [] };
}
export function isProfileStale(profile: PlatformProfile, now: Date): boolean {
const reviewed = Date.parse(`${profile.reviewedAt}T00:00:00Z`);
return now.getTime() - reviewed > PROFILE_STALE_DAYS * 24 * 60 * 60 * 1000;
}
export function summarizeProfile(
_profile: PlatformProfile,
_origin: "builtin" | "private",
_now: Date,
profile: PlatformProfile,
origin: "builtin" | "private",
now: Date,
): PlatformProfileSummary {
return notImplemented("summarizeProfile");
return {
id: profile.id,
name: profile.name,
kind: profile.kind,
origin,
unverified: profile.unverified,
stale: isProfileStale(profile, now),
aspectRatios: profile.video.aspectRatios,
hardMinMs: profile.duration.hardMinMs,
hardMaxMs: profile.duration.hardMaxMs,
recommendedMs: profile.duration.recommended,
};
}

View File

@ -0,0 +1,144 @@
import { mkdir, mkdtemp, rm, symlink, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import {
cosineSimilarity,
decodeVectors,
encodeVectors,
haversineKm,
roundCoordinate,
} from "./geo.js";
import {
assertInside,
expandHome,
loadProfilesFromDir,
PathOutsideRootError,
resolveInside,
} from "./node.js";
import { mergeProfiles, parseProfiles, summarizeProfile } from "./profiles.js";
const profileJson = (id: string, overrides: Record<string, unknown> = {}) => ({
id,
name: `Profile ${id}`,
kind: "free_social",
reviewedAt: "2026-09-01",
video: {
aspectRatios: ["9:16"],
width: 1080,
height: 1920,
fps: 30,
codec: "h264",
maxBitrateKbps: 8000,
maxFileSizeMb: 500,
},
duration: { hardMinMs: 1000, hardMaxMs: 60000, recommended: [15000] },
safeZones: { topPct: 10, bottomPct: 20, leftPct: 5, rightPct: 10 },
captions: { recommended: true },
audio: {},
text: { captionMaxChars: 2200, hashtagLimit: 5, linkPolicy: "bio_only" },
content: { thresholds: { explicit_nudity: 0.2 } },
disclosure: { aiAlteredLabelRequired: true },
consent: { requirePerformerConsent: false, requireAgeVerification: false },
export: {},
...overrides,
});
describe("geo", () => {
it("measures distance and rounds coordinates", () => {
expect(haversineKm({ lat: 52.52, lon: 13.405 }, { lat: 48.137, lon: 11.575 })).toBeCloseTo(
504,
-1,
);
expect(haversineKm({ lat: 1, lon: 1 }, { lat: 1, lon: 1 })).toBe(0);
expect(roundCoordinate(52.51678)).toBe(52.52);
});
it("computes cosine similarity and round-trips vectors", () => {
expect(cosineSimilarity([1, 0], [1, 0])).toBe(1);
expect(cosineSimilarity([1, 0], [0, 1])).toBe(0);
expect(cosineSimilarity([0, 0], [1, 1])).toBe(0);
expect(() => cosineSimilarity([1], [1, 2])).toThrow();
const decoded = decodeVectors(
encodeVectors([
[1, 2.5],
[-3, 0],
]),
2,
);
expect(Array.from(decoded[1] ?? [])).toEqual([-3, 0]);
expect(() => decodeVectors(new Uint8Array(6), 2)).toThrow();
});
});
describe("profiles", () => {
it("skips invalid and duplicate documents whole", () => {
const result = parseProfiles([
{ file: "a.json", json: profileJson("a") },
{ file: "b.json", json: { id: "b" } },
{ file: "a2.json", json: profileJson("a") },
{ file: "c.json", json: profileJson("c", { duration: { hardMinMs: 9, hardMaxMs: 1 } }) },
]);
expect(result.profiles.map((p) => p.id)).toEqual(["a"]);
expect(result.errors.map((e) => e.file)).toEqual(["b.json", "a2.json", "c.json"]);
});
it("lets private profiles override builtin ones and flags stale ones", () => {
const builtin = parseProfiles([{ file: "x", json: profileJson("shared") }]).profiles;
const own = parseProfiles([
{ file: "y", json: profileJson("shared", { name: "Mine" }) },
]).profiles;
const merged = mergeProfiles(builtin, own);
expect(merged.profiles.get("shared")?.origin).toBe("private");
expect(merged.profiles.get("shared")?.profile.name).toBe("Mine");
const summary = summarizeProfile(builtin[0]!, "builtin", new Date("2027-01-15T00:00:00Z"));
expect(summary.stale).toBe(true);
expect(summarizeProfile(builtin[0]!, "builtin", new Date("2026-09-20T00:00:00Z")).stale).toBe(
false,
);
});
});
describe("node helpers", () => {
let root: string;
let outside: string;
beforeAll(async () => {
root = await mkdtemp(path.join(tmpdir(), "media-root-"));
outside = await mkdtemp(path.join(tmpdir(), "media-outside-"));
await mkdir(path.join(root, "a"));
await writeFile(path.join(root, "a", "f.txt"), "x");
await symlink(outside, path.join(root, "escape"));
await writeFile(path.join(root, "good.json"), JSON.stringify(profileJson("good")));
await writeFile(path.join(root, "broken.json"), "{");
});
afterAll(async () => {
await rm(root, { recursive: true, force: true });
await rm(outside, { recursive: true, force: true });
});
it("resolves paths inside the root and rejects escapes", async () => {
expect(await resolveInside(root, "a/f.txt")).toMatch(/a\/f\.txt$/);
expect(await resolveInside(root, "a/new/file.mp4")).toMatch(/a\/new\/file\.mp4$/);
await expect(resolveInside(root, "../x")).rejects.toBeInstanceOf(PathOutsideRootError);
await expect(resolveInside(root, "/etc/passwd")).rejects.toBeInstanceOf(PathOutsideRootError);
await expect(resolveInside(root, "escape/secret")).rejects.toBeInstanceOf(PathOutsideRootError);
await expect(resolveInside(root, "a/\0x")).rejects.toBeInstanceOf(PathOutsideRootError);
await expect(assertInside(root, path.join(outside, "x"))).rejects.toBeInstanceOf(
PathOutsideRootError,
);
expect(await assertInside(root, path.join(root, "a"))).toMatch(/\/a$/);
});
it("loads profile directories and reports broken files", async () => {
const loaded = await loadProfilesFromDir(root);
expect(loaded.profiles.map((p) => p.id)).toEqual(["good"]);
expect(loaded.errors.map((e) => e.file)).toEqual(["broken.json"]);
expect(await loadProfilesFromDir(path.join(root, "missing"))).toEqual({
profiles: [],
errors: [],
});
expect(expandHome("~/x", "/home/u")).toBe("/home/u/x");
});
});