## 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
5.4 KiB
5.4 KiB
Research: Request-Scoped Derived State and Resolver Memoization
Decision 1: Use a dedicated request-scoped in-memory store bound through the Laravel container
- Decision: Introduce one dedicated request-scoped derived-state store with request-local lifecycle semantics instead of static arrays or persistent cache stores.
- Rationale: The feature needs explicit reuse within one HTTP or Livewire request and explicit isolation across requests. A request-local container binding makes that boundary visible and testable while avoiding new persistence and avoiding cross-request staleness.
- Alternatives considered:
- Static caches inside presenters or resources: rejected because they hide scope boundaries, duplicate behavior across families, and make invalidation inconsistent.
Cache::remember()or Redis-backed caching: rejected because the spec explicitly excludes cross-request caching and because stale semantic reuse would become much harder to reason about.
Decision 2: Key derivations by family, record identity, variant, and scope-sensitive context
- Decision: Define one deterministic key contract that includes the derived-state family, stable record identity, variant or surface mode, and any workspace, tenant, or visibility-sensitive context required to produce the correct result.
- Rationale: Existing repeated work happens because the same deterministic question is asked multiple times. Correct reuse therefore depends on a stable definition of “same question.” Model-object identity alone is insufficient because the same record can appear through different model instances or under different scopes.
- Alternatives considered:
- Model ID only: rejected because it cannot distinguish list vs detail variants or capability-sensitive outputs.
spl_object_hash()only: rejected because it prevents convergence across separate model instances representing the same record.
Decision 3: Integrate through the existing family entry points, not by adding a new presentation framework
- Decision: Route reuse through
ArtifactTruthPresenter,OperationUxPresenter, andRelatedNavigationResolverentry points instead of creating a generic presenter base class, a universal decorator, or a new UI taxonomy layer. - Rationale: The business semantics already live in these families. The feature's goal is to reduce repeated deterministic work beneath them, not to redesign how operator meaning is modeled.
- Alternatives considered:
- New cross-domain presentation framework: rejected because it would layer new semantics on top of already-correct families and violate the spec's narrow foundation intent.
- Surface-only fixes per page: rejected because the same repeated-cost pattern already spans multiple domains and would continue to reappear elsewhere.
Decision 4: Converge existing hidden caches into the shared contract and keep negative results reusable
- Decision: Standardize existing local request-like caches, such as the finding primary related-entry cache, behind the shared contract and allow deterministic negative results like “no related entry” or “no next action” to be reused within one request.
- Rationale: The repo already contains evidence that request-local reuse is useful, but it is unevenly applied. Converging on one contract avoids parallel caching patterns and still prevents repeated work when the correct result is the absence of a link or action.
- Alternatives considered:
- Leave existing local caches untouched and optimize only the worst pages: rejected because it would preserve multiple hidden patterns and make future adoption harder.
- Cache only positive results: rejected because deterministic negative results can also drive repeated work and should be equally reusable when scope-stable.
Decision 5: Treat mutation freshness as an explicit family rule
- Decision: Covered mutating flows must explicitly invalidate or bypass request-local reuse after business state changes within the same request.
- Rationale: The spec requires “no stale within request ambiguity.” A clear family-level freshness rule is safer than assuming the existing code path order will always avoid stale derived values.
- Alternatives considered:
- Cache for the full request without exceptions: rejected because post-action state could become stale and misleading.
- Disable reuse on every Livewire action: rejected because many actions still have deterministic pre- and post-action read phases where request-local reuse remains valuable.
Decision 6: Test derivation-count behavior directly instead of proxying everything through query-count assertions
- Decision: Validate the feature with focused unit and feature tests that prove one full derivation per request for representative covered families, plus explicit scope-safety and mutation-path tests.
- Rationale: The repeated-cost problem is not just SQL chatter. It is repeated presenter and resolver work across closures and page fragments. Query-count assertions alone would miss important non-query work and would not prove freshness rules.
- Alternatives considered:
- Measure only query counts: rejected because the problem is broader than SQL and includes repeated in-memory translation and navigation assembly.
- Rely on manual profiling only: rejected because this feature needs regression protection against future local cache drift.