Fix storage root fallback for references

This commit is contained in:
2026-04-21 12:54:48 +00:00
parent 27c69f7c1e
commit b9161f219e
5 changed files with 76 additions and 10 deletions

View File

@@ -8,9 +8,9 @@
}, },
"Hero": { "Hero": {
"Title": "Kosmos Storage Audit", "Title": "Kosmos Storage Audit",
"Intro1": "Prüft Medienreferenzen und markiert primär benutzerrelevante Risiken in den Foundry-Roots {dataRoot} und {publicRoot}.", "Intro1": "Prüft lokale Medienpfade aus Weltdokumenten, Paket-Packs und Manifesten gegen {dataRoot} und {publicRoot} und meldet nur broken-reference, non-package-to-package-reference und orphan-file.",
"Intro2": "Orphans werden bewusst nicht global für den gesamten {dataRoot}-Root behauptet, sondern nur in klar weltlokalen oder explizit riskanten Bereichen.", "Intro2": "Nicht gemeldet werden reguläre Weltverweise auf Modul- oder Systemassets, wenn das Ziel im Owner-Paket selbst sichtbar referenziert ist; Wildcards gelten als gültig, sobald mindestens eine Datei passt.",
"Intro3": "Weltverweise auf Modul- oder Systemassets gelten nicht pauschal als Problem. Relevant sind vor allem Ziele, die im Owner-Paket selbst nicht sichtbar referenziert werden." "Intro3": "orphan-file wird nur in weltlokalen oder explizit riskanten Bereichen gebildet, nie global für den gesamten {dataRoot}-Root; ChatMessages und andere flüchtige Quellen werden nicht geprüft."
}, },
"Action": { "Action": {
"Run": "Analyse starten", "Run": "Analyse starten",

View File

@@ -8,9 +8,9 @@
}, },
"Hero": { "Hero": {
"Title": "Kosmos Storage Audit", "Title": "Kosmos Storage Audit",
"Intro1": "Audits media references and highlights primarily user-relevant risks in the Foundry roots {dataRoot} and {publicRoot}.", "Intro1": "Checks local media paths from world documents, package packs, and manifests against {dataRoot} and {publicRoot}, and only reports broken-reference, non-package-to-package-reference, and orphan-file.",
"Intro2": "Orphans are intentionally not claimed globally for the entire {dataRoot} root, but only in clearly world-local or explicitly risky areas.", "Intro2": "Regular world references to module or system assets are not reported if the target is visibly referenced by its owning package; wildcards are treated as valid as soon as at least one file matches.",
"Intro3": "World references to module or system assets are not treated as a problem by default. The main focus is on targets that are not visibly referenced by their owning package." "Intro3": "orphan-file is only produced for world-local or explicitly risky areas, never globally for the entire {dataRoot} root; ChatMessages and other transient sources are excluded."
}, },
"Action": { "Action": {
"Run": "Start Analysis", "Run": "Start Analysis",

View File

@@ -2,7 +2,7 @@
"id": "kosmos-storage-audit", "id": "kosmos-storage-audit",
"title": "Kosmos Storage Audit", "title": "Kosmos Storage Audit",
"description": "Analyzes media references and risky storage locations across Foundry data and public roots.", "description": "Analyzes media references and risky storage locations across Foundry data and public roots.",
"version": "0.0.18", "version": "0.0.19",
"compatibility": { "compatibility": {
"minimum": "13", "minimum": "13",
"verified": "13" "verified": "13"

View File

@@ -39,11 +39,12 @@ export function buildFindings({ files, references, i18n }={}) {
path: file.path, path: file.path,
locator: createCanonicalLocator(file.storage, file.path) locator: createCanonicalLocator(file.storage, file.path)
})); }));
const resolvedReferences = references.map(reference => resolveReferenceTarget(reference, fileByLocator, fileLocators));
const refsByLocator = new Map(); const refsByLocator = new Map();
const wildcardReferences = []; const wildcardReferences = [];
const findings = []; const findings = [];
for (const reference of references) { for (const reference of resolvedReferences) {
const normalized = reference.normalized; const normalized = reference.normalized;
if (!normalized) continue; if (!normalized) continue;
if (normalized.targetKind === "wildcard") { if (normalized.targetKind === "wildcard") {
@@ -56,7 +57,7 @@ export function buildFindings({ files, references, i18n }={}) {
refsByLocator.set(normalizedLocator, bucket); refsByLocator.set(normalizedLocator, bucket);
} }
for (const reference of references) { for (const reference of resolvedReferences) {
const normalized = reference.normalized; const normalized = reference.normalized;
if (!normalized) continue; if (!normalized) continue;
const normalizedLocator = createCanonicalLocator(normalized.storage, normalized.path); const normalizedLocator = createCanonicalLocator(normalized.storage, normalized.path);
@@ -117,7 +118,7 @@ export function buildFindings({ files, references, i18n }={}) {
if (detectMediaKind(file.path) === "other") continue; if (detectMediaKind(file.path) === "other") continue;
const refs = matchingReferencesForFile(file, refsByLocator, wildcardReferences); const refs = matchingReferencesForFile(file, refsByLocator, wildcardReferences);
if (refs.length) continue; if (refs.length) continue;
if (!shouldReportOrphan(file, references)) continue; if (!shouldReportOrphan(file, resolvedReferences)) continue;
const severity = (file.riskClass === "package-module") || (file.riskClass === "package-system") || (file.riskClass === "release-public") const severity = (file.riskClass === "package-module") || (file.riskClass === "package-system") || (file.riskClass === "release-public")
? "warning" ? "warning"
@@ -139,6 +140,41 @@ export function buildFindings({ files, references, i18n }={}) {
return findings; 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) { function isAnchoredInOwningPackage(file, references) {
const ownerType = file.ownerHint.ownerType; const ownerType = file.ownerHint.ownerType;
const ownerId = file.ownerHint.ownerId; const ownerId = file.ownerHint.ownerId;

View File

@@ -0,0 +1,30 @@
import assert from "node:assert/strict";
import { buildFindings, createFileRecord } from "../scripts/core/finding-engine.js";
import { createFileLocator } from "../scripts/core/path-utils.js";
const files = [
createFileRecord(createFileLocator("data", "canvas/background_paper_16x9_4k.webp"), 1234)
];
const references = [
{
sourceType: "world-document",
sourceScope: { ownerType: "world", ownerId: "demo-world", systemId: "demo-system", subtype: "scenes" },
sourceLabel: "Scene demo",
rawValue: "canvas/background_paper_16x9_4k.webp",
normalized: {
...createFileLocator("public", "canvas/background_paper_16x9_4k.webp"),
targetKind: "local-file"
}
}
];
const findings = buildFindings({ files, references });
assert.equal(
findings.some(finding => finding.kind === "broken-reference" && finding.target.locator === "public:canvas/background_paper_16x9_4k.webp"),
false,
"references should be remapped to the existing storage root before reporting a broken target"
);
console.log("storage-root-resolution-test: ok");