Initial module scaffold
This commit is contained in:
154
scripts/core/finding-engine.js
Normal file
154
scripts/core/finding-engine.js
Normal file
@@ -0,0 +1,154 @@
|
||||
import { classifyRisk, 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 }) {
|
||||
const fileByLocator = new Map(files.map(file => [file.locator, file]));
|
||||
const refsByLocator = new Map();
|
||||
const findings = [];
|
||||
|
||||
for (const reference of references) {
|
||||
const normalized = reference.normalized;
|
||||
if (!normalized) continue;
|
||||
const bucket = refsByLocator.get(normalized.locator) ?? [];
|
||||
bucket.push(reference);
|
||||
refsByLocator.set(normalized.locator, bucket);
|
||||
}
|
||||
|
||||
for (const reference of references) {
|
||||
const normalized = reference.normalized;
|
||||
if (!normalized) continue;
|
||||
|
||||
const file = fileByLocator.get(normalized.locator);
|
||||
if (!file) {
|
||||
if (reference.sourceScope?.ownerType !== "world") continue;
|
||||
findings.push({
|
||||
kind: "broken-reference",
|
||||
severity: reference.sourceScope?.ownerType === "world" ? "high" : "warning",
|
||||
target: normalized,
|
||||
source: reference,
|
||||
reason: `Referenced file ${normalized.locator} does not exist in the scanned roots.`,
|
||||
recommendation: "Check whether the file was moved, deleted, or should be copied into a stable location.",
|
||||
confidence: "high"
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const ownerRelation = compareOwner(reference.sourceScope, file.ownerHint);
|
||||
if (ownerRelation === "non-package-to-package") {
|
||||
if (isAnchoredInOwningPackage(file, refsByLocator.get(normalized.locator) ?? [])) continue;
|
||||
findings.push({
|
||||
kind: "non-package-to-package-reference",
|
||||
severity: "high",
|
||||
target: normalized,
|
||||
source: reference,
|
||||
reason: `${reference.sourceScope.ownerType}:${reference.sourceScope.ownerId} references package-owned storage ${normalized.locator}, but the asset is not visibly referenced by its owning package.`,
|
||||
recommendation: "Review whether the file was manually placed into the package folder. If the package does not use it itself, prefer moving it into user-controlled storage.",
|
||||
confidence: "high"
|
||||
});
|
||||
} else if (ownerRelation === "risky-public") {
|
||||
if (reference.sourceScope?.ownerType !== "world") continue;
|
||||
findings.push({
|
||||
kind: "risky-public-reference",
|
||||
severity: "high",
|
||||
target: normalized,
|
||||
source: reference,
|
||||
reason: `${reference.sourceScope.ownerType}:${reference.sourceScope.ownerId} references release-public storage ${normalized.locator}.`,
|
||||
recommendation: "Prefer copying the asset into world or user-controlled storage.",
|
||||
confidence: "high"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const file of files) {
|
||||
if (detectMediaKind(file.path) === "other") continue;
|
||||
const refs = refsByLocator.get(file.locator) ?? [];
|
||||
if (refs.length) continue;
|
||||
if (!shouldReportOrphan(file, references)) 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: `No incoming media reference was found for ${file.locator}.`,
|
||||
recommendation: severity === "warning"
|
||||
? "Review whether the file is safe to remove or should be moved into a stable storage location."
|
||||
: "Review whether the file is intentionally kept as reserve content.",
|
||||
confidence: "medium"
|
||||
});
|
||||
}
|
||||
|
||||
return findings;
|
||||
}
|
||||
|
||||
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 shouldReportOrphan(file, references) {
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user