Release 0.0.7

This commit is contained in:
2026-04-20 21:13:27 +00:00
parent 456c656750
commit e998784ac6
6 changed files with 92 additions and 39 deletions

View File

@@ -4,19 +4,19 @@
"Menu": { "Menu": {
"Name": "Kosmos Storage Audit", "Name": "Kosmos Storage Audit",
"Label": "Report öffnen", "Label": "Report öffnen",
"Hint": "Die Storage-Analyse ausführen und priorisierte Findings prüfen." "Hint": "Die Speicher-Analyse ausführen und priorisierte Funde prüfen."
}, },
"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 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.", "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 owning Paket selbst nicht sichtbar referenziert werden." "Intro3": "Weltverweise auf Modul- oder Systemassets gelten nicht pauschal als Problem. Relevant sind vor allem Ziele, die im besitzenden Paket selbst nicht sichtbar referenziert werden."
}, },
"Action": { "Action": {
"Run": "Analyse starten", "Run": "Analyse starten",
"Running": "Analysiere...", "Running": "Analysiere...",
"Export": "Report exportieren", "Export": "Bericht exportieren",
"ShowAll": "Alle Findings", "ShowAll": "Alle Funde",
"ShowWarnings": "Nur Warnungen", "ShowWarnings": "Nur Warnungen",
"ShowRaw": "Einzelfälle anzeigen", "ShowRaw": "Einzelfälle anzeigen",
"HideRaw": "Einzelfälle ausblenden" "HideRaw": "Einzelfälle ausblenden"
@@ -24,16 +24,20 @@
"Progress": { "Progress": {
"Initialize": "Initialisiere Analyse", "Initialize": "Initialisiere Analyse",
"Analyzing": "Analysiere", "Analyzing": "Analysiere",
"ScanFiles": "Scanne Dateien",
"ReadReferences": "Lese Referenzen",
"ClassifyFindings": "Klassifiziere Funde",
"Completed": "Analyse abgeschlossen",
"Current": "Aktuell: {source}", "Current": "Aktuell: {source}",
"Files": "Dateien: {count}", "Files": "Dateien: {count}",
"Sources": "Quellen: {count}", "Sources": "Quellen: {count}",
"References": "Referenzen: {count}", "References": "Referenzen: {count}",
"Findings": "Findings: {count}", "Findings": "Funde: {count}",
"BrowseStorage": "Durchsuche {storage}:{path}", "BrowseStorage": "Durchsuche {storage}:{path}",
"ReadPack": "Lese Paket-Pack {pack}" "ReadPack": "Lese Paket-Pack {pack}"
}, },
"Notify": { "Notify": {
"Completed": "Kosmos Storage Audit abgeschlossen: {count} Findings.", "Completed": "Kosmos Storage Audit abgeschlossen: {count} Funde.",
"Failed": "Kosmos Storage Audit fehlgeschlagen: {message}", "Failed": "Kosmos Storage Audit fehlgeschlagen: {message}",
"OpenSourceFailed": "Die Quelle konnte nicht geöffnet werden: {uuid}" "OpenSourceFailed": "Die Quelle konnte nicht geöffnet werden: {uuid}"
}, },
@@ -41,11 +45,11 @@
"NoAnalysis": "Noch keine Analyse ausgeführt.", "NoAnalysis": "Noch keine Analyse ausgeführt.",
"Files": "Dateien", "Files": "Dateien",
"References": "Referenzen", "References": "Referenzen",
"Findings": "Findings", "Findings": "Funde",
"High": "High", "High": "Hoch",
"Warning": "Warning", "Warning": "Warnung",
"ByType": "Findings nach Typ", "ByType": "Funde nach Typ",
"NoFindings": "Keine Findings", "NoFindings": "Keine Funde",
"WorkBlocks": "Arbeitsblöcke", "WorkBlocks": "Arbeitsblöcke",
"MissingTargets": "Deduplizierte fehlende Ziele", "MissingTargets": "Deduplizierte fehlende Ziele",
"UnanchoredPackageTargets": "Unverankerte Paketziele", "UnanchoredPackageTargets": "Unverankerte Paketziele",
@@ -53,21 +57,21 @@
}, },
"Section": { "Section": {
"WorkView": "Arbeitsansicht", "WorkView": "Arbeitsansicht",
"NoGrouped": "Keine gruppierten {scope}Findings vorhanden.", "NoGrouped": "Keine gruppierten {scope}Funde vorhanden.",
"NoPrompt": "Die Analyse kann direkt aus dieser Ansicht gestartet werden.", "NoPrompt": "Die Analyse kann direkt aus dieser Ansicht gestartet werden.",
"Running": "Analyse läuft...", "Running": "Analyse läuft...",
"NoRaw": "Keine {scope}Findings gefunden.", "NoRaw": "Keine {scope}Funde gefunden.",
"RawEntries": "Einzelfälle", "RawEntries": "Einzelfälle",
"Samples": "Beispiele", "Samples": "Beispiele",
"UnanchoredPackageTargets": "Unverankerte Paketziele", "UnanchoredPackageTargets": "Unverankerte Paketziele",
"UnanchoredPackageTargetsDesc": "Diese Ziele liegen in Modul- oder Systemordnern, werden aus Weltdaten referenziert, sind im owning Paket selbst aber derzeit nicht als Referenz sichtbar.", "UnanchoredPackageTargetsDesc": "Diese Ziele liegen in Modul- oder Systemordnern, werden aus Weltdaten referenziert, sind im besitzenden Paket selbst aber derzeit nicht als Referenz sichtbar.",
"BrokenTargets": "Kaputte Ziele", "BrokenTargets": "Kaputte Ziele",
"BrokenTargetsDesc": "Diese Dateien fehlen, werden aber weiterhin referenziert.", "BrokenTargetsDesc": "Diese Dateien fehlen, werden aber weiterhin referenziert.",
"OrphanCandidates": "Orphan-Kandidaten", "OrphanCandidates": "Orphan-Kandidaten",
"OrphanCandidatesDesc": "Diese Dateien haben im aktuellen Analysekontext keine eingehende Referenz." "OrphanCandidatesDesc": "Diese Dateien haben im aktuellen Analysekontext keine eingehende Referenz."
}, },
"Field": { "Field": {
"OwnerPackage": "Owner-Paket", "OwnerPackage": "Besitzendes Paket",
"Assessment": "Bewertung", "Assessment": "Bewertung",
"Target": "Ziel", "Target": "Ziel",
"Source": "Quelle" "Source": "Quelle"
@@ -83,10 +87,25 @@
"WildcardNoMatch": "Musterreferenz: Wildcard wird von Foundry unterstützt, derzeit aber ohne passenden Treffer im Dateisystem." "WildcardNoMatch": "Musterreferenz: Wildcard wird von Foundry unterstützt, derzeit aber ohne passenden Treffer im Dateisystem."
}, },
"Severity": { "Severity": {
"high": "High", "high": "Hoch",
"warning": "Warning", "warning": "Warnung",
"info": "Info" "info": "Info"
}, },
"FindingReason": {
"BrokenWildcard": "Die Wildcard-Referenz {locator} passt auf keine Datei in den gescannten Roots.",
"BrokenReference": "Die referenzierte Datei {locator} existiert in den gescannten Roots nicht.",
"UnanchoredPackageTarget": "{sourceOwner} verweist auf paketgebundenen Speicher {locator}, aber das Asset ist im besitzenden Paket selbst nicht sichtbar referenziert.",
"RiskyPublicTarget": "{sourceOwner} verweist auf release-nahen Public-Speicher {locator}.",
"OrphanFile": "Für {locator} wurde keine eingehende Medienreferenz gefunden."
},
"FindingRecommendation": {
"CheckWildcard": "Prüfe, ob das Wildcard-Muster noch korrekt ist und ob passende Dateien noch vorhanden sind.",
"CheckMissingFile": "Prüfe, ob die Datei verschoben, gelöscht oder an einen stabilen Ort kopiert werden sollte.",
"MoveToStableStorage": "Prüfe, ob die Datei manuell in den Paketordner gelegt wurde. Falls das Paket sie selbst nicht nutzt, verschiebe sie bevorzugt in benutzerkontrollierten Speicher.",
"CopyToStableStorage": "Kopiere das Asset bevorzugt in Welt- oder benutzerkontrollierten Speicher.",
"ReviewOrMoveOrphan": "Prüfe, ob die Datei sicher entfernt oder an einen stabilen Speicherort verschoben werden sollte.",
"KeepAsReserve": "Prüfe, ob die Datei absichtlich als Reserveinhalt vorgehalten wird."
},
"Owner": { "Owner": {
"Unknown": "unbekannt" "Unknown": "unbekannt"
}, },

View File

@@ -24,6 +24,10 @@
"Progress": { "Progress": {
"Initialize": "Initializing analysis", "Initialize": "Initializing analysis",
"Analyzing": "Analyzing", "Analyzing": "Analyzing",
"ScanFiles": "Scanning files",
"ReadReferences": "Reading references",
"ClassifyFindings": "Classifying findings",
"Completed": "Analysis complete",
"Current": "Current: {source}", "Current": "Current: {source}",
"Files": "Files: {count}", "Files": "Files: {count}",
"Sources": "Sources: {count}", "Sources": "Sources: {count}",
@@ -87,6 +91,21 @@
"warning": "Warning", "warning": "Warning",
"info": "Info" "info": "Info"
}, },
"FindingReason": {
"BrokenWildcard": "Wildcard reference {locator} did not match any files in the scanned roots.",
"BrokenReference": "Referenced file {locator} does not exist in the scanned roots.",
"UnanchoredPackageTarget": "{sourceOwner} references package-owned storage {locator}, but the asset is not visibly referenced by its owning package.",
"RiskyPublicTarget": "{sourceOwner} references release-public storage {locator}.",
"OrphanFile": "No incoming media reference was found for {locator}."
},
"FindingRecommendation": {
"CheckWildcard": "Check whether the wildcard pattern is still correct and whether matching files still exist.",
"CheckMissingFile": "Check whether the file was moved, deleted, or should be copied into a stable location.",
"MoveToStableStorage": "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.",
"CopyToStableStorage": "Prefer copying the asset into world or user-controlled storage.",
"ReviewOrMoveOrphan": "Review whether the file is safe to remove or should be moved into a stable storage location.",
"KeepAsReserve": "Review whether the file is intentionally kept as reserve content."
},
"Owner": { "Owner": {
"Unknown": "unknown" "Unknown": "unknown"
}, },

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.6", "version": "0.0.7",
"compatibility": { "compatibility": {
"minimum": "13", "minimum": "13",
"verified": "13" "verified": "13"

View File

@@ -111,7 +111,8 @@ export async function runRuntimeAnalysis({ onProgress }={}) {
return analyzeStorage({ return analyzeStorage({
listFiles: () => listFoundryFiles(onProgress), listFiles: () => listFoundryFiles(onProgress),
listSources: () => listFoundrySources(onProgress), listSources: () => listFoundrySources(onProgress),
onProgress onProgress,
i18n: { format }
}); });
} }

View File

@@ -2,24 +2,24 @@ import { buildFindings, createFileRecord } from "./finding-engine.js";
import { extractReferencesFromValue } from "./reference-extractor.js"; import { extractReferencesFromValue } from "./reference-extractor.js";
import { createFileLocator } from "./path-utils.js"; import { createFileLocator } from "./path-utils.js";
export async function analyzeStorage({ listFiles, listSources, onProgress }={}) { export async function analyzeStorage({ listFiles, listSources, onProgress, i18n }={}) {
const files = []; const files = [];
let fileCount = 0; let fileCount = 0;
let sourceCount = 0; let sourceCount = 0;
let referenceCount = 0; let referenceCount = 0;
onProgress?.({ phase: "files", label: "Scanne Dateien", files: 0, sources: 0, references: 0 }); onProgress?.({ phase: "files", label: format(i18n, "KSA.Progress.ScanFiles"), files: 0, sources: 0, references: 0 });
for await (const entry of listFiles()) { for await (const entry of listFiles()) {
files.push(createFileRecord(createFileLocator(entry.storage, entry.path), entry.size ?? null)); files.push(createFileRecord(createFileLocator(entry.storage, entry.path), entry.size ?? null));
fileCount += 1; fileCount += 1;
if ((fileCount % 100) === 0) { if ((fileCount % 100) === 0) {
onProgress?.({ phase: "files", label: "Scanne Dateien", files: fileCount, sources: sourceCount, references: referenceCount }); onProgress?.({ phase: "files", label: format(i18n, "KSA.Progress.ScanFiles"), files: fileCount, sources: sourceCount, references: referenceCount });
await yieldToUI(); await yieldToUI();
} }
} }
const references = []; const references = [];
onProgress?.({ phase: "sources", label: "Lese Referenzen", files: fileCount, sources: 0, references: 0 }); onProgress?.({ phase: "sources", label: format(i18n, "KSA.Progress.ReadReferences"), files: fileCount, sources: 0, references: 0 });
for await (const source of listSources()) { for await (const source of listSources()) {
sourceCount += 1; sourceCount += 1;
const extracted = extractReferencesFromValue(source.value, { const extracted = extractReferencesFromValue(source.value, {
@@ -34,7 +34,7 @@ export async function analyzeStorage({ listFiles, listSources, onProgress }={})
if ((sourceCount % 50) === 0) { if ((sourceCount % 50) === 0) {
onProgress?.({ onProgress?.({
phase: "sources", phase: "sources",
label: "Lese Referenzen", label: format(i18n, "KSA.Progress.ReadReferences"),
files: fileCount, files: fileCount,
sources: sourceCount, sources: sourceCount,
references: referenceCount, references: referenceCount,
@@ -44,11 +44,11 @@ export async function analyzeStorage({ listFiles, listSources, onProgress }={})
} }
} }
onProgress?.({ phase: "findings", label: "Klassifiziere Findings", files: fileCount, sources: sourceCount, references: referenceCount }); onProgress?.({ phase: "findings", label: format(i18n, "KSA.Progress.ClassifyFindings"), files: fileCount, sources: sourceCount, references: referenceCount });
const findings = buildFindings({ files, references }); const findings = buildFindings({ files, references, i18n });
onProgress?.({ onProgress?.({
phase: "done", phase: "done",
label: "Analyse abgeschlossen", label: format(i18n, "KSA.Progress.Completed"),
files: fileCount, files: fileCount,
sources: sourceCount, sources: sourceCount,
references: referenceCount, references: referenceCount,
@@ -60,3 +60,7 @@ export async function analyzeStorage({ listFiles, listSources, onProgress }={})
async function yieldToUI() { async function yieldToUI() {
await new Promise(resolve => setTimeout(resolve, 0)); await new Promise(resolve => setTimeout(resolve, 0));
} }
function format(i18n, key, data = {}) {
return i18n?.format?.(key, data) ?? key;
}

View File

@@ -32,7 +32,7 @@ function compareOwner(sourceScope, targetOwner) {
return "allowed"; return "allowed";
} }
export function buildFindings({ files, references }) { export function buildFindings({ files, references, i18n }={}) {
const fileByLocator = new Map(files.map(file => [createCanonicalLocator(file.storage, file.path), file])); const fileByLocator = new Map(files.map(file => [createCanonicalLocator(file.storage, file.path), file]));
const fileLocators = files.map(file => ({ const fileLocators = files.map(file => ({
storage: file.storage, storage: file.storage,
@@ -71,11 +71,11 @@ export function buildFindings({ files, references }) {
target: { ...normalized, locator: normalizedLocator }, target: { ...normalized, locator: normalizedLocator },
source: reference, source: reference,
reason: normalized.targetKind === "wildcard" reason: normalized.targetKind === "wildcard"
? `Wildcard reference ${normalized.locator} did not match any files in the scanned roots.` ? format(i18n, "KSA.FindingReason.BrokenWildcard", { locator: normalized.locator })
: `Referenced file ${normalized.locator} does not exist in the scanned roots.`, : format(i18n, "KSA.FindingReason.BrokenReference", { locator: normalized.locator }),
recommendation: normalized.targetKind === "wildcard" recommendation: normalized.targetKind === "wildcard"
? "Check whether the wildcard pattern is still correct and whether matching files still exist." ? format(i18n, "KSA.FindingRecommendation.CheckWildcard")
: "Check whether the file was moved, deleted, or should be copied into a stable location.", : format(i18n, "KSA.FindingRecommendation.CheckMissingFile"),
confidence: "high" confidence: "high"
}); });
continue; continue;
@@ -89,8 +89,11 @@ export function buildFindings({ files, references }) {
severity: "high", severity: "high",
target: { ...normalized, locator: normalizedLocator }, target: { ...normalized, locator: normalizedLocator },
source: reference, 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.`, reason: format(i18n, "KSA.FindingReason.UnanchoredPackageTarget", {
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.", sourceOwner: `${reference.sourceScope.ownerType}:${reference.sourceScope.ownerId}`,
locator: normalized.locator
}),
recommendation: format(i18n, "KSA.FindingRecommendation.MoveToStableStorage"),
confidence: "high" confidence: "high"
}); });
} else if (ownerRelation === "risky-public") { } else if (ownerRelation === "risky-public") {
@@ -100,8 +103,11 @@ export function buildFindings({ files, references }) {
severity: "high", severity: "high",
target: { ...normalized, locator: normalizedLocator }, target: { ...normalized, locator: normalizedLocator },
source: reference, source: reference,
reason: `${reference.sourceScope.ownerType}:${reference.sourceScope.ownerId} references release-public storage ${normalized.locator}.`, reason: format(i18n, "KSA.FindingReason.RiskyPublicTarget", {
recommendation: "Prefer copying the asset into world or user-controlled storage.", sourceOwner: `${reference.sourceScope.ownerType}:${reference.sourceScope.ownerId}`,
locator: normalized.locator
}),
recommendation: format(i18n, "KSA.FindingRecommendation.CopyToStableStorage"),
confidence: "high" confidence: "high"
}); });
} }
@@ -122,10 +128,10 @@ export function buildFindings({ files, references }) {
severity, severity,
target: file, target: file,
source: null, source: null,
reason: `No incoming media reference was found for ${file.locator}.`, reason: format(i18n, "KSA.FindingReason.OrphanFile", { locator: file.locator }),
recommendation: severity === "warning" recommendation: severity === "warning"
? "Review whether the file is safe to remove or should be moved into a stable storage location." ? format(i18n, "KSA.FindingRecommendation.ReviewOrMoveOrphan")
: "Review whether the file is intentionally kept as reserve content.", : format(i18n, "KSA.FindingRecommendation.KeepAsReserve"),
confidence: "medium" confidence: "medium"
}); });
} }
@@ -194,3 +200,7 @@ export function createFileRecord(locator, size = null) {
exists: true exists: true
}; };
} }
function format(i18n, key, data = {}) {
return i18n?.format?.(key, data) ?? key;
}