22 KiB
Implementation Plan: Request-Scoped Derived State and Resolver Memoization
Branch: 167-derived-state-memoization | Date: 2026-03-28 | Spec: /Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/167-derived-state-memoization/spec.md
Input: Feature specification from /Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/167-derived-state-memoization/spec.md
Note: This template is filled in by the /speckit.plan command. See .specify/scripts/ for helper scripts.
Summary
Introduce one explicit request-scoped derived-state contract for deterministic presenter and resolver outputs that are currently recalculated multiple times per record and per surface during one HTTP or Livewire request. The first implementation slice will bind a per-request in-memory store inside the Laravel container, define a stable key contract, adopt the store behind ArtifactTruthPresenter, OperationUxPresenter, and RelatedNavigationResolver, converge existing local cache behavior where appropriate, and protect freshness with focused mutation-path and scope-safety tests.
Technical Context
Language/Version: PHP 8.4.15
Primary Dependencies: Laravel 12, Filament v5, Livewire v4, Pest v4, Tailwind CSS v4, existing ArtifactTruthPresenter, OperationUxPresenter, RelatedNavigationResolver, AppServiceProvider, BadgeCatalog, BadgeRenderer, and current Filament resource/page seams
Storage: PostgreSQL unchanged; feature adds no persistence and relies on request-local in-memory state only
Testing: Pest 4 unit and feature tests, including focused Filament page/component coverage and mutation-path regression tests run through Laravel Sail
Target Platform: Laravel monolith web application in Sail locally and containerized Linux deployment in staging/production
Project Type: web application
Performance Goals: Covered deterministic derived-state families resolve at most once per request for the same family + record + variant + scope tuple; covered list/detail/widget pages keep DB-only render behavior and avoid repeated presenter/resolver fan-out in one render pass
Constraints: No cross-request caching, no new persistent summaries, no business-semantic changes, no RBAC drift, request-local reuse must work for both HTTP and Livewire requests, mutation freshness must be explicit, and covered surfaces must preserve current operator-visible meaning
Scale/Scope: One cross-cutting runtime contract, three covered derived-state families, representative adoption across review register, evidence overview, baseline snapshot, operation-run detail/list, and related-context surfaces serving dozens of rows and multi-section pages per request
Constitution Check
GATE: Passed before Phase 0 research. Re-checked after Phase 1 design and still passing.
| Principle | Pre-Research | Post-Design | Notes |
|---|---|---|---|
| Inventory-first / snapshots-second | PASS | PASS | No inventory or snapshot ownership semantics change; reuse only affects request-time derivation. |
| Read/write separation | PASS | PASS | Default path is read-only. Mutation-path freshness is a guardrail, not a new write flow. |
| Graph contract path | N/A | N/A | No Graph calls or config/graph_contracts.php changes. |
| Deterministic capabilities | PASS | PASS | Capability semantics remain unchanged; any capability-sensitive derivation must key scope explicitly or bypass reuse. |
| Workspace + tenant isolation | PASS | PASS | Request-local reuse is explicitly scoped and must not cross workspace, tenant, or request boundaries. |
| RBAC-UX authorization semantics | PASS | PASS | No new policies or capabilities; tests must prove no 404/403 leakage through reused derived values. |
| Run observability / Ops-UX | PASS | PASS | No new OperationRun family or feedback path; covered operation surfaces reuse guidance only. |
| Data minimization | PASS | PASS | No additional persistence or log payloads; cached state lives only inside the current request. |
| Proportionality / no premature abstraction | PASS WITH JUSTIFIED ABSTRACTION | PASS WITH JUSTIFIED ABSTRACTION | One new abstraction is introduced because three existing families already share the same repeated-cost shape and page-local caches are insufficient. |
| Persisted truth / behavioral state | PASS | PASS | No new table, artifact, status, or reason family. |
| UI semantics / few layers | PASS | PASS | The design sits below existing presenters and resolvers and must not create a second interpretation layer. |
| Badge semantics (BADGE-001) | PASS | PASS | Existing badge catalogs and renderers remain the single source for visible state mappings. |
| Filament Action Surface Contract | PASS | PASS | No action inventory or inspect-affordance change; only repeated derivation behind existing surfaces changes. |
| Filament UX-001 | PASS | PASS | No layout redesign; current list/detail/widget structures stay intact. |
| Filament v5 / Livewire v4 compliance | PASS | PASS | The design remains inside the existing Filament v5 + Livewire v4 stack with no legacy API introduction. |
| Provider registration location | PASS | PASS | No panel or provider change; Laravel 11+ provider registration remains in bootstrap/providers.php. |
| Global-search hard rule | PASS | PASS | No global-search behavior changes are proposed. |
| Asset strategy | PASS | PASS | No new assets, no shared asset registration, and no new filament:assets requirement. |
Phase 0 Research
Research outcomes are captured in /Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/167-derived-state-memoization/research.md.
Key decisions:
- Use a dedicated per-request in-memory store bound through the Laravel container rather than static arrays or persistent caches.
- Define one stable key contract around family, record identity, variant, and scope context rather than relying on model-object identity or page-local assumptions.
- Integrate through the existing
ArtifactTruthPresenter,OperationUxPresenter, andRelatedNavigationResolverentry points instead of introducing a new presentation framework. - Standardize row-safe and page-safe consumer seams on covered Filament surfaces so multiple closures can share one resolved family result.
- Treat mutation freshness as an explicit invalidation or fresh-access rule rather than as an accidental side effect of current code order.
- Validate behavior with focused derivation-count, scope-safety, and mutation-path tests instead of relying on ad hoc microbenchmarks.
Phase 1 Design
Design artifacts are created under /Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/167-derived-state-memoization/:
data-model.md: runtime entities for the request-scoped store, key contract, supported family contract, and freshness policycontracts/request-scoped-derived-state.logical.openapi.yaml: logical service contract for resolve, invalidate, and consumer-validation behaviorcontracts/request-scoped-derived-state-key.schema.json: structural schema for deterministic key compositionquickstart.md: focused implementation and verification workflow
Design decisions:
- The request-scoped store is a single narrow abstraction, not a generic caching platform.
- The store is bound in the Laravel container per request and not persisted to cache stores or the database.
- Existing family entry points remain the only place where covered derivations are resolved; adoption happens behind those seams.
- Existing local caches such as
FindingResource::$primaryRelatedEntryCachebecome convergence points, not a parallel long-term pattern. - Covered mutation flows must either invalidate affected keys or force a fresh derivation path after business-state changes.
- Regression protection focuses on repeated-read elimination, cross-scope safety, and post-mutation freshness rather than on implementation trivia.
Project Structure
Documentation (this feature)
specs/167-derived-state-memoization/
├── spec.md
├── plan.md
├── research.md
├── data-model.md
├── quickstart.md
├── contracts/
│ ├── request-scoped-derived-state.logical.openapi.yaml
│ └── request-scoped-derived-state-key.schema.json
├── checklists/
│ └── requirements.md
└── tasks.md
Source Code (repository root)
app/
├── Filament/
│ ├── Pages/
│ │ ├── Monitoring/
│ │ │ └── EvidenceOverview.php
│ │ ├── Operations/
│ │ │ └── TenantlessOperationRunViewer.php
│ │ └── Reviews/
│ │ └── ReviewRegister.php
│ └── Resources/
│ ├── BaselineSnapshotResource.php
│ ├── EvidenceSnapshotResource.php
│ ├── FindingResource.php
│ ├── OperationRunResource.php
│ ├── ReviewPackResource.php
│ ├── TenantReviewResource.php
│ └── PolicyVersionResource/
│ └── Pages/
│ └── ViewPolicyVersion.php
├── Providers/
│ └── AppServiceProvider.php
└── Support/
├── Navigation/
│ └── RelatedNavigationResolver.php
├── OpsUx/
│ └── OperationUxPresenter.php
└── Ui/
├── DerivedState/
│ ├── DerivedStateKey.php
│ ├── DerivedStateFamily.php
│ └── RequestScopedDerivedStateStore.php
└── GovernanceArtifactTruth/
└── ArtifactTruthPresenter.php
tests/
├── Feature/
│ ├── Filament/
│ │ ├── ReviewRegisterDerivedStateMemoizationTest.php
│ │ ├── EvidenceOverviewDerivedStateMemoizationTest.php
│ │ ├── OperationRunDerivedStateMemoizationTest.php
│ │ └── DerivedStateMutationFreshnessTest.php
│ ├── Guards/
│ │ └── DerivedStateConsumerAdoptionGuardTest.php
│ └── Navigation/
│ └── RelatedNavigationResolverMemoizationTest.php
└── Unit/
└── Support/
└── Ui/
└── DerivedState/
└── RequestScopedDerivedStateStoreTest.php
Structure Decision: Keep the existing Laravel monolith structure. Introduce the new runtime support types as a narrow support layer near the covered presenter/resolver families, bind them in AppServiceProvider, and adopt them through current Filament resources/pages rather than introducing new base directories or a broader platform package.
Implementation Strategy
Phase A — Introduce the Request-Scoped Store and Key Contract
Goal: Add one explicit per-request store with deterministic keys and no persistence.
| Step | File | Change |
|---|---|---|
| A.1 | app/Providers/AppServiceProvider.php |
Bind the new derived-state store with request-local lifecycle semantics and no cross-request persistence |
| A.2 | app/Support/Ui/DerivedState/* |
Add the narrow store, key, and supported-family support types needed for resolve and invalidate behavior |
| A.3 | Covered unit tests | Verify hit/miss, negative-result reuse, distinct-variant separation, and explicit invalidation behavior |
Phase B — Adopt Artifact Truth on Representative Surfaces
Goal: Route repeated artifact-truth resolution through the shared contract and expose row-safe/page-safe access on representative surfaces.
| Step | File | Change |
|---|---|---|
| B.1 | app/Support/Ui/GovernanceArtifactTruth/ArtifactTruthPresenter.php |
Route forBaselineSnapshot(), forEvidenceSnapshot(), forTenantReview(), forReviewPack(), and forOperationRun() through the request-scoped store while preserving existing envelope semantics |
| B.2 | app/Filament/Pages/Reviews/ReviewRegister.php |
Replace repeated per-closure forTenantReview() calls with one row-safe access path |
| B.3 | app/Filament/Pages/Monitoring/EvidenceOverview.php |
Reuse one artifact-truth resolution per active snapshot row in the canonical evidence overview |
| B.4 | app/Filament/Resources/BaselineSnapshotResource.php, app/Filament/Resources/BaselineSnapshotResource/Pages/ViewBaselineSnapshot.php, app/Filament/Resources/EvidenceSnapshotResource.php, app/Filament/Resources/TenantReviewResource.php, app/Filament/Resources/ReviewPackResource.php, and app/Filament/Resources/OperationRunResource.php |
Keep the first-slice baseline snapshot, evidence snapshot, tenant review, review pack, and operation-run helper consumers aligned to the shared presenter contract without changing visible meaning |
Phase C — Adopt Operation UX and Related Navigation
Goal: Reuse operation guidance and related-context resolution through the same contract while converging existing hidden caches.
| Step | File | Change |
|---|---|---|
| C.1 | app/Support/OpsUx/OperationUxPresenter.php |
Route covered guidance/explanation reads through the request-scoped store |
| C.2 | app/Support/Navigation/RelatedNavigationResolver.php |
Route primary, detail, and header entry resolution through the request-scoped store |
| C.3 | app/Filament/Resources/OperationRunResource.php and app/Filament/Pages/Operations/TenantlessOperationRunViewer.php |
Reuse operation guidance and related context on run list/detail surfaces |
| C.4 | app/Filament/Resources/FindingResource.php and app/Filament/Resources/PolicyVersionResource/Pages/ViewPolicyVersion.php |
Replace page-local related-navigation repetition and align the existing finding-specific cache behavior with the shared contract |
Phase D — Freshness Rules and Consumer Guardrails
Goal: Make post-mutation freshness explicit and prevent future heavy closures from bypassing the shared contract.
| Step | File | Change |
|---|---|---|
| D.1 | Covered mutating resource/page actions | Document and implement invalidation or forced-fresh access where visible truth, guidance, or related navigation changes within the same request |
| D.2 | Covered resources/pages | Introduce clearly named row-safe or page-safe helper seams for repeated derived-state reads |
| D.3 | specs/167-derived-state-memoization/quickstart.md, specs/167-derived-state-memoization/contracts/request-scoped-derived-state.logical.openapi.yaml, and tests/Feature/Guards/DerivedStateConsumerAdoptionGuardTest.php |
Document the future-family adoption path and add an automated guard that locks the supported-family, keying, access-pattern, and freshness rules so future surfaces cannot drift back to ad hoc local caches |
Phase E — Regression Protection and Verification
Goal: Prove the first slice delivers one-derivation-per-request behavior without leaks or stale post-mutation state.
| Step | File | Change |
|---|---|---|
| E.1 | tests/Unit/Support/Ui/DerivedState/RequestScopedDerivedStateStoreTest.php |
Add core store and key behavior coverage |
| E.2 | tests/Feature/Filament/ReviewRegisterDerivedStateMemoizationTest.php |
Prove repeated artifact-truth reads on one row resolve once per request |
| E.3 | tests/Feature/Filament/EvidenceOverviewDerivedStateMemoizationTest.php |
Prove canonical evidence overview reuses one artifact-truth result per row without scope leakage |
| E.4 | tests/Feature/Filament/OperationRunDerivedStateMemoizationTest.php and tests/Feature/Navigation/RelatedNavigationResolverMemoizationTest.php |
Prove operation-guidance and related-navigation reuse plus authorization safety |
| E.5 | tests/Feature/Filament/DerivedStateMutationFreshnessTest.php |
Prove covered post-mutation truth and navigation follow explicit fresh-derivation rules within the same request |
| E.6 | tests/Feature/Guards/DerivedStateConsumerAdoptionGuardTest.php |
Fail CI with actionable output if a covered family introduces an ad hoc local cache or adopts the shared store without explicit declaration metadata |
| E.7 | vendor/bin/sail bin pint --dirty --format agent and focused Pest runs |
Required formatting and targeted verification before implementation completion |
Key Design Decisions
D-001 — Request-local reuse must be explicit, not hidden in page-local static arrays
The repo already shows local cache behavior in isolated places, but the repeated-cost problem spans three families and multiple surfaces. One explicit request-scoped contract is narrower and safer than multiplying hidden static caches.
D-002 — Keys must include scope context, not just record identity
Model ID alone is insufficient because some derived values depend on route context, surface variant, or capability-sensitive visibility. The key contract must include scope-sensitive inputs or the family must bypass reuse.
D-003 — Adoption should happen behind existing presenters and resolvers, not above them
The feature exists to reduce duplicate work beneath already-correct semantics. Replacing the current presenters or adding a new presentation meta-framework would violate the spec's bounded intent.
D-004 — Row-safe and page-safe helper seams are the UI-level adoption point
Most repeated work comes from multiple closures on the same list row or detail section. The consumer-side seam should therefore be one named row-safe or page-safe accessor rather than another layer of inline app() calls.
D-005 — Mutation freshness is part of the contract, not an afterthought
Request-local reuse is only safe if covered post-action flows either invalidate affected keys or force a fresh derivation path. Blanket caching to request end would create stale-state ambiguity.
Risk Assessment
| Risk | Impact | Likelihood | Mitigation |
|---|---|---|---|
| Incorrect key composition leaks state across variants or scopes | High | Medium | Include scope/context fields in the key contract and add explicit cross-tenant/workspace regression tests |
| Overbroad first slice creates a large diff with mixed concerns | Medium | Medium | Keep adoption to representative list/detail/canonical surfaces in the three covered families |
| Mutation flows accidentally reuse stale pre-action values | High | Medium | Define family-specific freshness policy and add at least one mutation-path regression per covered family |
| Existing local caches continue living in parallel with the shared contract | Medium | Medium | Converge finding-specific cache behavior into the shared contract and document page-local cache exceptions as temporary only |
| New contract becomes a general cache framework over time | Medium | Low | Keep support types narrow, document non-goals, and require future families to prove deterministic fit before adoption |
Test Strategy
- Add one narrow unit suite for the store and key contract instead of snapshotting every consuming presenter branch.
- Add focused Filament feature tests for representative list/detail/canonical surfaces where repeated closure calls exist today.
- Prove business-visible non-regression by asserting current labels, badge properties, next-action text, and related URLs remain unchanged on covered examples.
- Add scope-safety tests that exercise tenant-context and canonical workspace-context reuse without leaking unauthorized tenant state, including at least one explicit deny-as-not-found regression for non-members or wrong-scope users and one explicit forbidden regression for in-scope users lacking capability.
- Add a dedicated post-mutation freshness suite in
tests/Feature/Filament/DerivedStateMutationFreshnessTest.phpto prove stale pre-action truth, guidance, or related navigation is not reused after business state changes in the same request. - Keep the future-family adoption path documented in
quickstart.mdand the logical contract so new presenter or resolver families declare supported family, scope inputs, access pattern, and freshness behavior before using the shared store. - Add a lightweight guard test in
tests/Feature/Guards/DerivedStateConsumerAdoptionGuardTest.phpthat scans covered source paths and fails with file-and-snippet output when new ad hoc local caches or undeclared adoption patterns appear. - Keep Livewire v4-compatible page tests for covered pages and use the minimum focused Sail test set needed for implementation verification.
Complexity Tracking
| Violation | Why Needed | Simpler Alternative Rejected Because |
|---|---|---|
| New request-scoped derived-state abstraction | Three existing families already exhibit the same deterministic repeated-cost pattern across shipped surfaces, and the contract must enforce scope safety plus freshness rules consistently | Page-local static caches and one-off helper memoization hide scope boundaries, duplicate logic, and cannot provide one enforceable adoption path |
Proportionality Review
- Current operator problem: Covered pages recompute the same deterministic truth, guidance, and related-navigation state multiple times in one request, increasing render cost and making consistency across closures harder to defend.
- Existing structure is insufficient because: Existing presenters and resolvers are correct but have no explicit request-local reuse boundary, so every closure and page fragment can trigger a full derivation again.
- Narrowest correct implementation: One request-scoped store plus one stable key contract beneath the existing families is enough to remove repeated work without persistence, without semantic changes, and without a new UI meta-framework.
- Ownership cost created: The codebase gains one bounded runtime abstraction, adoption work on covered consumers, and focused tests for keying, scope safety, and freshness.
- Alternative intentionally rejected: Static arrays, ad hoc per-page caches, and persistent cache stores were rejected because they either obscure scope semantics or solve the wrong problem layer.
- Release truth: Current-release truth. The hotspot exists today on Review Register, Evidence Overview, Baseline Snapshot, Operation Run, and related navigation surfaces.