From b9161f219e05527e2630175dd7fcbb02b0c0372b Mon Sep 17 00:00:00 2001 From: Kosmos Date: Tue, 21 Apr 2026 12:54:48 +0000 Subject: [PATCH] Fix storage root fallback for references --- lang/de.json | 6 ++-- lang/en.json | 6 ++-- module.json | 2 +- scripts/core/finding-engine.js | 42 ++++++++++++++++++++++++-- tests/storage-root-resolution-test.mjs | 30 ++++++++++++++++++ 5 files changed, 76 insertions(+), 10 deletions(-) create mode 100644 tests/storage-root-resolution-test.mjs diff --git a/lang/de.json b/lang/de.json index 7a2f10d..dffbd1f 100644 --- a/lang/de.json +++ b/lang/de.json @@ -8,9 +8,9 @@ }, "Hero": { "Title": "Kosmos Storage Audit", - "Intro1": "Prüft Medienreferenzen und markiert primär benutzerrelevante Risiken in den Foundry-Roots {dataRoot} und {publicRoot}.", - "Intro2": "Orphans werden bewusst nicht global für den gesamten {dataRoot}-Root behauptet, sondern nur in klar weltlokalen oder explizit riskanten Bereichen.", - "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." + "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": "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": "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": { "Run": "Analyse starten", diff --git a/lang/en.json b/lang/en.json index bc6837c..680d31e 100644 --- a/lang/en.json +++ b/lang/en.json @@ -8,9 +8,9 @@ }, "Hero": { "Title": "Kosmos Storage Audit", - "Intro1": "Audits media references and highlights primarily user-relevant risks in the Foundry roots {dataRoot} and {publicRoot}.", - "Intro2": "Orphans are intentionally not claimed globally for the entire {dataRoot} root, but only in clearly world-local or explicitly risky areas.", - "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." + "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": "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": "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": { "Run": "Start Analysis", diff --git a/module.json b/module.json index 75355c0..ce008d9 100644 --- a/module.json +++ b/module.json @@ -2,7 +2,7 @@ "id": "kosmos-storage-audit", "title": "Kosmos Storage Audit", "description": "Analyzes media references and risky storage locations across Foundry data and public roots.", - "version": "0.0.18", + "version": "0.0.19", "compatibility": { "minimum": "13", "verified": "13" diff --git a/scripts/core/finding-engine.js b/scripts/core/finding-engine.js index 9730e5d..dc7a773 100644 --- a/scripts/core/finding-engine.js +++ b/scripts/core/finding-engine.js @@ -39,11 +39,12 @@ export function buildFindings({ files, references, i18n }={}) { 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 references) { + for (const reference of resolvedReferences) { const normalized = reference.normalized; if (!normalized) continue; if (normalized.targetKind === "wildcard") { @@ -56,7 +57,7 @@ export function buildFindings({ files, references, i18n }={}) { refsByLocator.set(normalizedLocator, bucket); } - for (const reference of references) { + for (const reference of resolvedReferences) { const normalized = reference.normalized; if (!normalized) continue; const normalizedLocator = createCanonicalLocator(normalized.storage, normalized.path); @@ -117,7 +118,7 @@ export function buildFindings({ files, references, i18n }={}) { if (detectMediaKind(file.path) === "other") continue; const refs = matchingReferencesForFile(file, refsByLocator, wildcardReferences); 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") ? "warning" @@ -139,6 +140,41 @@ export function buildFindings({ files, references, i18n }={}) { 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; diff --git a/tests/storage-root-resolution-test.mjs b/tests/storage-root-resolution-test.mjs new file mode 100644 index 0000000..3fcd86f --- /dev/null +++ b/tests/storage-root-resolution-test.mjs @@ -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");