TenantAtlas/tests/Feature/Guards/DerivedStateConsumerAdoptionGuardTest.php
ahmido d98dc30520 feat: add request-scoped derived state memoization (#198)
## Summary
- add a request-scoped derived-state store with deterministic keying and freshness controls
- adopt the shared contract in ArtifactTruthPresenter, OperationUxPresenter, and RelatedNavigationResolver plus the covered Filament consumers
- add spec, plan, contracts, guardrails, and focused memoization and freshness test coverage for spec 167

## Verification
- vendor/bin/sail artisan test --compact tests/Feature/078/RelatedLinksOnDetailTest.php
- vendor/bin/sail artisan test --compact tests/Feature/078/ tests/Feature/Operations/TenantlessOperationRunViewerTest.php tests/Feature/Monitoring/OperationsCanonicalUrlsTest.php tests/Feature/Monitoring/OperationsTenantScopeTest.php tests/Feature/Verification/VerificationAuthorizationTest.php tests/Feature/Verification/VerificationReportViewerDbOnlyTest.php tests/Feature/Verification/VerificationReportRedactionTest.php tests/Feature/Verification/VerificationReportMissingOrMalformedTest.php tests/Feature/OpsUx/FailureSanitizationTest.php tests/Feature/OpsUx/CanonicalViewRunLinksTest.php
- vendor/bin/sail bin pint --dirty --format agent

## Notes
- Livewire v4.0+ compliance preserved
- provider registration remains in bootstrap/providers.php
- no Filament assets or panel registration changes
- no global-search behavior changes
- no destructive action behavior changes in this PR

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #198
2026-03-28 14:58:30 +00:00

202 lines
7.3 KiB
PHP

<?php
declare(strict_types=1);
use Symfony\Component\Yaml\Yaml;
use Tests\Support\OpsUx\SourceFileScanner;
it('keeps covered derived-state consumers on declared access paths without ad hoc caches', function (): void {
$root = SourceFileScanner::projectRoot();
$contractPath = $root.'/specs/167-derived-state-memoization/contracts/request-scoped-derived-state.logical.openapi.yaml';
/** @var array<string, mixed> $contract */
$contract = Yaml::parseFile($contractPath);
$declarations = $contract['x-derived-state-consumers'] ?? [];
expect($declarations)->toBeArray()->not->toBeEmpty();
$allowedFamilies = [
'artifact_truth',
'operation_ux_guidance',
'operation_ux_explanation',
'related_navigation_primary',
'related_navigation_detail',
'related_navigation_header',
];
$allowedAccessPatterns = ['row_safe', 'page_safe', 'direct_once'];
$allowedFreshnessPolicies = ['request_stable', 'invalidate_after_mutation', 'no_reuse'];
$cachePattern = '/static\s+array\s+\$[A-Za-z0-9_]*cache\b/i';
$violations = [];
foreach ($declarations as $index => $declaration) {
if (! is_array($declaration)) {
$violations[] = [
'surface' => 'contract',
'message' => sprintf('Declaration %d must be an object.', $index),
];
continue;
}
$surface = trim((string) ($declaration['surface'] ?? ''));
$family = trim((string) ($declaration['family'] ?? ''));
$variant = trim((string) ($declaration['variant'] ?? ''));
$accessPattern = trim((string) ($declaration['accessPattern'] ?? ''));
$freshnessPolicy = trim((string) ($declaration['freshnessPolicy'] ?? ''));
$scopeInputs = $declaration['scopeInputs'] ?? null;
$guardScope = $declaration['guardScope'] ?? null;
$requiredMarkers = array_values(array_filter(
$declaration['requiredMarkers'] ?? [],
static fn (mixed $marker): bool => is_string($marker) && trim($marker) !== '',
));
$maxOccurrences = $declaration['maxOccurrences'] ?? [];
if ($surface === '' || $variant === '') {
$violations[] = [
'surface' => $surface !== '' ? $surface : 'contract',
'message' => 'Each declaration must provide non-empty surface and variant values.',
];
}
if (! in_array($family, $allowedFamilies, true)) {
$violations[] = [
'surface' => $surface !== '' ? $surface : 'contract',
'message' => sprintf('Unsupported family "%s".', $family),
];
}
if (! in_array($accessPattern, $allowedAccessPatterns, true)) {
$violations[] = [
'surface' => $surface !== '' ? $surface : 'contract',
'message' => sprintf('Unsupported accessPattern "%s".', $accessPattern),
];
}
if (! in_array($freshnessPolicy, $allowedFreshnessPolicies, true)) {
$violations[] = [
'surface' => $surface !== '' ? $surface : 'contract',
'message' => sprintf('Unsupported freshnessPolicy "%s".', $freshnessPolicy),
];
}
if (! is_array($scopeInputs) || $scopeInputs === []) {
$violations[] = [
'surface' => $surface !== '' ? $surface : 'contract',
'message' => 'Each declaration must include at least one scope input.',
];
}
if (! is_array($guardScope) || $guardScope === []) {
$violations[] = [
'surface' => $surface !== '' ? $surface : 'contract',
'message' => 'Each declaration must include at least one guardScope path.',
];
continue;
}
foreach ($guardScope as $relativePath) {
$relativePath = trim((string) $relativePath);
$absolutePath = $root.'/'.$relativePath;
if ($relativePath === '' || ! is_file($absolutePath)) {
$violations[] = [
'surface' => $surface !== '' ? $surface : 'contract',
'message' => sprintf('Missing guardScope file "%s".', $relativePath),
];
continue;
}
$source = SourceFileScanner::read($absolutePath);
foreach ($requiredMarkers as $marker) {
if (str_contains($source, $marker)) {
continue;
}
$violations[] = [
'surface' => $surface,
'file' => SourceFileScanner::relativePath($absolutePath),
'message' => sprintf('Missing required marker "%s".', $marker),
];
}
if (preg_match($cachePattern, $source, $match, PREG_OFFSET_CAPTURE) === 1) {
$offset = $match[0][1];
$line = substr_count(substr($source, 0, is_int($offset) ? $offset : 0), "\n") + 1;
$violations[] = [
'surface' => $surface,
'file' => SourceFileScanner::relativePath($absolutePath),
'line' => $line,
'message' => 'Ad hoc local cache detected in guarded surface.',
'snippet' => SourceFileScanner::snippet($source, $line),
];
}
if (! is_array($maxOccurrences)) {
continue;
}
foreach ($maxOccurrences as $occurrenceRule) {
if (! is_array($occurrenceRule)) {
continue;
}
$needle = trim((string) ($occurrenceRule['needle'] ?? ''));
$max = (int) ($occurrenceRule['max'] ?? -1);
if ($needle === '' || $max < 0) {
continue;
}
$actual = substr_count($source, $needle);
if ($actual <= $max) {
continue;
}
$offset = strpos($source, $needle);
$line = $offset === false ? 1 : substr_count(substr($source, 0, $offset), "\n") + 1;
$violations[] = [
'surface' => $surface,
'file' => SourceFileScanner::relativePath($absolutePath),
'line' => $line,
'message' => sprintf('Found %d occurrences of "%s"; expected at most %d.', $actual, $needle, $max),
'snippet' => SourceFileScanner::snippet($source, $line),
];
}
}
}
if ($violations !== []) {
$messages = array_map(static function (array $violation): string {
$location = $violation['surface'] ?? 'contract';
if (isset($violation['file'])) {
$location .= ' @ '.$violation['file'];
}
if (isset($violation['line'])) {
$location .= ':'.$violation['line'];
}
$message = $location."\n".$violation['message'];
if (isset($violation['snippet'])) {
$message .= "\n".$violation['snippet'];
}
return $message;
}, $violations);
$this->fail(
"Derived-state consumer guard violations found:\n\n".implode("\n\n", $messages)
);
}
expect($violations)->toBe([]);
});