TenantAtlas/specs/167-derived-state-memoization/contracts/request-scoped-derived-state.logical.openapi.yaml
2026-03-28 15:57:45 +01:00

572 lines
19 KiB
YAML

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