openapi: 3.1.0 info: title: Request-Scoped Derived State Logical Contract version: 0.1.0 summary: Logical contract for resolving, reusing, and invalidating deterministic derived state within one request. description: | This contract is logical rather than transport-prescriptive. It documents the expected behavior of the internal request-scoped derived-state store used by existing presenter and resolver families. It does not add new external APIs and does not imply cross-request caching. Resolution and invalidation payloads use camelCase transport keys in this contract, while the runtime data model and JSON schema keep their internal snake_case field names. Implementations must normalize between the two shapes rather than treating them as separate contracts. Every future presenter or resolver family that wants to use the shared store must document its family key, scope-sensitive inputs, access pattern, and freshness policy through the consumer-validation contract before adoption. servers: - url: https://tenantpilot.local x-derived-state-consumers: - surface: reviews.register.table family: artifact_truth variant: tenant_review accessPattern: row_safe scopeInputs: - record_class - record_key - workspace_id freshnessPolicy: invalidate_after_mutation guardScope: - app/Filament/Pages/Reviews/ReviewRegister.php requiredMarkers: - 'private function reviewTruth(TenantReview $record, bool $fresh = false): ArtifactTruthEnvelope' - '$this->reviewTruth($record)' maxOccurrences: - needle: '->forTenantReview(' max: 1 - needle: '->forTenantReviewFresh(' max: 1 - surface: monitoring.evidence_overview.table family: artifact_truth variant: evidence_snapshot accessPattern: row_safe scopeInputs: - record_class - record_key - workspace_id - tenant_id freshnessPolicy: invalidate_after_mutation guardScope: - app/Filament/Pages/Monitoring/EvidenceOverview.php requiredMarkers: - 'private function snapshotTruth(EvidenceSnapshot $snapshot, bool $fresh = false): ArtifactTruthEnvelope' - '$this->snapshotTruth($snapshot)' maxOccurrences: - needle: '->forEvidenceSnapshot(' max: 1 - needle: '->forEvidenceSnapshotFresh(' max: 1 - surface: tenant.evidence_snapshots.table family: artifact_truth variant: evidence_snapshot accessPattern: row_safe scopeInputs: - record_class - record_key - workspace_id - tenant_id freshnessPolicy: invalidate_after_mutation guardScope: - app/Filament/Resources/EvidenceSnapshotResource.php requiredMarkers: - 'private static function truthEnvelope(EvidenceSnapshot $record, bool $fresh = false): ArtifactTruthEnvelope' - 'static::truthEnvelope($record)' - 'fresh: true' maxOccurrences: - needle: '->forEvidenceSnapshot(' max: 1 - needle: '->forEvidenceSnapshotFresh(' max: 1 - surface: tenant.tenant_reviews.table family: artifact_truth variant: tenant_review accessPattern: row_safe scopeInputs: - record_class - record_key - workspace_id - tenant_id freshnessPolicy: invalidate_after_mutation guardScope: - app/Filament/Resources/TenantReviewResource.php requiredMarkers: - 'private static function truthEnvelope(TenantReview $record, bool $fresh = false): ArtifactTruthEnvelope' - 'static::truthEnvelope($record)' - 'static::truthEnvelope($review->refresh(), fresh: true);' maxOccurrences: - needle: '->forTenantReview(' max: 1 - needle: '->forTenantReviewFresh(' max: 1 - surface: tenant.review_packs.table family: artifact_truth variant: review_pack accessPattern: row_safe scopeInputs: - record_class - record_key - workspace_id - tenant_id freshnessPolicy: invalidate_after_mutation guardScope: - app/Filament/Resources/ReviewPackResource.php requiredMarkers: - 'private static function truthEnvelope(ReviewPack $record, bool $fresh = false): ArtifactTruthEnvelope' - 'static::truthEnvelope($record)' - 'static::truthEnvelope($reviewPack->refresh(), fresh: true);' maxOccurrences: - needle: '->forReviewPack(' max: 1 - needle: '->forReviewPackFresh(' max: 1 - surface: admin.baseline_snapshots.truth family: artifact_truth variant: baseline_snapshot accessPattern: row_safe scopeInputs: - record_class - record_key - workspace_id freshnessPolicy: invalidate_after_mutation guardScope: - app/Filament/Resources/BaselineSnapshotResource.php requiredMarkers: - 'private static function truthEnvelope(BaselineSnapshot $snapshot, bool $fresh = false): ArtifactTruthEnvelope' - 'self::truthEnvelope($record)' maxOccurrences: - needle: '->forBaselineSnapshot(' max: 1 - needle: '->forBaselineSnapshotFresh(' max: 1 - surface: admin.baseline_snapshots.primary_navigation family: related_navigation_primary variant: baseline_snapshot accessPattern: row_safe scopeInputs: - record_class - record_key - workspace_id - user_id freshnessPolicy: invalidate_after_mutation guardScope: - app/Filament/Resources/BaselineSnapshotResource.php requiredMarkers: - 'private static function primaryRelatedEntry(BaselineSnapshot $record): ?RelatedContextEntry' - 'static::primaryRelatedEntry($record)' maxOccurrences: - needle: '->primaryListAction(CrossResourceNavigationMatrix::SOURCE_BASELINE_SNAPSHOT, $record)' max: 1 - surface: tenant.findings.primary_navigation family: related_navigation_primary variant: finding accessPattern: row_safe scopeInputs: - record_class - record_key - workspace_id - tenant_id - active_tenant_id - user_id - route_name freshnessPolicy: invalidate_after_mutation guardScope: - app/Filament/Resources/FindingResource.php requiredMarkers: - 'private static function primaryRelatedEntry(Finding $record, bool $fresh = false): ?RelatedContextEntry' - 'static::primaryRelatedEntry($record)' maxOccurrences: - needle: '->primaryListAction(CrossResourceNavigationMatrix::SOURCE_FINDING, $record)' max: 1 - needle: '->primaryListActionFresh(CrossResourceNavigationMatrix::SOURCE_FINDING, $record)' max: 1 - needle: 'primaryRelatedEntryCache' max: 0 - surface: tenant.policy_versions.header_navigation family: related_navigation_primary variant: policy_version accessPattern: page_safe scopeInputs: - record_class - record_key - workspace_id - tenant_id - active_tenant_id - user_id - route_name freshnessPolicy: invalidate_after_mutation guardScope: - app/Filament/Resources/PolicyVersionResource/Pages/ViewPolicyVersion.php requiredMarkers: - 'private function primaryRelatedEntry(bool $fresh = false): ?RelatedContextEntry' - '$this->primaryRelatedEntry()' maxOccurrences: - needle: '->primaryListAction(CrossResourceNavigationMatrix::SOURCE_POLICY_VERSION, $this->getRecord())' max: 1 - needle: '->primaryListActionFresh(CrossResourceNavigationMatrix::SOURCE_POLICY_VERSION, $this->getRecord())' max: 1 - surface: admin.operations.table_guidance family: operation_ux_guidance variant: surface_guidance accessPattern: row_safe scopeInputs: - record_class - record_key - workspace_id - tenant_id freshnessPolicy: invalidate_after_mutation guardScope: - app/Filament/Resources/OperationRunResource.php requiredMarkers: - 'private static function surfaceGuidance(OperationRun $record, bool $fresh = false): ?string' - 'private static function lifecycleAttentionSummary(OperationRun $record, bool $fresh = false): ?string' - 'static::surfaceGuidance($record)' maxOccurrences: - needle: 'OperationUxPresenter::surfaceGuidance(' max: 1 - needle: 'OperationUxPresenter::surfaceGuidanceFresh(' max: 1 - needle: 'OperationUxPresenter::lifecycleAttentionSummary(' max: 1 - needle: 'OperationUxPresenter::lifecycleAttentionSummaryFresh(' max: 1 - surface: admin.operations.detail_related_context family: related_navigation_detail variant: operation_run accessPattern: page_safe scopeInputs: - record_class - record_key - workspace_id - tenant_id - active_tenant_id - user_id - route_name freshnessPolicy: invalidate_after_mutation guardScope: - app/Filament/Resources/OperationRunResource.php requiredMarkers: - 'private static function relatedContextEntries(OperationRun $record, bool $fresh = false): array' - 'CrossResourceNavigationMatrix::SOURCE_OPERATION_RUN' maxOccurrences: - needle: '->detailEntries(CrossResourceNavigationMatrix::SOURCE_OPERATION_RUN, $record)' max: 1 - needle: '->detailEntriesFresh(CrossResourceNavigationMatrix::SOURCE_OPERATION_RUN, $record)' max: 1 - surface: admin.operations.viewer_explanation family: operation_ux_explanation variant: governance_operator_explanation accessPattern: page_safe scopeInputs: - record_class - record_key - workspace_id - tenant_id freshnessPolicy: invalidate_after_mutation guardScope: - app/Filament/Pages/Operations/TenantlessOperationRunViewer.php requiredMarkers: - 'private function governanceOperatorExplanation(): ?OperatorExplanationPattern' - 'OperationUxPresenter::governanceOperatorExplanation($this->run);' maxOccurrences: - needle: 'OperationUxPresenter::governanceOperatorExplanation(' max: 1 - needle: 'ArtifactTruthPresenter::class)->forOperationRun(' max: 0 - surface: admin.operations.viewer_related_links family: related_navigation_detail variant: operation_run accessPattern: page_safe scopeInputs: - record_class - record_key - workspace_id - tenant_id - active_tenant_id - user_id - route_name freshnessPolicy: invalidate_after_mutation guardScope: - app/Filament/Pages/Operations/TenantlessOperationRunViewer.php requiredMarkers: - 'private function relatedLinks(bool $fresh = false): array' - '$resolver->operationLinks($this->run, $this->relatedLinksTenant())' maxOccurrences: - needle: '->operationLinks($this->run, $this->relatedLinksTenant())' max: 1 - needle: '->operationLinksFresh($this->run, $this->relatedLinksTenant())' max: 1 paths: /contracts/derived-state/resolve: post: summary: Resolve or reuse one deterministic derived-state result within the current request operationId: resolveDerivedState requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DerivedStateResolutionRequest' responses: '200': description: Derived-state value resolved, reused, or intentionally bypassed content: application/json: schema: $ref: '#/components/schemas/DerivedStateResolutionResponse' /contracts/derived-state/invalidate: post: summary: Invalidate one or more request-local derived-state entries after a covered mutation operationId: invalidateDerivedState requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DerivedStateInvalidationRequest' responses: '200': description: Matching request-local entries invalidated content: application/json: schema: $ref: '#/components/schemas/DerivedStateInvalidationResponse' /contracts/derived-state/validate-consumer: post: summary: Validate one UI consumer against the supported family, keying, and freshness rules description: | Use this logical validation step before onboarding a new presenter or resolver family or before replacing an existing local cache pattern. The consumer must declare its access pattern, scope inputs, and freshness policy so unsupported reuse never becomes implicit. The automated Pest guard for derived-state adoption should report violations from this validation step with file and snippet context. operationId: validateDerivedStateConsumer requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DerivedStateConsumerValidationRequest' responses: '200': description: Consumer validation result content: application/json: schema: $ref: '#/components/schemas/DerivedStateConsumerValidationResponse' components: schemas: DerivedStateResolutionRequest: type: object additionalProperties: false required: - family - recordClass - recordKey - variant properties: family: type: string enum: - artifact_truth - operation_ux_guidance - operation_ux_explanation - related_navigation_primary - related_navigation_detail - related_navigation_header recordClass: type: string example: App\\Models\\TenantReview recordKey: type: string example: '42' variant: type: string example: list_row workspaceId: type: - integer - 'null' tenantId: type: - integer - 'null' contextHash: type: - string - 'null' allowNegativeResultCache: type: boolean default: true freshnessPolicy: type: string enum: - request_stable - invalidate_after_mutation - no_reuse default: request_stable DerivedStateResolutionResponse: type: object additionalProperties: false required: - cacheStatus - family - variant - negativeResult - freshnessPolicy properties: cacheStatus: type: string enum: - miss_resolved - hit_reused - bypassed family: type: string variant: type: string negativeResult: type: boolean freshnessPolicy: type: string enum: - request_stable - invalidate_after_mutation - no_reuse scopeFingerprint: type: - string - 'null' notes: type: - string - 'null' DerivedStateInvalidationRequest: type: object additionalProperties: false properties: family: type: - string - 'null' recordClass: type: - string - 'null' recordKey: type: - string - 'null' variant: type: - string - 'null' workspaceId: type: - integer - 'null' tenantId: type: - integer - 'null' reason: type: string required: - reason DerivedStateInvalidationResponse: type: object additionalProperties: false required: - invalidatedCount properties: invalidatedCount: type: integer minimum: 0 DerivedStateConsumerValidationRequest: type: object description: | Adoption request for one UI consumer. New consumers should not use the shared store until family support, scope-sensitive inputs, access pattern, and freshness behavior are all explicit. additionalProperties: false required: - surface - family - variant - accessPattern - scopeInputs - freshnessPolicy - guardScope properties: surface: type: string example: reviews.register.table family: type: string description: Supported derived-state family name, or a proposed family under review for adoption. variant: type: string description: Stable variant identifier for the consumer path, such as `list_row` or `detail_page`. accessPattern: type: string enum: - row_safe - page_safe - direct_once scopeInputs: type: array description: Scope or capability inputs that affect the result for this consumer. items: type: string guardScope: type: array description: Source paths or helper seams the automated guard scans when validating this consumer. items: type: string mutationSensitive: type: boolean description: Advisory hint for the guard when post-mutation state changes require explicit freshness handling; does not replace `freshnessPolicy`. default: false capabilitySensitive: type: boolean description: Advisory hint for the guard when capability context changes the result; does not replace `scopeInputs`. default: false freshnessPolicy: type: string enum: - request_stable - invalidate_after_mutation - no_reuse default: request_stable DerivedStateConsumerValidationResponse: type: object additionalProperties: false required: - valid - violations properties: valid: type: boolean violations: type: array items: type: object additionalProperties: false required: - code - message properties: code: type: string enum: - missing_scope_context - unsupported_family - mutation_freshness_gap - ad_hoc_local_cache - unstable_variant_key - missing_guard_scope - missing_freshness_policy message: type: string