Files
clean-architecture-frontend…/config/recipes/frontend-capability-recipes.json
T

234 lines
13 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",
"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": "outbound connection plus inbound validated event adapter",
"port": "RealtimePort",
"fake": "FakeRealtimeAdapter",
"failureKinds": ["disconnect", "duplicate", "out-of-order", "auth-expiry"],
"lifecycleMethods": ["unsubscribe"],
"owner": "project-owner-required",
"securityPrivacy": ["Validate every event envelope.", "Never place credentials in URLs or telemetry.", "Refresh authorization through the session boundary."],
"bundleBudgetGzipBytes": 12000,
"fallback": "Bounded polling or explicitly stale UI.",
"removal": ["Remove composition registration.", "Remove adapter and vendor dependency.", "Run recipe-removal and production-bundle gates."],
"serverStatePolicy": "query-cache-owned"
},
{
"id": "offline-indexeddb",
"status": "RECIPE_AVAILABLE",
"trigger": "A product requirement needs durable offline data or a durable command queue beyond small public preferences.",
"forbiddenWhen": ["The data contains credentials.", "The browser would connect directly to a database or object store.", "A normal HTTP cache is sufficient."],
"boundary": "application-owned versioned repository output port",
"port": "VersionedOfflineRepository",
"fake": "MemoryOfflineRepository",
"failureKinds": ["quota", "corruption", "migration-rollback"],
"lifecycleMethods": ["close"],
"owner": "project-owner-required",
"securityPrivacy": ["Classify persisted fields.", "Encrypting in the same client is not a credential protection boundary.", "Version and test every migration."],
"bundleBudgetGzipBytes": 8000,
"fallback": "Online-only query path with an explicit offline state.",
"removal": ["Stop writes.", "Migrate or purge owned stores.", "Remove repository composition and dependency."],
"serverStatePolicy": "reference-or-command-only"
},
{
"id": "service-worker-pwa",
"status": "RECIPE_AVAILABLE",
"trigger": "Installability or a measured offline-shell requirement is approved with cache ownership.",
"forbiddenWhen": ["Hosting cache and worker cache ownership conflict.", "Update and rollback UX is undefined."],
"boundary": "bootstrap update controller and cache policy adapter",
"port": "ServiceWorkerUpdatePort",
"fake": "FakeServiceWorkerUpdateAdapter",
"failureKinds": ["stale-worker", "update-loop", "offline-fallback"],
"lifecycleMethods": ["unregister", "rollback"],
"owner": "project-owner-required",
"securityPrivacy": ["Never cache authenticated API responses by default.", "Bind cache names to release identity.", "Fail closed on malformed update metadata."],
"bundleBudgetGzipBytes": 10000,
"fallback": "Normal network application with hosting cache headers.",
"removal": ["Deploy an unregister migration.", "Delete owned caches.", "Remove worker registration and manifest."],
"serverStatePolicy": "network-cache-policy-only"
},
{
"id": "file-transfer",
"status": "RECIPE_AVAILABLE",
"trigger": "The product accepts or delivers files with progress and cancellation requirements.",
"forbiddenWhen": ["Allowed size and MIME policy is missing.", "Long-lived credentials would be embedded in URLs."],
"boundary": "application file transfer output port behind an authorized backend protocol",
"port": "FileTransferPort",
"fake": "FakeFileTransferAdapter",
"failureKinds": ["size-rejection", "type-rejection", "abort", "expired-url"],
"lifecycleMethods": ["cancel-via-AbortSignal"],
"owner": "project-owner-required",
"securityPrivacy": ["Treat MIME as untrusted metadata.", "Use short-lived opaque resource identifiers.", "Redact file names when classified as personal data."],
"bundleBudgetGzipBytes": 6000,
"fallback": "Standard request with bounded size and no background continuation.",
"removal": ["Cancel active transfers.", "Remove route actions and composition.", "Remove transfer dependency."],
"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"
}
]
}