fix p57
This commit is contained in:
64
RETRIEX_PATCH_57_SINGLE_GENRE_SOURCE_AUDIT_README.md
Normal file
64
RETRIEX_PATCH_57_SINGLE_GENRE_SOURCE_AUDIT_README.md
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# RetrieX Patch p57 - Single-Genre Source Audit / Legacy Cleanup Guard
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
|
||||||
|
Dieser Patch ist der naechste kleine Schritt nach p56. Er loescht noch keine Legacy-YAML-Werte, sondern haertet die zentrale Single-Genre-Pflegeflaeche fuer den spaeteren Cleanup.
|
||||||
|
|
||||||
|
Es bleibt strikt bei: eine Installation = ein Genre. Es gibt keine Multi-Genre-Umschaltung, keinen Tenant-Kontext und keinen Request-/Host-Resolver.
|
||||||
|
|
||||||
|
## Warum dieser Schritt
|
||||||
|
|
||||||
|
Nach p55/p56 sind viele fachliche Werte bevorzugt in `config/retriex/genre.yaml` gepflegt, waehrend die alten Pfade als Fallback bestehen bleiben. Damit diese Uebergangsphase kontrollierbar bleibt, muessen die in `genre.configuration_values.*.source_paths` dokumentierten Legacy-/Effective-Pfade selbst validierbar und auditierbar sein.
|
||||||
|
|
||||||
|
p57 macht diese Source-Path-Metadaten maschinenlesbar und sichtbar.
|
||||||
|
|
||||||
|
## Was geaendert wurde
|
||||||
|
|
||||||
|
- `GenreConfig` erhaelt `getConfigurationValueSourcePaths()`.
|
||||||
|
- `RetriexEffectiveConfigProvider` validiert nun auch die `source_paths` unter `genre.configuration_values` gegen bekannte effektive bzw. Legacy-Config-Pfade.
|
||||||
|
- `ConfigSourceAuditProvider` sammelt die Single-Genre-Source-Path-Metadaten fuer `mto:agent:config:audit-source --json`.
|
||||||
|
- `ConfigSourceAuditCommand --details` zeigt zusaetzlich eine Tabelle der Single-Genre-Source-Pfade.
|
||||||
|
|
||||||
|
## Nicht geaendert
|
||||||
|
|
||||||
|
- Keine Fachlogik-Aenderung.
|
||||||
|
- Keine neuen fachlichen Listen.
|
||||||
|
- Keine neuen Defaults.
|
||||||
|
- Keine Entfernung von Legacy-Fallback-Werten.
|
||||||
|
- Keine Shopware-Kriterien-, Ranking-, Retrieval- oder LLM-Verhaltensaenderung.
|
||||||
|
- Keine Multi-Genre-/Tenant-Loesung.
|
||||||
|
|
||||||
|
## Erwartetes Verhalten
|
||||||
|
|
||||||
|
Das Antwortverhalten bleibt unveraendert. Der Unterschied ist nur, dass die zentrale Genre-Konfiguration besser validiert und auditierbar ist.
|
||||||
|
|
||||||
|
Wenn spaeter ein falscher oder veralteter `source_paths`-Eintrag in `genre.yaml` entsteht, meldet `mto:agent:config:validate` eine Warnung statt den Fehler unbemerkt zu lassen.
|
||||||
|
|
||||||
|
## Geaenderte Dateien
|
||||||
|
|
||||||
|
- `src/Config/GenreConfig.php`
|
||||||
|
- `src/Config/RetriexEffectiveConfigProvider.php`
|
||||||
|
- `src/Config/ConfigSourceAuditProvider.php`
|
||||||
|
- `src/Command/ConfigSourceAuditCommand.php`
|
||||||
|
- `RETRIEX_PATCH_57_SINGLE_GENRE_SOURCE_AUDIT_README.md`
|
||||||
|
|
||||||
|
## Lokale Checks
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php -l src/Config/GenreConfig.php
|
||||||
|
php -l src/Config/RetriexEffectiveConfigProvider.php
|
||||||
|
php -l src/Config/ConfigSourceAuditProvider.php
|
||||||
|
php -l src/Command/ConfigSourceAuditCommand.php
|
||||||
|
```
|
||||||
|
|
||||||
|
YAML-Parsing fuer alle Dateien unter `config/retriex/*.yaml`.
|
||||||
|
|
||||||
|
Projektchecks nach dem Einspielen:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bin/console cache:clear
|
||||||
|
bin/console mto:agent:config:validate
|
||||||
|
bin/console mto:agent:regression:test
|
||||||
|
bin/console mto:agent:config:audit-source --details
|
||||||
|
bin/console mto:agent:config:audit-patterns --details
|
||||||
|
```
|
||||||
@@ -63,7 +63,9 @@ final class ConfigSourceAuditCommand extends Command
|
|||||||
['fallback_accessors_with_yaml' => (string) ($summary['fallback_accessors_with_yaml'] ?? 0)],
|
['fallback_accessors_with_yaml' => (string) ($summary['fallback_accessors_with_yaml'] ?? 0)],
|
||||||
['fallback_accessors_missing_yaml' => (string) ($summary['fallback_accessors_missing_yaml'] ?? 0)],
|
['fallback_accessors_missing_yaml' => (string) ($summary['fallback_accessors_missing_yaml'] ?? 0)],
|
||||||
['constructor_defaults' => (string) ($summary['constructor_defaults'] ?? 0)],
|
['constructor_defaults' => (string) ($summary['constructor_defaults'] ?? 0)],
|
||||||
['constructor_defaults_without_yaml_mapping' => (string) ($summary['constructor_defaults_without_yaml_mapping'] ?? 0)]
|
['constructor_defaults_without_yaml_mapping' => (string) ($summary['constructor_defaults_without_yaml_mapping'] ?? 0)],
|
||||||
|
['genre_value_paths_with_source_paths' => (string) ($summary['genre_value_paths_with_source_paths'] ?? 0)],
|
||||||
|
['genre_declared_source_paths' => (string) ($summary['genre_declared_source_paths'] ?? 0)]
|
||||||
);
|
);
|
||||||
|
|
||||||
$warnings = is_array($result['warnings'] ?? null) ? $result['warnings'] : [];
|
$warnings = is_array($result['warnings'] ?? null) ? $result['warnings'] : [];
|
||||||
@@ -98,5 +100,25 @@ final class ConfigSourceAuditCommand extends Command
|
|||||||
$io->section('Fallback accessors');
|
$io->section('Fallback accessors');
|
||||||
$io->table(['Class', 'Line', 'Key', 'YAML', 'Source'], $fallbackRows);
|
$io->table(['Class', 'Line', 'Key', 'YAML', 'Source'], $fallbackRows);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$genreSourceRows = [];
|
||||||
|
foreach (($result['genre_configuration_source_paths'] ?? []) as $valuePath => $sourcePaths) {
|
||||||
|
if (!is_string($valuePath) || !is_array($sourcePaths)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($sourcePaths as $sourcePath) {
|
||||||
|
if (!is_string($sourcePath)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$genreSourceRows[] = [$valuePath, $sourcePath];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($genreSourceRows !== []) {
|
||||||
|
$io->section('Single-genre configuration source paths');
|
||||||
|
$io->table(['Genre value path', 'Legacy/effective source path'], $genreSourceRows);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ final readonly class ConfigSourceAuditProvider
|
|||||||
public function audit(): array
|
public function audit(): array
|
||||||
{
|
{
|
||||||
$yamlPaths = $this->collectYamlParameterPaths();
|
$yamlPaths = $this->collectYamlParameterPaths();
|
||||||
|
$genreSourcePaths = $this->collectGenreConfigurationSourcePaths();
|
||||||
$fallbackAccessors = [];
|
$fallbackAccessors = [];
|
||||||
$constructorDefaults = [];
|
$constructorDefaults = [];
|
||||||
$phpConstants = [];
|
$phpConstants = [];
|
||||||
@@ -135,14 +136,30 @@ final readonly class ConfigSourceAuditProvider
|
|||||||
'fallback_accessors_missing_yaml' => count($missingYamlFallbacks),
|
'fallback_accessors_missing_yaml' => count($missingYamlFallbacks),
|
||||||
'constructor_defaults' => count($constructorDefaults),
|
'constructor_defaults' => count($constructorDefaults),
|
||||||
'constructor_defaults_without_yaml_mapping' => count($constructorPhpDefaults),
|
'constructor_defaults_without_yaml_mapping' => count($constructorPhpDefaults),
|
||||||
|
'genre_value_paths_with_source_paths' => count($genreSourcePaths),
|
||||||
|
'genre_declared_source_paths' => $this->countGenreDeclaredSourcePaths($genreSourcePaths),
|
||||||
],
|
],
|
||||||
'warnings' => $this->buildWarnings($missingYamlFallbacks, $phpOnlyConstants, $constructorPhpDefaults),
|
'warnings' => $this->buildWarnings($missingYamlFallbacks, $phpOnlyConstants, $constructorPhpDefaults),
|
||||||
'fallback_accessors' => $fallbackAccessors,
|
'fallback_accessors' => $fallbackAccessors,
|
||||||
'constructor_defaults' => $constructorDefaults,
|
'constructor_defaults' => $constructorDefaults,
|
||||||
'php_constants' => $phpConstants,
|
'php_constants' => $phpConstants,
|
||||||
|
'genre_configuration_source_paths' => $genreSourcePaths,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, string[]> $sourcePaths
|
||||||
|
*/
|
||||||
|
private function countGenreDeclaredSourcePaths(array $sourcePaths): int
|
||||||
|
{
|
||||||
|
$count = 0;
|
||||||
|
foreach ($sourcePaths as $paths) {
|
||||||
|
$count += count($paths);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $count;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return list<array<string, mixed>>
|
* @return list<array<string, mixed>>
|
||||||
*/
|
*/
|
||||||
@@ -158,6 +175,78 @@ final readonly class ConfigSourceAuditProvider
|
|||||||
return $files;
|
return $files;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, string[]>
|
||||||
|
*/
|
||||||
|
private function collectGenreConfigurationSourcePaths(): array
|
||||||
|
{
|
||||||
|
$file = $this->projectDir . '/config/retriex/genre.yaml';
|
||||||
|
if (!is_file($file)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$parsed = Yaml::parseFile($file);
|
||||||
|
if (!is_array($parsed)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$parameters = $parsed['parameters'] ?? [];
|
||||||
|
if (!is_array($parameters)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$genreConfig = $parameters['retriex.genre.config'] ?? [];
|
||||||
|
if (!is_array($genreConfig)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$configurationValues = $genreConfig['configuration_values'] ?? [];
|
||||||
|
if (!is_array($configurationValues)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$out = [];
|
||||||
|
$this->collectGenreSourcePathsRecursive($configurationValues, '', $out);
|
||||||
|
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<int|string, mixed> $value
|
||||||
|
* @param array<string, string[]> $out
|
||||||
|
*/
|
||||||
|
private function collectGenreSourcePathsRecursive(array $value, string $path, array &$out): void
|
||||||
|
{
|
||||||
|
$sourcePaths = $value['source_paths'] ?? null;
|
||||||
|
if (is_array($sourcePaths)) {
|
||||||
|
$clean = [];
|
||||||
|
foreach ($sourcePaths as $sourcePath) {
|
||||||
|
if (!is_string($sourcePath) || trim($sourcePath) === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sourcePath = trim($sourcePath);
|
||||||
|
if (!in_array($sourcePath, $clean, true)) {
|
||||||
|
$clean[] = $sourcePath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($clean !== [] && $path !== '') {
|
||||||
|
$out[$path] = $clean;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($value as $key => $child) {
|
||||||
|
if ($key === 'source_paths' || !is_string($key) || !is_array($child)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$childPath = $path === '' ? $key : $path . '.' . $key;
|
||||||
|
$this->collectGenreSourcePathsRecursive($child, $childPath, $out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array<string, true>
|
* @return array<string, true>
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -64,6 +64,25 @@ final class GenreConfig
|
|||||||
return is_array($groupValues) ? $groupValues : [];
|
return is_array($groupValues) ? $groupValues : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all declared legacy/effective source paths from the single-genre value surface.
|
||||||
|
*
|
||||||
|
* The returned map uses the configuration_values-relative value path as key and the
|
||||||
|
* declared source paths as value. This is metadata only; runtime value lookup still
|
||||||
|
* happens through the typed getValue* accessors.
|
||||||
|
*
|
||||||
|
* @return array<string, string[]>
|
||||||
|
*/
|
||||||
|
public function getConfigurationValueSourcePaths(): array
|
||||||
|
{
|
||||||
|
$values = $this->getConfigurationValues();
|
||||||
|
$out = [];
|
||||||
|
$this->collectSourcePaths($values, '', $out);
|
||||||
|
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a non-empty string list from the central single-genre value surface.
|
* Returns a non-empty string list from the central single-genre value surface.
|
||||||
*
|
*
|
||||||
@@ -153,6 +172,31 @@ final class GenreConfig
|
|||||||
return $this->config;
|
return $this->config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<int|string, mixed> $value
|
||||||
|
* @param array<string, string[]> $out
|
||||||
|
*/
|
||||||
|
private function collectSourcePaths(array $value, string $path, array &$out): void
|
||||||
|
{
|
||||||
|
$sourcePaths = $value['source_paths'] ?? null;
|
||||||
|
if (is_array($sourcePaths)) {
|
||||||
|
$clean = $this->uniqueStringList($sourcePaths);
|
||||||
|
if ($clean !== [] && $path !== '') {
|
||||||
|
$out[$path] = $clean;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($value as $key => $child) {
|
||||||
|
if ($key === 'source_paths' || !is_string($key) || !is_array($child)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$childPath = $path === '' ? $key : $path . '.' . $key;
|
||||||
|
$this->collectSourcePaths($child, $childPath, $out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private function string(string $path, string $fallback): string
|
private function string(string $path, string $fallback): string
|
||||||
{
|
{
|
||||||
$value = $this->value($path, $fallback);
|
$value = $this->value($path, $fallback);
|
||||||
|
|||||||
@@ -1179,6 +1179,18 @@ final readonly class RetriexEffectiveConfigProvider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach ($this->collectGenreConfigurationValueSourcePaths($configurationValues) as $valuePath => $sourcePaths) {
|
||||||
|
foreach ($sourcePaths as $sourcePath) {
|
||||||
|
if (!isset($flattened[$sourcePath])) {
|
||||||
|
$warnings[] = sprintf(
|
||||||
|
'genre.configuration_values.%s references unknown source path: %s.',
|
||||||
|
$valuePath,
|
||||||
|
$sourcePath
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach (array_keys($surface) as $group) {
|
foreach (array_keys($surface) as $group) {
|
||||||
if (!is_string($group) || $group === '') {
|
if (!is_string($group) || $group === '') {
|
||||||
continue;
|
continue;
|
||||||
@@ -1190,6 +1202,54 @@ final readonly class RetriexEffectiveConfigProvider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $configurationValues
|
||||||
|
* @return array<string, string[]>
|
||||||
|
*/
|
||||||
|
private function collectGenreConfigurationValueSourcePaths(array $configurationValues): array
|
||||||
|
{
|
||||||
|
$out = [];
|
||||||
|
$this->collectGenreSourcePathsRecursive($configurationValues, '', $out);
|
||||||
|
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<int|string, mixed> $value
|
||||||
|
* @param array<string, string[]> $out
|
||||||
|
*/
|
||||||
|
private function collectGenreSourcePathsRecursive(array $value, string $path, array &$out): void
|
||||||
|
{
|
||||||
|
$sourcePaths = $value['source_paths'] ?? null;
|
||||||
|
if (is_array($sourcePaths)) {
|
||||||
|
$clean = [];
|
||||||
|
foreach ($sourcePaths as $sourcePath) {
|
||||||
|
if (!is_string($sourcePath) || trim($sourcePath) === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sourcePath = trim($sourcePath);
|
||||||
|
if (!in_array($sourcePath, $clean, true)) {
|
||||||
|
$clean[] = $sourcePath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($clean !== [] && $path !== '') {
|
||||||
|
$out[$path] = $clean;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($value as $key => $child) {
|
||||||
|
if ($key === 'source_paths' || !is_string($key) || !is_array($child)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$childPath = $path === '' ? $key : $path . '.' . $key;
|
||||||
|
$this->collectGenreSourcePathsRecursive($child, $childPath, $out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<string, true> $paths
|
* @param array<string, true> $paths
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user