Files
kosmos-storage-audit/scripts/core/finding-engine.js

249 lines
9.7 KiB
JavaScript

import { classifyRisk, createCanonicalLocator, detectMediaKind, inferOwnerHint, isCorePublicPath, isStorageAreaPath } from "./path-utils.js";
function compareOwner(sourceScope, targetOwner) {
if (!sourceScope) return "unknown";
if ((sourceScope.ownerType === "world") && (targetOwner.ownerType === "system") && (sourceScope.systemId === targetOwner.ownerId)) {
return "same-system";
}
if ((sourceScope.ownerType === "world") && (targetOwner.ownerType === "public") &&
["cards", "icons", "nue", "sounds", "ui"].includes(targetOwner.ownerId)) {
return "core-public";
}
if ((sourceScope.ownerType === "module") && (targetOwner.ownerType === "module")) {
return sourceScope.ownerId === targetOwner.ownerId ? "same-package" : "foreign-module";
}
if ((sourceScope.ownerType === "system") && (targetOwner.ownerType === "system")) {
return sourceScope.ownerId === targetOwner.ownerId ? "same-package" : "cross-package";
}
if ((sourceScope.ownerType === "world") && (targetOwner.ownerType === "world")) {
return sourceScope.ownerId === targetOwner.ownerId ? "same-world" : "cross-world";
}
if ((sourceScope.ownerType === "world") && ((targetOwner.ownerType === "module") || (targetOwner.ownerType === "system"))) {
return "non-package-to-package";
}
if ((sourceScope.ownerType === "world") && (targetOwner.ownerType === "public")) {
return "risky-public";
}
if (((sourceScope.ownerType === "module") || (sourceScope.ownerType === "system")) &&
((targetOwner.ownerType === "module") || (targetOwner.ownerType === "system")) &&
((sourceScope.ownerType !== targetOwner.ownerType) || (sourceScope.ownerId !== targetOwner.ownerId))) {
return "cross-package";
}
return "allowed";
}
export function buildFindings({ files, references, i18n }={}) {
const fileByLocator = new Map(files.map(file => [createCanonicalLocator(file.storage, file.path), file]));
const fileLocators = files.map(file => ({
storage: file.storage,
path: file.path,
locator: createCanonicalLocator(file.storage, file.path)
}));
const resolvedReferences = references.map(reference => resolveReferenceTarget(reference, fileByLocator, fileLocators));
const refsByLocator = new Map();
const wildcardReferences = [];
const findings = [];
for (const reference of resolvedReferences) {
const normalized = reference.normalized;
if (!normalized) continue;
if (normalized.targetKind === "wildcard") {
wildcardReferences.push(reference);
continue;
}
const normalizedLocator = createCanonicalLocator(normalized.storage, normalized.path);
const bucket = refsByLocator.get(normalizedLocator) ?? [];
bucket.push(reference);
refsByLocator.set(normalizedLocator, bucket);
}
for (const reference of resolvedReferences) {
const normalized = reference.normalized;
if (!normalized) continue;
const normalizedLocator = createCanonicalLocator(normalized.storage, normalized.path);
const file = fileByLocator.get(normalizedLocator);
if (!file) {
if ((normalized.targetKind === "wildcard") && wildcardMatchesAny(normalized, fileLocators)) continue;
if (reference.sourceScope?.ownerType !== "world") continue;
findings.push({
kind: "broken-reference",
severity: reference.sourceScope?.ownerType === "world" ? "high" : "warning",
target: { ...normalized, locator: normalizedLocator },
source: reference,
reason: normalized.targetKind === "wildcard"
? format(i18n, "KSA.FindingReason.BrokenWildcard", { locator: normalized.locator })
: format(i18n, "KSA.FindingReason.BrokenReference", { locator: normalized.locator }),
recommendation: normalized.targetKind === "wildcard"
? format(i18n, "KSA.FindingRecommendation.CheckWildcard")
: format(i18n, "KSA.FindingRecommendation.CheckMissingFile"),
confidence: "high"
});
continue;
}
const ownerRelation = compareOwner(reference.sourceScope, file.ownerHint);
if (ownerRelation === "non-package-to-package") {
if (isAnchoredInOwningPackage(file, matchingReferencesForFile(file, refsByLocator, wildcardReferences))) continue;
findings.push({
kind: "non-package-to-package-reference",
severity: "high",
target: { ...normalized, locator: normalizedLocator },
source: reference,
reason: format(i18n, "KSA.FindingReason.UnanchoredPackageTarget", {
sourceOwner: `${reference.sourceScope.ownerType}:${reference.sourceScope.ownerId}`,
locator: normalized.locator
}),
recommendation: format(i18n, "KSA.FindingRecommendation.MoveToStableStorage"),
confidence: "high"
});
} else if (ownerRelation === "risky-public") {
if (reference.sourceScope?.ownerType !== "world") continue;
findings.push({
kind: "risky-public-reference",
severity: "high",
target: { ...normalized, locator: normalizedLocator },
source: reference,
reason: format(i18n, "KSA.FindingReason.RiskyPublicTarget", {
sourceOwner: `${reference.sourceScope.ownerType}:${reference.sourceScope.ownerId}`,
locator: normalized.locator
}),
recommendation: format(i18n, "KSA.FindingRecommendation.CopyToStableStorage"),
confidence: "high"
});
}
}
for (const file of files) {
if (detectMediaKind(file.path) === "other") continue;
const refs = matchingReferencesForFile(file, refsByLocator, wildcardReferences);
if (refs.length) continue;
if (!shouldReportOrphan(file, resolvedReferences)) continue;
const severity = (file.riskClass === "package-module") || (file.riskClass === "package-system") || (file.riskClass === "release-public")
? "warning"
: "info";
findings.push({
kind: "orphan-file",
severity,
target: file,
source: null,
reason: format(i18n, "KSA.FindingReason.OrphanFile", { locator: file.locator }),
recommendation: severity === "warning"
? format(i18n, "KSA.FindingRecommendation.ReviewOrMoveOrphan")
: format(i18n, "KSA.FindingRecommendation.KeepAsReserve"),
confidence: "medium"
});
}
return findings;
}
function resolveReferenceTarget(reference, fileByLocator, fileLocators) {
const normalized = reference.normalized;
if (!normalized) return reference;
const currentLocator = createCanonicalLocator(normalized.storage, normalized.path);
if (fileByLocator.has(currentLocator)) return reference;
const alternateStorage = normalized.storage === "data"
? "public"
: normalized.storage === "public"
? "data"
: null;
if (!alternateStorage) return reference;
const alternateLocator = createCanonicalLocator(alternateStorage, normalized.path);
if (normalized.targetKind === "wildcard") {
const alternateTarget = { ...normalized, storage: alternateStorage, locator: alternateLocator };
if (!wildcardMatchesAny(alternateTarget, fileLocators)) return reference;
return {
...reference,
normalized: alternateTarget
};
}
if (!fileByLocator.has(alternateLocator)) return reference;
return {
...reference,
normalized: {
...normalized,
storage: alternateStorage,
locator: alternateLocator
}
};
}
function isAnchoredInOwningPackage(file, references) {
const ownerType = file.ownerHint.ownerType;
const ownerId = file.ownerHint.ownerId;
if (!["module", "system"].includes(ownerType) || !ownerId) return false;
return references.some(reference =>
(reference.sourceScope?.ownerType === ownerType) &&
(reference.sourceScope?.ownerId === ownerId)
);
}
function matchingReferencesForFile(file, refsByLocator, wildcardReferences) {
const exactRefs = refsByLocator.get(createCanonicalLocator(file.storage, file.path)) ?? [];
const matchingWildcardRefs = wildcardReferences.filter(reference => wildcardMatchesFile(reference.normalized, file));
return [...exactRefs, ...matchingWildcardRefs];
}
function wildcardMatchesAny(target, fileLocators) {
const matcher = wildcardToRegExp(target.path);
return fileLocators.some(file => file.storage === target.storage && matcher.test(file.path));
}
function wildcardMatchesFile(target, file) {
if (!target || (target.targetKind !== "wildcard")) return false;
if (target.storage !== file.storage) return false;
return wildcardToRegExp(target.path).test(file.path);
}
function wildcardToRegExp(pattern) {
const escaped = String(pattern)
.replace(/[.+^${}()|\\]/g, "\\$&")
.replace(/\*/g, ".*")
.replace(/\?/g, ".");
return new RegExp(`^${escaped}$`, "u");
}
function shouldReportOrphan(file, references) {
if (isDerivedSceneThumbnail(file)) return false;
if (file.riskClass === "stable-world") {
const worldId = file.ownerHint.ownerId;
return references.some(reference => reference.sourceScope?.ownerType === "world" && reference.sourceScope.ownerId === worldId);
}
if ((file.riskClass === "package-module") || (file.riskClass === "package-system")) {
return isStorageAreaPath(file);
}
if (file.riskClass === "release-public") {
return !isCorePublicPath(file);
}
return false;
}
function isDerivedSceneThumbnail(file) {
const path = String(file.path ?? "");
return /^worlds\/[^/]+\/assets\/scenes\/[^/]+-thumb\.(?:png|webp)$/u.test(path);
}
export function createFileRecord(locator, size = null) {
return {
...locator,
basename: locator.path.split("/").pop() ?? locator.path,
extension: locator.path.includes(".") ? locator.path.split(".").pop().toLowerCase() : "",
size,
mediaKind: detectMediaKind(locator.path),
ownerHint: inferOwnerHint(locator),
riskClass: classifyRisk(locator),
exists: true
};
}
function format(i18n, key, data = {}) {
return i18n?.format?.(key, data) ?? key;
}