This commit is contained in:
team 1
2026-05-01 17:40:48 +02:00
parent 17cb098235
commit ad7cac72be
19 changed files with 1084 additions and 157 deletions

View File

@@ -0,0 +1,216 @@
<?php
declare(strict_types=1);
namespace App\Config;
final class GovernanceConfig
{
/**
* @param array<string, mixed> $config
*/
public function __construct(private readonly array $config = [])
{
}
/** @return array<string, mixed> */
public function toArray(): array
{
return $this->config;
}
/** @return string[] */
public function getRegressionProtectedShortModelTokens(): array
{
return $this->requiredStringList('regression_baseline.protected_short_model_tokens');
}
/** @return string[] */
public function getRegressionProtectedMeasurementValues(): array
{
return $this->requiredStringList('regression_baseline.protected_measurement_values');
}
/** @return string[] */
public function getRegressionProtectedTechnicalPromptKeywords(): array
{
return $this->requiredStringList('regression_baseline.protected_technical_prompt_keywords');
}
/** @return string[] */
public function getRegressionTechnicalPriorityRequiredMarkers(): array
{
return $this->requiredStringList('regression_baseline.technical_priority_required_markers');
}
/** @return string[] */
public function getRegressionProtectedAccessoryPromptKeywords(): array
{
return $this->requiredStringList('regression_baseline.protected_accessory_prompt_keywords');
}
/** @return string[] */
public function getRegressionProtectedSearchRepairSpecificityTerms(): array
{
return $this->requiredStringList('regression_baseline.protected_search_repair_specificity_terms');
}
/** @return string[] */
public function getRegressionProtectedRetrievalReagentWords(): array
{
return $this->requiredStringList('regression_baseline.protected_retrieval_reagent_words');
}
/** @return array<string, string[]> */
public function getRegressionProtectedRetrievalDeviceWordGroups(): array
{
$value = $this->requiredValue('regression_baseline.protected_retrieval_device_word_groups');
if (!is_array($value)) {
throw $this->invalid('regression_baseline.protected_retrieval_device_word_groups', 'must be a map of string lists');
}
$out = [];
foreach ($value as $key => $item) {
if (is_string($key) && is_array($item)) {
$normalizedKey = trim($key);
$terms = $this->normalizeStringList($item);
if ($normalizedKey !== '' && $terms !== []) {
$out[$normalizedKey] = $terms;
}
continue;
}
// Backwards-compatible reader for the temporary p15/p15b list-of-groups shape.
if (is_array($item)) {
$groupKey = isset($item['key']) && is_scalar($item['key']) ? trim((string) $item['key']) : '';
$terms = $this->normalizeStringList($item['terms'] ?? []);
if ($groupKey !== '' && $terms !== []) {
$out[$groupKey] = $terms;
}
}
}
if ($out === []) {
throw $this->invalid('regression_baseline.protected_retrieval_device_word_groups', 'must contain at least one valid group');
}
return $out;
}
public function getRegressionShopPromptOriginalQuery(): string
{
return $this->requiredString('regression_baseline.shop_prompt_regression_original_query');
}
/** @return string[] */
public function getRegressionShopPromptRequiredOutputInstructionMarkers(): array
{
return $this->requiredStringList('regression_baseline.shop_prompt_required_output_instruction_markers');
}
/** @return string[] */
public function getRegressionShopQueryMetaGuardTerms(): array
{
return $this->requiredStringList('regression_baseline.shop_query_meta_guard_terms');
}
/** @return string[] */
public function getRegressionShopQueryContextFallbackFilterTerms(): array
{
return $this->requiredStringList('regression_baseline.shop_query_context_fallback_filter_terms');
}
/** @return string[] */
public function getVocabularyProtectedShortModelTokens(): array
{
return $this->requiredStringList('vocabulary.protected_short_model_tokens');
}
/** @return string[] */
public function getLanguageProtectedStopwordTerms(): array
{
return $this->requiredStringList('language.protected_stopword_terms');
}
private function requiredString(string $path): string
{
$value = $this->requiredValue($path);
if (!is_scalar($value)) {
throw $this->invalid($path, 'must be a scalar string');
}
$value = trim((string) $value);
if ($value === '') {
throw $this->invalid($path, 'must not be empty');
}
return $value;
}
/** @return string[] */
private function requiredStringList(string $path): array
{
return $this->nonEmptyStringList($path, $this->requiredValue($path));
}
/** @return string[] */
private function nonEmptyStringList(string $path, mixed $value): array
{
if (!is_array($value)) {
throw $this->invalid($path, 'must be a string list');
}
$out = $this->normalizeStringList($value);
if ($out === []) {
throw $this->invalid($path, 'must contain at least one value');
}
return $out;
}
/** @return string[] */
private function normalizeStringList(mixed $value): array
{
if (!is_array($value)) {
return [];
}
$out = [];
foreach ($value as $item) {
if (!is_scalar($item)) {
continue;
}
$item = trim((string) $item);
if ($item !== '' && !in_array($item, $out, true)) {
$out[] = $item;
}
}
return $out;
}
private function requiredValue(string $path): mixed
{
$value = $this->config;
foreach (explode('.', $path) as $segment) {
if (!is_array($value) || !array_key_exists($segment, $value)) {
throw $this->missing($path);
}
$value = $value[$segment];
}
return $value;
}
private function missing(string $path): \InvalidArgumentException
{
return new \InvalidArgumentException(sprintf('RetrieX governance config "%s" is missing.', $path));
}
private function invalid(string $path, string $reason): \InvalidArgumentException
{
return new \InvalidArgumentException(sprintf('RetrieX governance config "%s" %s.', $path, $reason));
}
}