The ledger declared "All 38 are now FIXED" while six of those rows were reproducibly partial. A summary sentence is cheap and a reviewer reads it as evidence, so the claim is now derived from a machine-readable record: `docs/operations/adapter-remediation-dispositions.json` carries each finding's disposition and the test paths that hold it, and `check:remediation-ledger` joins that file to the prose, verifies every evidence path exists, and refuses a blanket closure sentence while any row is still open. The shared-abort gate had the same weakness in miniature: it passed when at least one production file imported the primitive, so an unrelated import satisfied it while Image and Resumable kept their own diverging copies. It now requires the four named consumers to resolve their import to the primitive itself, and prints the exact importer set rather than a count. `check:optional-recipes:source` was already failing before this work (52,078 against a 52,000 budget) and the correctness code above pushed it further. Duplicate abort mechanics were consolidated first — Image and Resumable onto the shared primitive, four decoders onto one snapshot helper — and the remainder is code the review asked for, so the budget is reset to 54,600 against a measured 53,810 with that reasoning recorded, rather than the failure being carried forward as if it were green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
341 lines
27 KiB
JSON
341 lines
27 KiB
JSON
{
|
|
"$schema": "../../schemas/config/frontend-capability-recipes.schema.json",
|
|
"schemaVersion": 1,
|
|
"decisionId": "VD-10",
|
|
"defaultStatus": "NOT_INSTALLED",
|
|
"productionRuntimeDependencies": [],
|
|
"catalogOwner": "frontend-platform",
|
|
"reviewOn": "project-capability-selection",
|
|
"vendorPackagePatterns": [
|
|
"@launchdarkly/*",
|
|
"@sentry/*",
|
|
"@opentelemetry/*",
|
|
"@openapitools/openapi-generator-cli",
|
|
"@reduxjs/toolkit",
|
|
"@tanstack/react-virtual",
|
|
"@uppy/*",
|
|
"firebase",
|
|
"idb",
|
|
"react-window",
|
|
"redux",
|
|
"socket.io-client",
|
|
"tus-js-client",
|
|
"workbox-window",
|
|
"xstate",
|
|
"zustand"
|
|
],
|
|
"recipes": [
|
|
{
|
|
"id": "realtime",
|
|
"status": "RECIPE_AVAILABLE",
|
|
"referenceRuntime": {
|
|
"status": "AVAILABLE_NOT_COMPOSED",
|
|
"coveredCapabilities": [
|
|
"transport-independent event authority and recovery",
|
|
"bounded reconnect ownership",
|
|
"fetch-stream SSE",
|
|
"bounded polling",
|
|
"single-writer live and polling handoff",
|
|
"WebSocket closed protocol",
|
|
"Web Push window and Service Worker control"
|
|
],
|
|
"sourceRoots": [
|
|
"src/application/ports/realtime",
|
|
"src/application/ports/out/web-push-control.ts",
|
|
"src/application/policies/bounded-polling.ts",
|
|
"src/contracts/realtime-events.ts",
|
|
"src/contracts/realtime-streams.ts",
|
|
"src/contracts/web-push.ts",
|
|
"src/adapters/realtime",
|
|
"src/adapters/web-push"
|
|
],
|
|
"conformanceScripts": [
|
|
"test:unit",
|
|
"check:realtime-boundaries",
|
|
"check:realtime-boundaries:fixture",
|
|
"check:optional-recipes",
|
|
"test:realtime-removal"
|
|
],
|
|
"productionComposition": false
|
|
},
|
|
"trigger": "The backend exposes ordered push events with a documented resume and authorization protocol.",
|
|
"forbiddenWhen": ["Polling satisfies the measured freshness requirement.", "Event ordering and reconnect ownership are undefined."],
|
|
"boundary": "transport-independent event authority plus separately owned SSE, WebSocket, bounded polling and Web Push adapters",
|
|
"port": "RealtimeEventAuthority / WebPushControlPort / transport-specific connection and polling factories",
|
|
"fake": "Deterministic event authority, transport facade, clock, repository and Service Worker test doubles",
|
|
"failureKinds": ["abort", "disconnect-or-timeout", "protocol-or-mapping-mismatch", "duplicate-or-stale", "sequence-gap-or-cursor-expiry", "queue-overflow", "scope-fenced", "poll-budget-exhausted", "push-permission-or-subscription-failure"],
|
|
"lifecycleMethods": ["close-or-dispose", "unsubscribe", "cancel-via-AbortSignal", "bounded-poll-lease", "revoke-push-association"],
|
|
"owner": "project-owner-required",
|
|
"securityPrivacy": ["Validate every event envelope and closed transport frame before application effects.", "Bind stream state to the current opaque scope generation and advance checkpoints only after committed effects or authoritative recovery.", "Use fixed same-origin endpoints and an exact WebSocket subprotocol; never place credentials, cursors, subscription material or scope bindings in URLs or telemetry.", "Treat Web Push as a notification hint, fence registration and revocation with one durable compare-and-swap control record, and allow only registry-owned notification and route intents.", "Keep every queue, parser, reconnect, poll, notification and storage operation bounded and abortable."],
|
|
"bundleBudgetGzipBytes": 40000,
|
|
"fallback": "Bounded polling or explicitly stale UI.",
|
|
"removal": ["Disable admission and close active readers, sockets, poll leases and worker handlers.", "Revoke and purge only the owned Web Push association and notification state.", "Remove composition, registries, adapters and any selected vendor dependency.", "Run realtime boundary, runtime-removal and production-bundle gates."],
|
|
"serverStatePolicy": "query-cache-owned"
|
|
},
|
|
{
|
|
"id": "offline-indexeddb",
|
|
"status": "RECIPE_AVAILABLE",
|
|
"referenceRuntime": {
|
|
"status": "AVAILABLE_NOT_COMPOSED",
|
|
"coveredCapabilities": [
|
|
"IndexedDB",
|
|
"OPFS",
|
|
"StorageManager estimate/persistence"
|
|
],
|
|
"sourceRoots": [
|
|
"src/application/ports/browser-file-storage/indexeddb-port.ts",
|
|
"src/application/ports/browser-file-storage/opfs-ports.ts",
|
|
"src/application/ports/browser-file-storage/storage-durability-port.ts",
|
|
"src/adapters/browser-file-storage",
|
|
"src/adapters/storage/indexeddb",
|
|
"src/adapters/storage/opfs"
|
|
],
|
|
"conformanceScripts": [
|
|
"test:unit",
|
|
"test:browser-capabilities",
|
|
"verify:browser-capability-evidence",
|
|
"check:browser-file-storage-boundaries",
|
|
"check:optional-recipes",
|
|
"test:browser-file-storage-removal"
|
|
],
|
|
"productionComposition": false
|
|
},
|
|
"trigger": "A product requirement needs indexed offline records, an unsynced command queue, or a large local binary sidecar beyond small public preferences.",
|
|
"forbiddenWhen": ["The data contains credentials.", "The browser would connect directly to a server database or object store.", "A normal HTTP cache is sufficient.", "Partition, retention, quota and recovery ownership are undefined."],
|
|
"boundary": "feature-specific async repository with registry-issued opaque dataset scope and immutable full-policy binding, plus an OPFS large-object sidecar whose logical commit authority and bidirectional scope binding are owned by an IndexedDB journal",
|
|
"port": "IndexedDbRepositoryPort / IndexedDbMaintenancePort / DurableObjectStorePort / DurableObjectMaintenancePort / StorageDurabilityPort",
|
|
"fake": "MemoryStructuredOfflineStore / MemoryDurableObjectStore / MemoryStorageDurabilityAdapter",
|
|
"failureKinds": ["open-blocked", "versionchange", "quota", "corruption", "migration-rollback", "revision-conflict", "storage-eviction", "partial-object-write", "dataset-binding-mismatch", "dataset-budget-exceeded", "lifecycle-authorization-denied", "expired-resource"],
|
|
"lifecycleMethods": ["close", "cancel-via-AbortSignal", "enforce-bounded-lifecycle-batch", "prune-expired-receipts", "reconcile", "enforcePolicies-with-composition-authority"],
|
|
"owner": "project-owner-required",
|
|
"securityPrivacy": ["Classify every persisted field and binary namespace.", "Encrypting in the same client is not a credential protection boundary.", "Keep database schema and record codec versions separate.", "Derive the physical IndexedDB name only from registry-issued authority, namespace and partition tokens; readable namespace, business and account IDs are forbidden.", "Persist and revalidate an immutable scope plus full BrowserStoragePolicy binding during upgrade, post-open and maintenance; missing or mismatched existing bindings fail closed.", "Use the actual IndexedDB wire split: StoredRecord contains only key, codecVersion, revision and payload; writtenAtEpochMs, synchronization, measuredBytes and eligibleAtEpochMs belong to the retention sidecar, while idempotency receipts and governance binding/budget use separate stores.", "Include every store registered in lifecycleMetadataStores in bounded full-partition purge while retaining immutable governance identity.", "Measure conservative logical bytes through the codec and atomically enforce dataset usedBytes plus receiptCount with record, lifecycle and migration writes.", "Enforce TTL before sweep visibility, delete UNTIL_SYNCED only after explicit confirmation, and require a composition-authorized short-lived proof for every deleting IndexedDB lifecycle batch.", "Bound idempotency receipt retention to 31 days and configured count to the implementation ceiling of 1000000; bound migration to old-writer-drained batches no larger than 500 rows or 30000ms.", "Bind OPFS readable and physical scopes in both directions and use only /ca-frontend-opfs-v1/authorities/<authorityToken>/<namespaceToken>/<partitionToken>/ for physical dataset layout.", "For OPFS LOGOUT, UNTIL_SYNCED and ACCOUNT_DELETION maintenance, composition must provide both requestMaintenanceAuthority and consumeMaintenanceAuthority; issue a fresh proof bound to the exact frozen reason, scope and policy for at most five minutes, then atomically consume it to reject replay.", "Never expose an OPFS authority proof through the application request, persistence, diagnostics or telemetry.", "Never place user file names or identifiers in OPFS paths or diagnostics."],
|
|
"bundleBudgetGzipBytes": 36000,
|
|
"fallback": "Read-only or online-only query path; OPFS may degrade to a size-capped IndexedDB Blob only when the product policy approves it.",
|
|
"removal": ["Stop writes and background migration.", "Reconcile or export unsynced data, then purge only governance-bound owned partitions and OPFS namespaces through authorized bounded lifecycle operations.", "Close all database, channel, worker and file handles.", "Remove repository composition and dependency."],
|
|
"serverStatePolicy": "reference-or-command-only"
|
|
},
|
|
{
|
|
"id": "service-worker-pwa",
|
|
"status": "RECIPE_AVAILABLE",
|
|
"referenceRuntime": {
|
|
"status": "AVAILABLE_NOT_COMPOSED",
|
|
"coveredCapabilities": [
|
|
"Cache Storage public-response administration"
|
|
],
|
|
"sourceRoots": [
|
|
"src/application/ports/browser-file-storage/cache-storage-ports.ts",
|
|
"src/adapters/browser-file-storage",
|
|
"src/adapters/cache-storage"
|
|
],
|
|
"conformanceScripts": [
|
|
"test:unit",
|
|
"test:browser-capabilities",
|
|
"verify:browser-capability-evidence",
|
|
"check:browser-file-storage-boundaries",
|
|
"check:optional-recipes",
|
|
"test:browser-file-storage-removal"
|
|
],
|
|
"productionComposition": false
|
|
},
|
|
"trigger": "Installability, a measured offline-shell requirement, or an explicitly owned public HTTP representation cache is approved.",
|
|
"forbiddenWhen": ["Hosting cache and worker cache ownership conflict.", "Update and rollback UX is undefined.", "Authenticated, private, opaque or personal responses would be cached.", "Cache freshness, byte and entry limits are undefined."],
|
|
"boundary": "bootstrap update controller plus platform-local public Request/Response cache administration",
|
|
"port": "ServiceWorkerUpdatePort / PublicResponseCacheAdmin recipe / PublicResponseCachePort / PublicResponseCacheAdminPort reference runtime",
|
|
"fake": "FakeServiceWorkerUpdateAdapter / MemoryPublicResponseCache",
|
|
"failureKinds": ["stale-worker", "update-loop", "offline-fallback", "incomplete-candidate", "integrity-mismatch", "cache-policy-rejection", "quota"],
|
|
"lifecycleMethods": ["unregister", "rollback", "delete-owned-caches"],
|
|
"owner": "project-owner-required",
|
|
"securityPrivacy": ["Cache only explicit same-origin public GET representations.", "Never cache authenticated, cookie-dependent, private, no-store, opaque or personal responses.", "Bind candidate cache names to release identity and a canonical manifest digest that includes normalized expectedContentType, exact request identity, expected byte length and integrity digest.", "Reject a response whose normalized Content-Type differs from manifest expectedContentType even when body integrity matches.", "Derive cleanup retention only from the verified active pointer and composition retainedPreviousReleaseCount; cleanup callers cannot submit cache names, release registry IDs or any retain set.", "Read active-pointer and release-marker control JSON through a strict UTF-8 stream capped at exactly 2 MiB (2097152 bytes), cancel on overflow and fail closed before parsing oversized metadata.", "Keep exact query and Vary semantics; ignoreSearch and ignoreVary are forbidden.", "Fail closed on malformed update metadata or integrity mismatch."],
|
|
"bundleBudgetGzipBytes": 10000,
|
|
"fallback": "Normal network application with hosting cache headers.",
|
|
"removal": ["Deploy an unregister migration.", "Delete only parsed, owned cache namespaces after old controlled clients drain.", "Remove worker registration, cache metadata and manifest."],
|
|
"serverStatePolicy": "network-cache-policy-only"
|
|
},
|
|
{
|
|
"id": "file-transfer",
|
|
"status": "RECIPE_AVAILABLE",
|
|
"referenceRuntime": {
|
|
"status": "AVAILABLE_NOT_COMPOSED",
|
|
"coveredCapabilities": [
|
|
"File",
|
|
"Blob",
|
|
"native file input",
|
|
"system file picker",
|
|
"object URL preview",
|
|
"download delivery",
|
|
"presigned URL capability",
|
|
"bounded streaming download",
|
|
"multipart/resumable upload",
|
|
"durable non-secret upload checkpoint",
|
|
"Image CDN responsive delivery"
|
|
],
|
|
"sourceRoots": [
|
|
"src/application/ports/browser-file-storage/file.ts",
|
|
"src/application/ports/browser-transfer",
|
|
"src/adapters/browser-file-storage",
|
|
"src/adapters/browser-files",
|
|
"src/adapters/browser-transfer"
|
|
],
|
|
"conformanceScripts": [
|
|
"test:unit",
|
|
"test:browser-capabilities",
|
|
"verify:browser-capability-evidence",
|
|
"check:browser-file-storage-boundaries",
|
|
"check:optional-recipes",
|
|
"test:browser-file-storage-removal"
|
|
],
|
|
"productionComposition": false
|
|
},
|
|
"trigger": "The product selects, inspects, previews, uploads, downloads or delivers image renditions with bounded memory, resumability, cancellation, expiry and integrity requirements.",
|
|
"forbiddenWhen": ["Allowed count, byte, extension, MIME and content-signature policy is missing.", "The BFF does not own authorization, short-lived capability issuance, upload session reconciliation, quarantine and orphan cleanup.", "Long-lived credentials, presigned URLs or signed headers would enter persistence, application state or telemetry.", "Native File, Blob, object URL or file-system handles would cross into domain state or persistence.", "Large downloads would be returned as one in-memory byte array or Blob.", "Image callers could submit arbitrary CDN source URLs or transform parameters."],
|
|
"boundary": "BFF-owned transfer control plane plus adapter-owned browser/object-storage data plane; presentation receives opaque file/image references, registered policies and bounded result streams only",
|
|
"port": "FilePickerPort / FileContentPort / TransientPreviewPort / DownloadDeliveryPort / PresignedDownloadSourcePort / PresignedUploadPartPort / ResumableUploadPort / ImageCdnPresentationPort",
|
|
"fake": "Memory file/preview/download adapters plus injected deterministic capability, upload-control-plane, part-executor and image-verifier test doubles",
|
|
"failureKinds": ["dismissed", "permission-denied", "count-or-size-rejection", "type-or-signature-rejection", "file-changed", "abort", "integrity-failure", "partial-save", "expired-or-revoked-capability", "part-or-session-conflict", "checkpoint-conflict", "quarantined", "image-policy-rejection"],
|
|
"lifecycleMethods": ["release-file-ref", "release-or-dispose-preview-leases", "cancel-via-AbortSignal", "reconcile-or-explicitly-abort-upload", "close-checkpoint-store", "dispose-capability-and-image-runtime"],
|
|
"owner": "project-owner-required",
|
|
"securityPrivacy": ["Treat file name, extension, MIME and lastModified as untrusted metadata.", "Resolve only exact composition-issued file and image policy object identities; callers cannot raise byte, candidate, pixel, quality, format, lifetime or origin ceilings.", "Use opaque file references and verification receipts bound to an inspected immutable file snapshot and the exact registered profile; reject replay through another profile even when an inspection rule ID matches.", "Treat presigned URLs as bearer capabilities; bind exact method, resource or upload part, offset, length, media type, checksum, origin, path, query, headers and expiry in an in-memory identity vault.", "Use credentials omit, redirect error, no-referrer and no-store for direct data-plane fetch; never persist or observe URL, query, signed header, capability, file name or raw backend message, and never emit digest, raw ETag or receipt values to diagnostics or telemetry.", "A strict account-partitioned upload checkpoint may persist only the protocol-defined SHA-256 file fingerprint, per-part checksum and bounded opaque non-authorizing part receipt token required for server reconciliation; no bearer token or raw signed capability is allowed.", "Persist only strict non-authorizing upload checkpoints and reconcile them with server-authoritative status and re-hashed local parts before completion.", "Require a synchronous server-issued browser-managed download capability whose receipt exactly equals the caller's branded capability receipt and whose resource, media type, safe extension, maximum bytes, optional digest and expiry all match before handoff.", "Expose File, OPFS, Cache and transfer byte streams only as chunk-level closed Results; stop after the first failure, cancel native readers and never throw a raw native exception across the port.", "Accept Image CDN assets only through immutable allowlisted or signature-verified descriptors and registered preset identities; reject active formats, arbitrary transforms, pixel/decode-budget overflow and unsafe cache policy.", "Upload completion remains QUARANTINED until backend scan and promotion; client capability checks are not an authorization boundary.", "Active content preview requires isolation or download-only treatment."],
|
|
"bundleBudgetGzipBytes": 54600,
|
|
"fallback": "Accessible native file input, same-origin authorized server upload/download and a single bounded server-selected image rendition; generated artifacts above the buffer budget move to server-side generation.",
|
|
"removal": ["Stop new capability and upload-session issuance, then cancel active reads and transfers.", "Reconcile or explicitly abort active multipart sessions and let backend TTL cleanup remove ambiguous orphans.", "Remove non-secret checkpoints according to account and retention policy.", "Release file references, revoke preview object-URL leases and dispose file, capability and image runtimes.", "Remove transfer/image feature facades and composition, then prove browser-transfer sources are absent from the production module inventory."],
|
|
"serverStatePolicy": "query-cache-metadata-only"
|
|
},
|
|
{
|
|
"id": "generated-api",
|
|
"status": "RECIPE_AVAILABLE",
|
|
"trigger": "A versioned backend contract justifies generated transport code.",
|
|
"forbiddenWhen": ["Generated DTOs would escape into domain or presentation.", "Contract drift cannot block CI."],
|
|
"boundary": "generated client wrapped by a feature gateway facade and mapper",
|
|
"port": "GeneratedApiFacade",
|
|
"fake": "FakeGeneratedApiAdapter",
|
|
"failureKinds": ["contract-drift", "unsupported-field"],
|
|
"lifecycleMethods": ["cancel-via-AbortSignal"],
|
|
"owner": "project-owner-required",
|
|
"securityPrivacy": ["Generate from an authenticated source.", "Review generator execution and output.", "Do not log request bodies."],
|
|
"bundleBudgetGzipBytes": 16000,
|
|
"fallback": "Existing typed request builder and runtime response schema.",
|
|
"removal": ["Restore handwritten gateway.", "Remove generated output and generator.", "Verify DTOs do not remain in public types."],
|
|
"serverStatePolicy": "query-cache-owned"
|
|
},
|
|
{
|
|
"id": "feature-flag",
|
|
"status": "RECIPE_AVAILABLE",
|
|
"trigger": "A staged rollout or kill switch has a named owner, default and stale policy.",
|
|
"forbiddenWhen": ["A flag is used as authorization.", "Unknown and unavailable behavior is undefined."],
|
|
"boundary": "application feature policy output port",
|
|
"port": "FeatureFlagPort",
|
|
"fake": "FakeFeatureFlagAdapter",
|
|
"failureKinds": ["provider-unavailable", "unknown-flag", "stale-value"],
|
|
"lifecycleMethods": ["dispose-provider-if-installed"],
|
|
"owner": "project-owner-required",
|
|
"securityPrivacy": ["Flags are hints, never access control.", "Minimize targeting attributes.", "Apply consent rules to personal attributes."],
|
|
"bundleBudgetGzipBytes": 10000,
|
|
"fallback": "Typed local default with an explicit stale decision.",
|
|
"removal": ["Resolve the rollout permanently.", "Delete flag key and branches.", "Remove provider composition and dependency."],
|
|
"serverStatePolicy": "policy-cache-only"
|
|
},
|
|
{
|
|
"id": "web-worker",
|
|
"status": "RECIPE_AVAILABLE",
|
|
"trigger": "Profiling shows CPU work blocking the main thread beyond the performance budget.",
|
|
"forbiddenWhen": ["The task is primarily network I/O.", "Cancellation and stale-result ownership are undefined."],
|
|
"boundary": "request/result/cancel output port with a validated message adapter",
|
|
"port": "WorkerTaskPort",
|
|
"fake": "FakeWorkerTaskAdapter",
|
|
"failureKinds": ["crash", "stale-result", "transfer-failure"],
|
|
"lifecycleMethods": ["cancel", "dispose"],
|
|
"owner": "project-owner-required",
|
|
"securityPrivacy": ["Validate worker messages.", "Do not send credentials.", "Bound transferred data and worker count."],
|
|
"bundleBudgetGzipBytes": 14000,
|
|
"fallback": "Chunked or deferred main-thread execution within a measured limit.",
|
|
"removal": ["Stop and dispose workers.", "Restore synchronous facade implementation.", "Remove worker entry and chunk."],
|
|
"serverStatePolicy": "no-server-state"
|
|
},
|
|
{
|
|
"id": "multi-tab",
|
|
"status": "RECIPE_AVAILABLE",
|
|
"trigger": "A documented workflow must synchronize non-sensitive events across tabs.",
|
|
"forbiddenWhen": ["The server is the correct conflict authority.", "Event version and source identity are undefined."],
|
|
"boundary": "versioned browser event output/input adapter",
|
|
"port": "MultiTabPort",
|
|
"fake": "FakeMultiTabAdapter",
|
|
"failureKinds": ["self-echo", "duplicate", "conflict"],
|
|
"lifecycleMethods": ["unsubscribe", "close"],
|
|
"owner": "project-owner-required",
|
|
"securityPrivacy": ["Broadcast no credentials or personal payload.", "Validate versions.", "Treat events as hints rather than authorization."],
|
|
"bundleBudgetGzipBytes": 4000,
|
|
"fallback": "Refresh from the authoritative server on focus.",
|
|
"removal": ["Close channels.", "Remove event registry entries.", "Restore focus-based refresh."],
|
|
"serverStatePolicy": "invalidation-only"
|
|
},
|
|
{
|
|
"id": "browser-permission",
|
|
"status": "RECIPE_AVAILABLE",
|
|
"trigger": "A user-initiated flow requires clipboard, notification or media access.",
|
|
"forbiddenWhen": ["Permission would be requested at boot.", "Denied, dismissed and unsupported UX are not designed."],
|
|
"boundary": "presentation input action through a browser capability output port",
|
|
"port": "BrowserPermissionPort",
|
|
"fake": "FakeBrowserPermissionAdapter",
|
|
"failureKinds": ["denied", "dismissed", "unsupported"],
|
|
"lifecycleMethods": ["stop-media-tracks-if-opened"],
|
|
"owner": "project-owner-required",
|
|
"securityPrivacy": ["Require an explicit user gesture.", "Minimize requested scope.", "Do not persist permission as authorization."],
|
|
"bundleBudgetGzipBytes": 3000,
|
|
"fallback": "Manual input or copy/download instruction.",
|
|
"removal": ["Stop acquired resources.", "Remove permission action and adapter.", "Retest denied-path accessibility."],
|
|
"serverStatePolicy": "no-server-state"
|
|
},
|
|
{
|
|
"id": "client-workflow",
|
|
"status": "RECIPE_AVAILABLE",
|
|
"trigger": "A measured cross-page client-only workflow cannot be represented by URL, local state, context or query cache.",
|
|
"forbiddenWhen": ["The store would duplicate server response collections.", "A library is selected before state ownership is documented.", "Zustand and Redux Toolkit would both be installed."],
|
|
"boundary": "workflow-specific local facade; vendor types remain in its adapter",
|
|
"port": "ClientWorkflowPort",
|
|
"fake": "FakeClientWorkflowAdapter",
|
|
"failureKinds": ["reset", "version-mismatch", "server-state-duplication"],
|
|
"lifecycleMethods": ["unsubscribe", "reset"],
|
|
"owner": "project-owner-required",
|
|
"securityPrivacy": ["Persist only explicitly classified workflow fields.", "Never persist credentials.", "Define logout and version reset."],
|
|
"bundleBudgetGzipBytes": 9000,
|
|
"fallback": "URL, component state, context and TanStack Query ownership.",
|
|
"removal": ["Move remaining state to its natural owner.", "Remove facade and one selected store dependency.", "Verify logout/reset."],
|
|
"serverStatePolicy": "reference-only"
|
|
},
|
|
{
|
|
"id": "large-data-ui",
|
|
"status": "RECIPE_AVAILABLE",
|
|
"trigger": "Production-like profiling proves a list or grid exceeds interaction and rendering budgets.",
|
|
"forbiddenWhen": ["Pagination solves the scale requirement.", "Keyboard and screen-reader focus behavior is undefined."],
|
|
"boundary": "presentation facade around virtualizer or data-grid behavior",
|
|
"port": "LargeDataUiFacade",
|
|
"fake": "FakeLargeDataUiAdapter",
|
|
"failureKinds": ["focus-loss", "stale-row", "scale-limit"],
|
|
"lifecycleMethods": ["dispose-observers-if-installed"],
|
|
"owner": "project-owner-required",
|
|
"securityPrivacy": ["Render only authorized rows.", "Do not expose hidden row data to telemetry.", "Preserve accessible row identity."],
|
|
"bundleBudgetGzipBytes": 30000,
|
|
"fallback": "Accessible pagination and bounded result sets.",
|
|
"removal": ["Restore paginated primitive.", "Remove facade adapter and dependency.", "Run keyboard and performance evidence."],
|
|
"serverStatePolicy": "query-cache-owned"
|
|
},
|
|
{
|
|
"id": "analytics-error-sink",
|
|
"status": "RECIPE_AVAILABLE",
|
|
"trigger": "A production provider, consent policy, retention owner and event registry are approved.",
|
|
"forbiddenWhen": ["Consent and essential diagnostics are not separated.", "Arbitrary message or attribute keys can bypass redaction."],
|
|
"boundary": "closed diagnostics/analytics port with provider adapter",
|
|
"port": "AnalyticsErrorSink",
|
|
"fake": "RecordingAnalyticsAdapter",
|
|
"failureKinds": ["consent-denied", "queue-full", "provider-unavailable"],
|
|
"lifecycleMethods": ["flush", "dispose"],
|
|
"owner": "project-owner-required",
|
|
"securityPrivacy": ["Allowlist events and attributes.", "Redact before queueing.", "Apply consent, sampling and retention policy."],
|
|
"bundleBudgetGzipBytes": 25000,
|
|
"fallback": "Existing bounded local diagnostics and best-effort telemetry port.",
|
|
"removal": ["Disable provider delivery.", "Flush or discard by policy.", "Remove adapter, runtime config and dependency."],
|
|
"serverStatePolicy": "no-server-state"
|
|
}
|
|
]
|
|
}
|