From f00d54662f343d4c28570a0a0cc1446491efa117 Mon Sep 17 00:00:00 2001 From: Max Chesnikov Date: Thu, 5 Mar 2026 01:21:45 -0800 Subject: [PATCH 1/2] v1.19 DX 2.0: preset composition, production validator, and reference cookbook --- .../ProductionProfileExample.swift | 29 + Examples/README.md | 105 +--- Package.swift | 6 + README.md | 37 ++ .../Client/NetworkClientPresetDX.swift | 500 ++++++++++++++++++ .../NovaNetworkClient.md | 3 + .../NetworkingCoverageTests+Presets.swift | 44 ++ ...rkingCoverageTests+ReferenceCookbook.swift | 29 + docs/SETUP_GUIDE.md | 65 ++- docs/TRACEABILITY_PACK_v1.19.md | 29 + docs/WHATS_NEW_v1.19.md | 31 ++ docs/dfr/DX_2_0_V1_19_DFR.md | 161 ++++++ 12 files changed, 944 insertions(+), 95 deletions(-) create mode 100644 Examples/ProductionProfile/ProductionProfileExample.swift create mode 100644 Sources/NovaNetworkClient/Networking/Client/NetworkClientPresetDX.swift create mode 100644 Tests/NovaNetworkClientTests/Unit/Networking/NetworkingCoverageTests+ReferenceCookbook.swift create mode 100644 docs/TRACEABILITY_PACK_v1.19.md create mode 100644 docs/WHATS_NEW_v1.19.md create mode 100644 docs/dfr/DX_2_0_V1_19_DFR.md diff --git a/Examples/ProductionProfile/ProductionProfileExample.swift b/Examples/ProductionProfile/ProductionProfileExample.swift new file mode 100644 index 0000000..2fb571b --- /dev/null +++ b/Examples/ProductionProfile/ProductionProfileExample.swift @@ -0,0 +1,29 @@ +import Foundation +import NovaNetworkClient + +@main +struct ProductionProfileExample { + static func main() async { + let generator = NetworkClientProductionProfileGenerator() + let profile = generator.generate( + goal: .offlineFirst, + overlays: [.offlineDurability, .strictReliability], + offlineStoreConfigured: false + ) + + print("Goal: \(profile.goal.rawValue)") + print("Base preset: \(profile.basePreset.kind.rawValue)") + print("Overlays: \(profile.overlays.map { $0.rawValue }.joined(separator: ", "))") + print("Production ready: \(profile.validation.isProductionReady)") + if !profile.validation.issues.isEmpty { + print("Validation issues:") + for issue in profile.validation.issues { + print("- [\(issue.severity.rawValue)] \(issue.code): \(issue.message)") + print(" Recommendation: \(issue.recommendation)") + } + } + + print("\nBootstrap snippet:\n") + print(profile.bootstrapSnippet(includeOfflineStore: true)) + } +} diff --git a/Examples/README.md b/Examples/README.md index a89b609..7d85606 100644 --- a/Examples/README.md +++ b/Examples/README.md @@ -1,107 +1,50 @@ -# Examples +# Examples Reference Cookbook -Runnable examples for `NovaNetworkClient`. +Runnable and test-backed scenarios for `NovaNetworkClient`. -## JSONPlaceholder Coalescing +## Quick Start -Uses the public API [https://jsonplaceholder.typicode.com](https://jsonplaceholder.typicode.com) to show: -- typed decoding; -- request coalescing for identical concurrent requests. - -Run: +Build all examples: ```bash -swift run NovaNetworkClientJSONPlaceholderExample +swift build ``` -## Batch Loading - -Shows `loadBatch` with several JSONPlaceholder endpoints and typed decoding. - -Run: +Run cookbook contract tests: ```bash -swift run NovaNetworkClientBatchTodosExample +swift test --filter NetworkingCoverageTests ``` -## Middleware +## Cookbook Scenarios -Shows request middleware (`beforeSend`) by injecting custom headers and validating them via [https://httpbin.org/anything](https://httpbin.org/anything). +| Scenario ID | Focus | Example target | Contract test | +|---|---|---|---| +| CB-1 | Coalesced typed read | `NovaNetworkClientJSONPlaceholderExample` | `cookbookScenarioCoalescedRequestUsesSingleTransportCall` | +| CB-2 | Preset composition v2 (`base + overlays`) | `NovaNetworkClientDiagnosticsReferenceExample` | `presetV2CompositionAppliesOverlayOrder` | +| CB-3 | Production validator anti-patterns | `NovaNetworkClientProductionProfileExample` | `presetV2ValidatorFlagsOfflineQueueWithoutStoreAsBlocking` | +| CB-4 | Offline queue onboarding baseline | `NovaNetworkClientOfflineQueueExample` | `enqueueWriteQueuesWhenOfflineAndAppliesDefaultIdempotencyKey` | +| CB-5 | Telemetry onboarding baseline | `NovaNetworkClientDiagnosticsReferenceExample` | `telemetryHooksEmitCoalescerRetryAndCancellationContracts` | +| CB-6 | Production profile generator (DX 2.0) | `NovaNetworkClientProductionProfileExample` | `cookbookScenarioProductionProfileForOfflineFirstRequiresStore` | -Run: +## Run Commands ```bash +swift run NovaNetworkClientJSONPlaceholderExample +swift run NovaNetworkClientBatchTodosExample swift run NovaNetworkClientMiddlewareExample -``` - -## Offline Queue - -Shows `enqueueWrite` with durable `DiskOfflineWriteStore` and queue depth inspection. - -Run: - -```bash swift run NovaNetworkClientOfflineQueueExample -``` - -## WebSocket - -Shows realtime connect/send/receive over a public echo endpoint (`wss://ws.postman-echo.com/raw`) with state observation. - -Run: - -```bash swift run NovaNetworkClientWebSocketExample -``` - -Optional endpoint override: - -```bash -NOVA_WS_URL=wss://ws.ifelse.io swift run NovaNetworkClientWebSocketExample -``` - -## Reference: Auth Refresh - -Shows a reference app flow where an expired bearer token gets a `401`, refreshes token state, and retries with `NetworkClientPreset.restHeavy`. - -Run: - -```bash swift run NovaNetworkClientAuthRefreshReferenceExample -``` - -## Reference: Reconnect Recovery - -Shows a reference app flow for WebSocket reconnect recovery with queue pressure diagnostics and telemetry stream output. - -Run: - -```bash swift run NovaNetworkClientReconnectRecoveryReferenceExample -``` - -Optional endpoint override: - -```bash -NOVA_WS_URL=wss://ws.ifelse.io swift run NovaNetworkClientReconnectRecoveryReferenceExample -``` - -## Reference: Offline Replay - -Shows `NetworkClientPreset.offlineFirst` with durable writes and replay/metrics inspection. - -Run: - -```bash swift run NovaNetworkClientOfflineReplayReferenceExample +swift run NovaNetworkClientDiagnosticsReferenceExample +swift run NovaNetworkClientProductionProfileExample ``` -## Reference: Observability and Diagnostics - -Shows request event stream + telemetry hooks + runtime policy update events as a diagnostics baseline. - -Run: +Optional WebSocket endpoint override: ```bash -swift run NovaNetworkClientDiagnosticsReferenceExample +NOVA_WS_URL=wss://ws.ifelse.io swift run NovaNetworkClientWebSocketExample +NOVA_WS_URL=wss://ws.ifelse.io swift run NovaNetworkClientReconnectRecoveryReferenceExample ``` diff --git a/Package.swift b/Package.swift index c6c56fc..c5ec696 100644 --- a/Package.swift +++ b/Package.swift @@ -19,6 +19,7 @@ let package = Package( .executable(name: "NovaNetworkClientReconnectRecoveryReferenceExample", targets: ["NovaNetworkClientReconnectRecoveryReferenceExample"]), .executable(name: "NovaNetworkClientOfflineReplayReferenceExample", targets: ["NovaNetworkClientOfflineReplayReferenceExample"]), .executable(name: "NovaNetworkClientDiagnosticsReferenceExample", targets: ["NovaNetworkClientDiagnosticsReferenceExample"]), + .executable(name: "NovaNetworkClientProductionProfileExample", targets: ["NovaNetworkClientProductionProfileExample"]), ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. @@ -87,5 +88,10 @@ let package = Package( dependencies: ["NovaNetworkClient"], path: "Examples/DiagnosticsReference" ), + .executableTarget( + name: "NovaNetworkClientProductionProfileExample", + dependencies: ["NovaNetworkClient"], + path: "Examples/ProductionProfile" + ), ] ) diff --git a/README.md b/README.md index e4ca473..09f3e0c 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ When multiple callers ask for the same resource at the same time, only one under - [Telemetry Contract v2](docs/TELEMETRY_CONTRACT_V2.md) - [v1.15 Traceability Pack](docs/TRACEABILITY_PACK_v1.15.md) - [v1.16 Traceability Pack](docs/TRACEABILITY_PACK_v1.16.md) +- [v1.19 Traceability Pack](docs/TRACEABILITY_PACK_v1.19.md) ## Product Delivery Templates @@ -71,7 +72,14 @@ targets: [ - `NetworkClientPreset.restHeavy` - `NetworkClientPreset.realtimeHeavy` - `NetworkClientPreset.offlineFirst` +- Presets v2 composition model (`base preset + overlays`) via: + - `NetworkClientPreset.compose(base:overlays:)` + - `NetworkClientPresetOverlayKind` - Safe preset override points via `NetworkClientPreset.RequestOverrides` (merge-only overrides). +- Production onboarding helpers: + - `NetworkClientProductionProfileGenerator` + - `NetworkClientPresetValidator` / `validateProductionReadiness` + - anti-pattern validation report with blocking vs warning findings. - Testable retry behavior via injectable clock and random generator. - Data and typed `Decodable` loading APIs. - Typed error mapping overloads (`errorMapper`). @@ -142,6 +150,35 @@ let payload = try await client.load( ) ``` +## DX 2.0 Production Profile Quick Start (v1.19) + +```swift +import Foundation +import NovaNetworkClient + +let profile = NetworkClientProductionProfileGenerator().generate( + goal: .offlineFirst, + overlays: [.offlineDurability, .strictReliability], + offlineStoreConfigured: true +) + +guard profile.validation.isProductionReady else { + for issue in profile.validation.issues { + print("[\(issue.severity.rawValue)] \(issue.code): \(issue.message)") + } + fatalError("Fix production validation issues before rollout.") +} + +let preset = profile.composedPreset +let client = NetworkClient( + transport: Transport(), + retryPolicy: preset.retryPolicy, + defaultCachePolicy: preset.defaultCachePolicy, + offlineWriteStore: DiskOfflineWriteStore(directoryURL: queueURL) +) +await client.applyRuntimePolicy(from: preset) +``` + ## Examples ### 1) Typed GET Request diff --git a/Sources/NovaNetworkClient/Networking/Client/NetworkClientPresetDX.swift b/Sources/NovaNetworkClient/Networking/Client/NetworkClientPresetDX.swift new file mode 100644 index 0000000..6a19de1 --- /dev/null +++ b/Sources/NovaNetworkClient/Networking/Client/NetworkClientPresetDX.swift @@ -0,0 +1,500 @@ +import Foundation + +/// Named overlays for the preset v2 composition model (`base preset + overlays`). +public enum NetworkClientPresetOverlayKind: String, Sendable, CaseIterable { + case lowLatency = "low_latency" + case strictReliability = "strict_reliability" + case offlineDurability = "offline_durability" + case highThroughput = "high_throughput" +} + +/// Overlay payload that can be composed on top of a base ``NetworkClientPreset``. +public struct NetworkClientPresetOverlay: Sendable { + /// Overlay identity. + public let kind: NetworkClientPresetOverlayKind + /// Optional retry policy replacement. + public let retryPolicy: RetryPolicy? + /// Optional default cache policy replacement. + public let defaultCachePolicy: CachePolicy? + /// Optional runtime policy override (merged field-by-field). + public let runtimePolicy: NetworkClientRuntimePolicy? + /// Merge-only request overrides. + public let requestOverrides: NetworkClientPreset.RequestOverrides + /// User-facing tradeoff notes introduced by this overlay. + public let tradeoffs: [String] + + /// Creates an overlay for preset composition. + public init( + kind: NetworkClientPresetOverlayKind, + retryPolicy: RetryPolicy? = nil, + defaultCachePolicy: CachePolicy? = nil, + runtimePolicy: NetworkClientRuntimePolicy? = nil, + requestOverrides: NetworkClientPreset.RequestOverrides = .init(), + tradeoffs: [String] = [] + ) { + self.kind = kind + self.retryPolicy = retryPolicy + self.defaultCachePolicy = defaultCachePolicy + self.runtimePolicy = runtimePolicy + self.requestOverrides = requestOverrides + self.tradeoffs = tradeoffs + } +} + +/// Severity level for preset validation issues. +public enum NetworkClientPresetValidationSeverity: String, Sendable { + case warning + case error +} + +/// Single validation issue reported by ``NetworkClientPresetValidator``. +public struct NetworkClientPresetValidationIssue: Sendable { + /// Stable issue code for CI/reporting. + public let code: String + /// Issue severity. + public let severity: NetworkClientPresetValidationSeverity + /// Human-readable issue description. + public let message: String + /// Suggested remediation path. + public let recommendation: String + + /// Creates a validation issue. + public init( + code: String, + severity: NetworkClientPresetValidationSeverity, + message: String, + recommendation: String + ) { + self.code = code + self.severity = severity + self.message = message + self.recommendation = recommendation + } +} + +/// Aggregate validation result for production-readiness checks. +public struct NetworkClientPresetValidationReport: Sendable { + /// All findings generated by the validator. + public let issues: [NetworkClientPresetValidationIssue] + + /// Creates a validation report. + public init(issues: [NetworkClientPresetValidationIssue]) { + self.issues = issues + } + + /// Returns `true` when the report has no blocking (`error`) issues. + public var isProductionReady: Bool { + !issues.contains { $0.severity == .error } + } + + /// Returns only blocking (`error`) issues. + public var blockingIssues: [NetworkClientPresetValidationIssue] { + issues.filter { $0.severity == .error } + } +} + +/// Production onboarding goals used by ``NetworkClientProductionProfileGenerator``. +public enum NetworkClientProductionGoal: String, Sendable { + case restAPI = "rest_api" + case realtime = "realtime" + case offlineFirst = "offline_first" +} + +/// Generated onboarding artifact that includes composed config and validation output. +public struct NetworkClientProductionProfile: Sendable { + /// Goal used to generate this profile. + public let goal: NetworkClientProductionGoal + /// Base preset chosen for this profile. + public let basePreset: NetworkClientPreset + /// Overlays applied to the base preset. + public let overlays: [NetworkClientPresetOverlayKind] + /// Final composed preset. + public let composedPreset: NetworkClientPreset + /// Validation report for anti-pattern checks. + public let validation: NetworkClientPresetValidationReport + + /// Creates a generated production profile. + public init( + goal: NetworkClientProductionGoal, + basePreset: NetworkClientPreset, + overlays: [NetworkClientPresetOverlayKind], + composedPreset: NetworkClientPreset, + validation: NetworkClientPresetValidationReport + ) { + self.goal = goal + self.basePreset = basePreset + self.overlays = overlays + self.composedPreset = composedPreset + self.validation = validation + } + + /// Generates a copy-paste bootstrap snippet for setup onboarding. + public func bootstrapSnippet( + presetVariableName: String = "preset", + transportExpression: String = "Transport()", + includeOfflineStore: Bool = false + ) -> String { + let basePresetName: String + switch basePreset.kind { + case .restHeavy: + basePresetName = "restHeavy" + case .realtimeHeavy: + basePresetName = "realtimeHeavy" + case .offlineFirst: + basePresetName = "offlineFirst" + } + let overlaysLiteral = overlays.map { ".\($0)" }.joined(separator: ", ") + let overlaysLine = overlays.isEmpty + ? "let overlays: [NetworkClientPresetOverlayKind] = []" + : "let overlays: [NetworkClientPresetOverlayKind] = [\(overlaysLiteral)]" + let storeLine = includeOfflineStore ? ",\n offlineWriteStore: DiskOfflineWriteStore(directoryURL: queueURL)" : "" + return """ + \(overlaysLine) + let \(presetVariableName) = NetworkClientPreset.compose( + base: .\(basePresetName), + overlays: overlays + ) + + let client = NetworkClient( + transport: \(transportExpression), + retryPolicy: \(presetVariableName).retryPolicy, + defaultCachePolicy: \(presetVariableName).defaultCachePolicy\(storeLine) + ) + await client.applyRuntimePolicy(from: \(presetVariableName)) + """ + } +} + +/// Preset v2 composition engine (`base preset + overlays`). +public enum NetworkClientPresetComposer { + /// Composes a base preset with overlays applied in sequence. + public static func compose( + base: NetworkClientPreset, + overlays: [NetworkClientPresetOverlay] + ) -> NetworkClientPreset { + var retryPolicy = base.retryPolicy + var defaultCachePolicy = base.defaultCachePolicy + var runtimePolicy = base.runtimePolicy + var requestOptions = base.defaultRequestOptions + var tradeoffs = base.tradeoffs + + for overlay in overlays { + if let value = overlay.retryPolicy { + retryPolicy = value + } + if let value = overlay.defaultCachePolicy { + defaultCachePolicy = value + } + if let value = overlay.runtimePolicy { + runtimePolicy = merge(base: runtimePolicy, override: value) + } + requestOptions = requestOptions.applying(overlay.requestOverrides) + tradeoffs.append(contentsOf: overlay.tradeoffs) + } + + return NetworkClientPreset( + kind: base.kind, + retryPolicy: retryPolicy, + defaultCachePolicy: defaultCachePolicy, + runtimePolicy: runtimePolicy, + defaultRequestOptions: requestOptions, + tradeoffs: unique(tradeoffs) + ) + } + + private static func merge( + base: NetworkClientRuntimePolicy, + override: NetworkClientRuntimePolicy + ) -> NetworkClientRuntimePolicy { + NetworkClientRuntimePolicy( + retryPolicy: override.retryPolicy ?? base.retryPolicy, + deadlineBudgetSeconds: override.deadlineBudgetSeconds ?? base.deadlineBudgetSeconds, + circuitBreakerPolicy: override.circuitBreakerPolicy ?? base.circuitBreakerPolicy, + coalescingPolicy: override.coalescingPolicy ?? base.coalescingPolicy + ) + } + + private static func unique(_ values: [String]) -> [String] { + var seen: Set = [] + var result: [String] = [] + for value in values where seen.insert(value).inserted { + result.append(value) + } + return result + } +} + +/// Validator for production profile anti-pattern checks. +public enum NetworkClientPresetValidator { + /// Validates a composed preset for production-readiness and anti-patterns. + public static func validate( + preset: NetworkClientPreset, + overlays: [NetworkClientPresetOverlayKind] = [], + expectsOfflineDurability: Bool = false, + offlineStoreConfigured: Bool = false + ) -> NetworkClientPresetValidationReport { + var issues: [NetworkClientPresetValidationIssue] = [] + let options = preset.defaultRequestOptions + let queueEnabled = options.offlineQueuePolicy.mode != .disabled + + if preset.retryPolicy.maxAttempts < 2 { + issues.append(.init( + code: "AP-RETRY-001", + severity: .warning, + message: "Retry policy has no practical retry budget for transient failures.", + recommendation: "Use maxAttempts >= 2 for production unless endpoint semantics prohibit retries." + )) + } + + if options.rateLimitPolicy == nil { + issues.append(.init( + code: "AP-RATE-001", + severity: .warning, + message: "No rate limit policy is configured in default request options.", + recommendation: "Set RequestExecutionOptions.rateLimitPolicy to protect upstreams during client bursts." + )) + } + + if preset.runtimePolicy.circuitBreakerPolicy == nil && options.circuitBreakerPolicy == nil { + issues.append(.init( + code: "AP-CB-001", + severity: .warning, + message: "Circuit breaker is not configured at runtime or request level.", + recommendation: "Enable circuit breaker policy to reduce cascading failures." + )) + } + + if options.deadlineBudgetSeconds == nil && preset.runtimePolicy.deadlineBudgetSeconds == nil { + issues.append(.init( + code: "AP-DEADLINE-001", + severity: .warning, + message: "No deadline budget configured for request execution.", + recommendation: "Define a runtime or request deadline budget to avoid unbounded waits." + )) + } + + if queueEnabled && !offlineStoreConfigured { + issues.append(.init( + code: "AP-OFFLINE-001", + severity: .error, + message: "Offline queue is enabled but no durable offline write store is configured.", + recommendation: "Configure DiskOfflineWriteStore (or custom OfflineWriteStore) before enabling offline queue." + )) + } + + if queueEnabled && options.idempotencyPolicy == nil { + issues.append(.init( + code: "AP-IDEMPOTENCY-001", + severity: .warning, + message: "Offline queue is enabled without idempotency policy.", + recommendation: "Configure IdempotencyPolicy to reduce replay duplication risk." + )) + } + + if expectsOfflineDurability && !queueEnabled { + issues.append(.init( + code: "AP-OFFLINE-002", + severity: .error, + message: "Offline-first goal selected, but offline queue is disabled.", + recommendation: "Enable offline queue mode (`enqueueWhenOffline` or `alwaysEnqueue`)." + )) + } + + if overlays.contains(.lowLatency) && overlays.contains(.offlineDurability) { + issues.append(.init( + code: "AP-OVERLAY-001", + severity: .warning, + message: "Selected overlays combine low-latency and offline-durability priorities.", + recommendation: "Verify SLA tradeoffs and tune queue/rate limits for your dominant workload." + )) + } + + return .init(issues: issues) + } +} + +/// Onboarding generator that returns production-ready presets and validation output. +public struct NetworkClientProductionProfileGenerator: Sendable { + /// Creates a generator instance. + public init() {} + + /// Generates a production profile for the selected goal. + public func generate( + goal: NetworkClientProductionGoal, + overlays explicitOverlays: [NetworkClientPresetOverlayKind] = [], + offlineStoreConfigured: Bool = false + ) -> NetworkClientProductionProfile { + let basePreset: NetworkClientPreset + let recommended: [NetworkClientPresetOverlayKind] + + switch goal { + case .restAPI: + basePreset = .restHeavy + recommended = [.strictReliability] + case .realtime: + basePreset = .realtimeHeavy + recommended = [.lowLatency, .highThroughput] + case .offlineFirst: + basePreset = .offlineFirst + recommended = [.offlineDurability, .strictReliability] + } + + let mergedKinds = Self.unique(explicitOverlays.isEmpty ? recommended : explicitOverlays) + let composedPreset = NetworkClientPreset.compose(base: basePreset, overlays: mergedKinds) + let validation = NetworkClientPresetValidator.validate( + preset: composedPreset, + overlays: mergedKinds, + expectsOfflineDurability: goal == .offlineFirst, + offlineStoreConfigured: offlineStoreConfigured + ) + + return .init( + goal: goal, + basePreset: basePreset, + overlays: mergedKinds, + composedPreset: composedPreset, + validation: validation + ) + } + + private static func unique(_ values: [NetworkClientPresetOverlayKind]) -> [NetworkClientPresetOverlayKind] { + var seen: Set = [] + var result: [NetworkClientPresetOverlayKind] = [] + for value in values where seen.insert(value).inserted { + result.append(value) + } + return result + } +} + +public extension NetworkClientPreset { + /// Returns a preset overlay descriptor by kind. + static func overlay(_ kind: NetworkClientPresetOverlayKind) -> NetworkClientPresetOverlay { + switch kind { + case .lowLatency: + return .init( + kind: kind, + retryPolicy: RetryPolicy( + maxAttempts: 2, + retryBudget: 2, + retryNonIdempotentRequests: false, + baseDelayNanoseconds: 80_000_000, + maxDelayNanoseconds: 500_000_000, + maxRetryAfterNanoseconds: 5_000_000_000, + respectRetryAfterHeader: true, + jitterRange: 0.95...1.1 + ), + runtimePolicy: .init( + deadlineBudgetSeconds: 1.5, + coalescingPolicy: .init(dedupeTTLSeconds: 0.15) + ), + requestOverrides: .init( + priority: .high, + rateLimitPolicy: RateLimitPolicy(maxRequests: 40, intervalSeconds: 1) + ), + tradeoffs: [ + "Overlay low-latency: tighter deadlines and lighter retries can increase fast-fail frequency." + ] + ) + case .strictReliability: + let reliabilityCircuitBreaker = CircuitBreakerPolicy( + scope: .host, + failureThreshold: 4, + cooldownSeconds: 6, + halfOpenJitterSeconds: 0.8, + probePolicy: .singleProbe + ) + return .init( + kind: kind, + runtimePolicy: .init(circuitBreakerPolicy: reliabilityCircuitBreaker), + requestOverrides: .init( + rateLimitPolicy: RateLimitPolicy(maxRequests: 10, intervalSeconds: 1), + idempotencyPolicy: .init(keyStrategy: .fingerprintDigest) + ), + tradeoffs: [ + "Overlay strict-reliability: stronger protective limits may throttle burst traffic earlier." + ] + ) + case .offlineDurability: + let queuePolicy = OfflineQueuePolicy( + mode: .enqueueWhenOffline, + maxEntries: 8_000, + ttlSeconds: 14 * 24 * 60 * 60, + maxReplayAttempts: 10, + replayConflictPolicy: .manualReview, + replayDedupeWindowSeconds: 48 * 60 * 60, + replayPriority: .critical, + replaySchedulerPolicy: .init() + ) + return .init( + kind: kind, + defaultCachePolicy: .staleWhileRevalidate(maxAge: 20, staleAge: 180), + requestOverrides: .init( + idempotencyPolicy: .init(keyStrategy: .fingerprintDigest), + offlineQueuePolicy: queuePolicy + ), + tradeoffs: [ + "Overlay offline-durability: larger replay horizon improves delivery guarantees but increases local storage." + ] + ) + case .highThroughput: + let throughputCircuitBreaker = CircuitBreakerPolicy( + scope: .host, + failureThreshold: 6, + cooldownSeconds: 4, + halfOpenJitterSeconds: 0.4, + probePolicy: .parallelProbes(maxConcurrent: 3) + ) + return .init( + kind: kind, + runtimePolicy: .init(circuitBreakerPolicy: throughputCircuitBreaker), + requestOverrides: .init( + capacityScheduling: .queueByPriority, + rateLimitPolicy: RateLimitPolicy(maxRequests: 50, intervalSeconds: 1) + ), + tradeoffs: [ + "Overlay high-throughput: increased parallel pressure may require tighter upstream monitoring." + ] + ) + } + } + + /// Composes a base preset with overlays addressed by kind. + static func compose( + base: NetworkClientPreset, + overlays kinds: [NetworkClientPresetOverlayKind] + ) -> NetworkClientPreset { + let overlays = kinds.map(Self.overlay(_:)) + return NetworkClientPresetComposer.compose(base: base, overlays: overlays) + } + + /// Validates the preset for production-readiness and common anti-patterns. + func validateProductionReadiness( + overlays: [NetworkClientPresetOverlayKind] = [], + expectsOfflineDurability: Bool = false, + offlineStoreConfigured: Bool = false + ) -> NetworkClientPresetValidationReport { + NetworkClientPresetValidator.validate( + preset: self, + overlays: overlays, + expectsOfflineDurability: expectsOfflineDurability, + offlineStoreConfigured: offlineStoreConfigured + ) + } +} + +private extension RequestExecutionOptions { + func applying(_ overrides: NetworkClientPreset.RequestOverrides) -> RequestExecutionOptions { + RequestExecutionOptions( + coalescerLimitsOverride: coalescerLimitsOverride, + priority: overrides.priority ?? priority, + capacityScheduling: overrides.capacityScheduling ?? capacityScheduling, + coalescingMode: overrides.coalescingMode ?? coalescingMode, + deadlineBudgetSeconds: overrides.deadlineBudgetSeconds ?? deadlineBudgetSeconds, + circuitBreakerPolicy: overrides.circuitBreakerPolicy ?? circuitBreakerPolicy, + rateLimitPolicy: overrides.rateLimitPolicy ?? rateLimitPolicy, + idempotencyPolicy: overrides.idempotencyPolicy ?? idempotencyPolicy, + offlineQueuePolicy: overrides.offlineQueuePolicy ?? offlineQueuePolicy + ) + } +} diff --git a/Sources/NovaNetworkClient/NovaNetworkClient.docc/NovaNetworkClient.md b/Sources/NovaNetworkClient/NovaNetworkClient.docc/NovaNetworkClient.md index d168e49..7ed9fee 100644 --- a/Sources/NovaNetworkClient/NovaNetworkClient.docc/NovaNetworkClient.md +++ b/Sources/NovaNetworkClient/NovaNetworkClient.docc/NovaNetworkClient.md @@ -23,6 +23,9 @@ Use ``NetworkClient`` for a batteries-included integration with `URLSession`. ### Networking - ``NetworkClient`` +- ``NetworkClientPreset`` +- ``NetworkClientPresetOverlayKind`` +- ``NetworkClientProductionProfileGenerator`` - ``CachePolicy`` - ``NetworkClientEvent`` - ``APIRequest`` diff --git a/Tests/NovaNetworkClientTests/Unit/Networking/NetworkingCoverageTests+Presets.swift b/Tests/NovaNetworkClientTests/Unit/Networking/NetworkingCoverageTests+Presets.swift index d72064b..3bf8a6d 100644 --- a/Tests/NovaNetworkClientTests/Unit/Networking/NetworkingCoverageTests+Presets.swift +++ b/Tests/NovaNetworkClientTests/Unit/Networking/NetworkingCoverageTests+Presets.swift @@ -82,4 +82,48 @@ extension NetworkingCoverageTests { #expect(update.changedFields.contains("circuit_breaker_policy")) #expect(update.changedFields.contains("coalescing_policy")) } + + @Test + func presetV2CompositionAppliesOverlayOrder() { + let composed = NetworkClientPreset.compose( + base: .restHeavy, + overlays: [.strictReliability, .lowLatency] + ) + + #expect(composed.defaultRequestOptions.priority == .high) + #expect(composed.defaultRequestOptions.rateLimitPolicy?.maxRequests == 40) + #expect(composed.retryPolicy.maxAttempts == 2) + #expect(composed.runtimePolicy.deadlineBudgetSeconds == 1.5) + #expect(composed.tradeoffs.contains { $0.contains("low-latency") }) + } + + @Test + func presetV2ValidatorFlagsOfflineQueueWithoutStoreAsBlocking() { + let composed = NetworkClientPreset.compose( + base: .offlineFirst, + overlays: [.offlineDurability] + ) + let report = composed.validateProductionReadiness( + overlays: [.offlineDurability], + expectsOfflineDurability: true, + offlineStoreConfigured: false + ) + + #expect(report.isProductionReady == false) + #expect(report.blockingIssues.contains { $0.code == "AP-OFFLINE-001" }) + } + + @Test + func productionProfileGeneratorBuildsValidatedRealtimeProfile() { + let profile = NetworkClientProductionProfileGenerator().generate( + goal: .realtime, + offlineStoreConfigured: false + ) + + #expect(profile.basePreset.kind == .realtimeHeavy) + #expect(profile.overlays.contains(.lowLatency)) + #expect(profile.composedPreset.defaultRequestOptions.priority == .high) + #expect(profile.validation.blockingIssues.isEmpty) + #expect(profile.bootstrapSnippet().contains("NetworkClientPreset.compose")) + } } diff --git a/Tests/NovaNetworkClientTests/Unit/Networking/NetworkingCoverageTests+ReferenceCookbook.swift b/Tests/NovaNetworkClientTests/Unit/Networking/NetworkingCoverageTests+ReferenceCookbook.swift new file mode 100644 index 0000000..a0acbd7 --- /dev/null +++ b/Tests/NovaNetworkClientTests/Unit/Networking/NetworkingCoverageTests+ReferenceCookbook.swift @@ -0,0 +1,29 @@ +import Foundation +import Testing +@testable import NovaNetworkClient + +extension NetworkingCoverageTests { + @Test + func cookbookScenarioCoalescedRequestUsesSingleTransportCall() async throws { + let transport = StubNetworkTransport(delayNanos: 120_000_000, response: .success(Data("ok".utf8))) + let client = NetworkClient(transport: transport) + let request = APIRequest(method: .get, url: URL(string: "https://example.com/cookbook/coalesced")!) + + async let first = client.load(request: request, authScope: "cookbook") + async let second = client.load(request: request, authScope: "cookbook") + let _ = try await (first, second) + + #expect(await transport.calls() == 1) + } + + @Test + func cookbookScenarioProductionProfileForOfflineFirstRequiresStore() { + let profile = NetworkClientProductionProfileGenerator().generate( + goal: .offlineFirst, + offlineStoreConfigured: false + ) + + #expect(profile.validation.isProductionReady == false) + #expect(profile.validation.blockingIssues.contains { $0.code == "AP-OFFLINE-001" }) + } +} diff --git a/docs/SETUP_GUIDE.md b/docs/SETUP_GUIDE.md index 18bf67f..2b2cc3e 100644 --- a/docs/SETUP_GUIDE.md +++ b/docs/SETUP_GUIDE.md @@ -7,20 +7,21 @@ This guide covers full setup and practical usage of `NovaNetworkClient`. 1. [Requirements](#requirements) 2. [Installation (SwiftPM)](#installation-swiftpm) 3. [Minimal Setup](#minimal-setup) -4. [Building Requests](#building-requests) -5. [Loading Raw Data](#loading-raw-data) -6. [Loading Decodable Models](#loading-decodable-models) -7. [How Coalescing Works](#how-coalescing-works) -8. [Cancellation Policies](#cancellation-policies) -9. [Fingerprint Policy](#fingerprint-policy) -10. [Retry Policy](#retry-policy) -11. [Cache Policies](#cache-policies) -12. [Offline Queue (Write Requests)](#offline-queue-write-requests) -13. [Observability (Events + Metrics)](#observability-events--metrics) -14. [Error Handling](#error-handling) -15. [Using Custom Transport](#using-custom-transport) -16. [Using `RequestCoalescer` Directly](#using-requestcoalescer-directly) -17. [Testing](#testing) +4. [Production Profile Generator (DX 2.0)](#production-profile-generator-dx-20) +5. [Building Requests](#building-requests) +6. [Loading Raw Data](#loading-raw-data) +7. [Loading Decodable Models](#loading-decodable-models) +8. [How Coalescing Works](#how-coalescing-works) +9. [Cancellation Policies](#cancellation-policies) +10. [Fingerprint Policy](#fingerprint-policy) +11. [Retry Policy](#retry-policy) +12. [Cache Policies](#cache-policies) +13. [Offline Queue (Write Requests)](#offline-queue-write-requests) +14. [Observability (Events + Metrics)](#observability-events--metrics) +15. [Error Handling](#error-handling) +16. [Using Custom Transport](#using-custom-transport) +17. [Using `RequestCoalescer` Directly](#using-requestcoalescer-directly) +18. [Testing](#testing) ## Requirements @@ -59,6 +60,42 @@ import NovaNetworkClient let client = NetworkClient() ``` +## Production Profile Generator (DX 2.0) + +Use presets v2 (`base + overlays`) plus anti-pattern checks for faster production onboarding. + +```swift +let profile = NetworkClientProductionProfileGenerator().generate( + goal: .restAPI, + overlays: [.strictReliability], + offlineStoreConfigured: false +) + +guard profile.validation.isProductionReady else { + for issue in profile.validation.issues { + print("[\(issue.severity.rawValue)] \(issue.code): \(issue.message)") + } + fatalError("Invalid production profile. Resolve validator findings first.") +} + +let preset = profile.composedPreset +let client = NetworkClient( + retryPolicy: preset.retryPolicy, + defaultCachePolicy: preset.defaultCachePolicy +) +await client.applyRuntimePolicy(from: preset) +``` + +You can also compose manually: + +```swift +let preset = NetworkClientPreset.compose( + base: .realtimeHeavy, + overlays: [.lowLatency, .highThroughput] +) +let report = preset.validateProductionReadiness(overlays: [.lowLatency, .highThroughput]) +``` + `NetworkClient` default configuration: - `transport`: `Transport()` (`URLSession.shared`) diff --git a/docs/TRACEABILITY_PACK_v1.19.md b/docs/TRACEABILITY_PACK_v1.19.md new file mode 100644 index 0000000..7f37fa6 --- /dev/null +++ b/docs/TRACEABILITY_PACK_v1.19.md @@ -0,0 +1,29 @@ +# Traceability Pack v1.19 (DX 2.0) + +## DFR +- `docs/dfr/DX_2_0_V1_19_DFR.md` + +## Requirement -> Code Mapping +| Requirement ID | Code references | +|---|---| +| FR-1 | `Sources/NovaNetworkClient/Networking/Client/NetworkClientPresetDX.swift` (`NetworkClientPresetComposer`, `NetworkClientPreset.compose`) | +| FR-2 | `Sources/NovaNetworkClient/Networking/Client/NetworkClientPresetDX.swift` (`NetworkClientPresetValidator`, validation report models) | +| FR-3 | `Sources/NovaNetworkClient/Networking/Client/NetworkClientPresetDX.swift` (`NetworkClientProductionProfileGenerator`, `NetworkClientProductionProfile`) | +| FR-4 | `Examples/README.md`, `Examples/ProductionProfile/ProductionProfileExample.swift`, `Package.swift` | +| UR-1 | `NetworkClientProductionProfile.bootstrapSnippet` | +| UR-2 | `NetworkClientPresetValidationIssue` | +| AR-1 | `AP-OFFLINE-001` validator rule | + +## Requirement -> Test Mapping +| Requirement ID | Test ID | File | +|---|---|---| +| FR-1 | T-19.1 `presetV2CompositionAppliesOverlayOrder` | `Tests/NovaNetworkClientTests/Unit/Networking/NetworkingCoverageTests+Presets.swift` | +| FR-2 | T-19.2 `presetV2ValidatorFlagsOfflineQueueWithoutStoreAsBlocking` | `Tests/NovaNetworkClientTests/Unit/Networking/NetworkingCoverageTests+Presets.swift` | +| FR-3 | T-19.3 `productionProfileGeneratorBuildsValidatedRealtimeProfile` | `Tests/NovaNetworkClientTests/Unit/Networking/NetworkingCoverageTests+Presets.swift` | +| FR-4 | T-19.4 `cookbookScenarioCoalescedRequestUsesSingleTransportCall` | `Tests/NovaNetworkClientTests/Unit/Networking/NetworkingCoverageTests+ReferenceCookbook.swift` | +| AR-1 | T-19.5 `cookbookScenarioProductionProfileForOfflineFirstRequiresStore` | `Tests/NovaNetworkClientTests/Unit/Networking/NetworkingCoverageTests+ReferenceCookbook.swift` | + +## Coverage and Validation +- Unit tests: run `swift test` +- Coverage gate: run `swift test --enable-code-coverage` +- E2E gate: run `RUN_E2E_TESTS=1 swift test --filter E2ECoverageTests` diff --git a/docs/WHATS_NEW_v1.19.md b/docs/WHATS_NEW_v1.19.md new file mode 100644 index 0000000..7b514ea --- /dev/null +++ b/docs/WHATS_NEW_v1.19.md @@ -0,0 +1,31 @@ +# What's New in 1.19 + +## DX 2.0: Presets v2 (`base + overlays`) +- Added composable preset model: + - `NetworkClientPreset.compose(base:overlays:)` + - `NetworkClientPresetOverlayKind` + - `NetworkClientPresetOverlay` +- Overlay application is deterministic and merge-only for request options/runtime policy fields. + +## Production Onboarding Improvements +- Added `NetworkClientProductionProfileGenerator` to generate production setup profiles by goal (`restAPI`, `realtime`, `offlineFirst`). +- Added bootstrap snippet generation via `NetworkClientProductionProfile.bootstrapSnippet(...)`. +- Added `NetworkClientPresetValidator` with anti-pattern checks and severity levels: + - Missing durable offline store with enabled offline queue (blocking). + - Missing rate-limit/circuit-breaker/deadline guardrails (warnings). + - Overlay priority conflict hints (warnings). + +## Examples -> Reference Cookbook +- Updated `Examples/README.md` to cookbook format with scenario IDs and contract-test mapping. +- Added new runnable onboarding example: + - `NovaNetworkClientProductionProfileExample` + +## Traceability (DFR -> Tests) +- Added DFR: `docs/dfr/DX_2_0_V1_19_DFR.md` +- Added traceability pack: `docs/TRACEABILITY_PACK_v1.19.md` +- Added/updated tests: + - `presetV2CompositionAppliesOverlayOrder` + - `presetV2ValidatorFlagsOfflineQueueWithoutStoreAsBlocking` + - `productionProfileGeneratorBuildsValidatedRealtimeProfile` + - `cookbookScenarioCoalescedRequestUsesSingleTransportCall` + - `cookbookScenarioProductionProfileForOfflineFirstRequiresStore` diff --git a/docs/dfr/DX_2_0_V1_19_DFR.md b/docs/dfr/DX_2_0_V1_19_DFR.md new file mode 100644 index 0000000..ef1a40b --- /dev/null +++ b/docs/dfr/DX_2_0_V1_19_DFR.md @@ -0,0 +1,161 @@ +# DFR: DX 2.0 (v1.19) + +## 1. Metadata +- Feature name: DX 2.0 Production Setup Acceleration +- Owner: Networking Platform +- Stakeholders: iOS Platform, QA, Developer Experience +- Status: `In Development` +- Target version/build: `v1.19` +- Related links: + - Design: `N/A (repo-driven)` + - API contract: `Sources/NovaNetworkClient/Networking/Client/NetworkClientPresetDX.swift` + - Experiment: `N/A` + - Legal/compliance: `N/A` + +## 2. Goal and Scope +### Goal +Reduce time-to-first-production-setup by introducing composable presets, explicit production validation, and cookbook-style runnable references. + +### Non-goals +- No runtime auto-migration of existing app configs. +- No behavioral changes in existing v1 preset defaults unless overlays are explicitly used. + +### Definition of Done +- [x] DFR updated and approved +- [x] Code implemented +- [x] Tests added/updated per matrix +- [x] Telemetry implemented and verified +- [x] "What's New" added/updated +- [x] Rollout plan documented + +### MVP / V1 / Nice-to-have +- MVP: Preset v2 composition model (`base + overlays`) and validator. +- V1: Production profile generator and onboarding snippets. +- Nice-to-have: Additional opinionated overlays for vertical-specific traffic patterns. + +## 3. User Value +### User problem +Teams spend significant setup time selecting resilient defaults and validating production readiness. Misconfiguration risks include missing offline store for queued writes, no retry/rate controls, and ad-hoc copy-paste onboarding. + +DX 2.0 provides a guided path: generate profile -> validate anti-patterns -> bootstrap from snippet -> verify via cookbook tests. + +### Success metrics +| Metric | Baseline | Target | Measurement method | +|---|---:|---:|---| +| Time to first production-ready preset setup | Manual setup flow | -35% | Internal onboarding sessions / setup checklist timing | +| Blocking config errors caught before rollout | Low | +80% detection | Validator reports in CI/onboarding | +| Cookbook scenario verification coverage | Partial | 100% DX scenarios mapped | Requirement-to-test matrix | + +## 4. Rollout, Dependencies, Risks +### Rollout plan +- Feature flag: Not required (library API additive) +- Initial rollout percentage: 100% for package consumers +- Segments: All integrators on v1.19+ +- Ramp plan: Release with docs + examples + test coverage +- Rollback trigger: Revert v1.19 DX APIs if blocking regressions are found + +### Dependencies +- Internal: Existing `NetworkClientPreset`, `RequestExecutionOptions`, `RuntimePolicy` +- External: None + +### Risks and mitigations +| Risk | Impact | Likelihood | Mitigation | +|---|---|---|---| +| Overlay semantics misunderstood | Medium | Medium | Explicit docs + generator snippets + validator output | +| False confidence from partial validation | High | Low | Blocking/error severity for unsafe offline queue setup | +| Regression in preset behavior | High | Low | Contract tests for existing v1 presets and new v2 composition | + +## 5. Requirements + +### Functional requirements (FR) +| ID | Requirement | Acceptance criteria | Trace links | +|---|---|---|---| +| FR-1 | Provide preset v2 composition model (`base + overlays`) | API composes a base preset with ordered overlays and returns final `NetworkClientPreset` | `NetworkClientPresetComposer`, `NetworkClientPreset.compose` | +| FR-2 | Provide production validator | Validator outputs issues with severity and blocking status for anti-patterns | `NetworkClientPresetValidator` | +| FR-3 | Provide onboarding production profile generator | Generator outputs base preset, overlays, composed preset, validation, bootstrap snippet | `NetworkClientProductionProfileGenerator` | +| FR-4 | Provide cookbook reference examples | `Examples/README.md` maps runnable scenarios to test contracts | `Examples/README.md`, new example target | + +### UX requirements (UR) +| ID | Requirement | Acceptance criteria | Trace links | +|---|---|---|---| +| UR-1 | Onboarding must be actionable | Generated profile includes copy-paste setup snippet | `NetworkClientProductionProfile.bootstrapSnippet` | +| UR-2 | Findings must be understandable | Validation issue includes stable code + message + recommendation | `NetworkClientPresetValidationIssue` | + +### Data requirements (DR) +| ID | Requirement | Acceptance criteria | Trace links | +|---|---|---|---| +| DR-1 | Validation output is machine-readable | `NetworkClientPresetValidationReport` exposes issues and blocking subset | `NetworkClientPresetValidationReport` | + +### Analytics requirements (AR) +| ID | Requirement | Acceptance criteria | Trace links | +|---|---|---|---| +| AR-1 | No false production-ready signal for unsafe offline setup | Missing store with enabled offline queue returns blocking issue | `AP-OFFLINE-001`, tests | + +### Non-functional requirements (NFR) +| ID | Requirement | Acceptance criteria | Trace links | +|---|---|---|---| +| NFR-1 | Backward compatibility | Existing v1 presets and APIs continue to behave unchanged without overlays | Preset contract tests | +| NFR-2 | Testability | DX scenarios are covered by deterministic unit tests | `NetworkingCoverageTests+Presets.swift`, `+ReferenceCookbook.swift` | + +### Edge cases (EC) +| ID | Scenario | Expected behavior | Trace links | +|---|---|---|---| +| EC-1 | Offline-first selected but offline queue disabled | Validator returns blocking error | `AP-OFFLINE-002` | +| EC-2 | Low-latency and offline-durability overlays combined | Validator returns warning about mixed priorities | `AP-OVERLAY-001` | + +## 6. State Machine and Flows +### States +- `draft_profile` +- `generated_profile` +- `validated_ready` +- `validated_blocked` + +### Transitions +| From | Trigger | To | Notes | +|---|---|---|---| +| `draft_profile` | Generate profile | `generated_profile` | Base + overlays composed | +| `generated_profile` | Validate (no blocking issues) | `validated_ready` | Ready for production setup | +| `generated_profile` | Validate (blocking issues) | `validated_blocked` | Requires remediation | + +### State to UI/Actions/Analytics mapping +| State | UI | Allowed actions | Analytics | +|---|---|---|---| +| `generated_profile` | Show composed config summary | Apply overlays, run validator | None | +| `validated_ready` | Show green-ready status | Bootstrap setup | None | +| `validated_blocked` | Show blocking findings | Fix config and re-validate | None | + +## 7. Engineering Notes +- Overlay order is deterministic and applied sequentially. +- Runtime policy merge follows existing runtime store semantics (override non-nil fields). +- Validator intentionally starts with pragmatic anti-pattern coverage and stable issue codes. + +## 8. Test Matrix +| Requirement ID | Test ID | Test type (`unit/integration/ui`) | Owner | Status | +|---|---|---|---|---| +| FR-1 | T-19.1 `presetV2CompositionAppliesOverlayOrder` | unit | Platform | passing | +| FR-2 | T-19.2 `presetV2ValidatorFlagsOfflineQueueWithoutStoreAsBlocking` | unit | Platform | passing | +| FR-3 | T-19.3 `productionProfileGeneratorBuildsValidatedRealtimeProfile` | unit | Platform | passing | +| FR-4 | T-19.4 `cookbookScenarioCoalescedRequestUsesSingleTransportCall` | unit | Platform | passing | +| AR-1 | T-19.5 `cookbookScenarioProductionProfileForOfflineFirstRequiresStore` | unit | Platform | passing | + +### Negative tests +- T-19.2 validates blocking error when offline queue is enabled without durable store. +- T-19.5 validates offline-first generator output fails readiness without store. + +### Regression risks +- Existing v1 preset contracts remain covered by `presetsExposeSafeDefaultsAndTradeoffs`. +- Runtime policy application telemetry remains covered by `applyRuntimePolicyFromPresetEmitsPolicyUpdateTelemetry`. + +## 9. Release Notes Input ("What's New") +### Customer impact +- Faster production onboarding with fewer unsafe setup mistakes. + +### User-facing changes +- New preset v2 composition APIs, validator, and production profile generator. +- New cookbook example for production profile generation. + +### Behavior changes / migration notes +- Existing APIs unchanged. New features are additive and opt-in. + +### Known limitations +- Validator rules are opinionated and focused on common anti-patterns; teams may add extra internal checks. From 813ebfeba16cc99c1a4ce95ab4e58f6f1af5879e Mon Sep 17 00:00:00 2001 From: Max Chesnikov Date: Thu, 5 Mar 2026 01:23:44 -0800 Subject: [PATCH 2/2] delete docs --- docs/dfr/DX_2_0_V1_19_DFR.md | 161 ----------------------------------- 1 file changed, 161 deletions(-) delete mode 100644 docs/dfr/DX_2_0_V1_19_DFR.md diff --git a/docs/dfr/DX_2_0_V1_19_DFR.md b/docs/dfr/DX_2_0_V1_19_DFR.md deleted file mode 100644 index ef1a40b..0000000 --- a/docs/dfr/DX_2_0_V1_19_DFR.md +++ /dev/null @@ -1,161 +0,0 @@ -# DFR: DX 2.0 (v1.19) - -## 1. Metadata -- Feature name: DX 2.0 Production Setup Acceleration -- Owner: Networking Platform -- Stakeholders: iOS Platform, QA, Developer Experience -- Status: `In Development` -- Target version/build: `v1.19` -- Related links: - - Design: `N/A (repo-driven)` - - API contract: `Sources/NovaNetworkClient/Networking/Client/NetworkClientPresetDX.swift` - - Experiment: `N/A` - - Legal/compliance: `N/A` - -## 2. Goal and Scope -### Goal -Reduce time-to-first-production-setup by introducing composable presets, explicit production validation, and cookbook-style runnable references. - -### Non-goals -- No runtime auto-migration of existing app configs. -- No behavioral changes in existing v1 preset defaults unless overlays are explicitly used. - -### Definition of Done -- [x] DFR updated and approved -- [x] Code implemented -- [x] Tests added/updated per matrix -- [x] Telemetry implemented and verified -- [x] "What's New" added/updated -- [x] Rollout plan documented - -### MVP / V1 / Nice-to-have -- MVP: Preset v2 composition model (`base + overlays`) and validator. -- V1: Production profile generator and onboarding snippets. -- Nice-to-have: Additional opinionated overlays for vertical-specific traffic patterns. - -## 3. User Value -### User problem -Teams spend significant setup time selecting resilient defaults and validating production readiness. Misconfiguration risks include missing offline store for queued writes, no retry/rate controls, and ad-hoc copy-paste onboarding. - -DX 2.0 provides a guided path: generate profile -> validate anti-patterns -> bootstrap from snippet -> verify via cookbook tests. - -### Success metrics -| Metric | Baseline | Target | Measurement method | -|---|---:|---:|---| -| Time to first production-ready preset setup | Manual setup flow | -35% | Internal onboarding sessions / setup checklist timing | -| Blocking config errors caught before rollout | Low | +80% detection | Validator reports in CI/onboarding | -| Cookbook scenario verification coverage | Partial | 100% DX scenarios mapped | Requirement-to-test matrix | - -## 4. Rollout, Dependencies, Risks -### Rollout plan -- Feature flag: Not required (library API additive) -- Initial rollout percentage: 100% for package consumers -- Segments: All integrators on v1.19+ -- Ramp plan: Release with docs + examples + test coverage -- Rollback trigger: Revert v1.19 DX APIs if blocking regressions are found - -### Dependencies -- Internal: Existing `NetworkClientPreset`, `RequestExecutionOptions`, `RuntimePolicy` -- External: None - -### Risks and mitigations -| Risk | Impact | Likelihood | Mitigation | -|---|---|---|---| -| Overlay semantics misunderstood | Medium | Medium | Explicit docs + generator snippets + validator output | -| False confidence from partial validation | High | Low | Blocking/error severity for unsafe offline queue setup | -| Regression in preset behavior | High | Low | Contract tests for existing v1 presets and new v2 composition | - -## 5. Requirements - -### Functional requirements (FR) -| ID | Requirement | Acceptance criteria | Trace links | -|---|---|---|---| -| FR-1 | Provide preset v2 composition model (`base + overlays`) | API composes a base preset with ordered overlays and returns final `NetworkClientPreset` | `NetworkClientPresetComposer`, `NetworkClientPreset.compose` | -| FR-2 | Provide production validator | Validator outputs issues with severity and blocking status for anti-patterns | `NetworkClientPresetValidator` | -| FR-3 | Provide onboarding production profile generator | Generator outputs base preset, overlays, composed preset, validation, bootstrap snippet | `NetworkClientProductionProfileGenerator` | -| FR-4 | Provide cookbook reference examples | `Examples/README.md` maps runnable scenarios to test contracts | `Examples/README.md`, new example target | - -### UX requirements (UR) -| ID | Requirement | Acceptance criteria | Trace links | -|---|---|---|---| -| UR-1 | Onboarding must be actionable | Generated profile includes copy-paste setup snippet | `NetworkClientProductionProfile.bootstrapSnippet` | -| UR-2 | Findings must be understandable | Validation issue includes stable code + message + recommendation | `NetworkClientPresetValidationIssue` | - -### Data requirements (DR) -| ID | Requirement | Acceptance criteria | Trace links | -|---|---|---|---| -| DR-1 | Validation output is machine-readable | `NetworkClientPresetValidationReport` exposes issues and blocking subset | `NetworkClientPresetValidationReport` | - -### Analytics requirements (AR) -| ID | Requirement | Acceptance criteria | Trace links | -|---|---|---|---| -| AR-1 | No false production-ready signal for unsafe offline setup | Missing store with enabled offline queue returns blocking issue | `AP-OFFLINE-001`, tests | - -### Non-functional requirements (NFR) -| ID | Requirement | Acceptance criteria | Trace links | -|---|---|---|---| -| NFR-1 | Backward compatibility | Existing v1 presets and APIs continue to behave unchanged without overlays | Preset contract tests | -| NFR-2 | Testability | DX scenarios are covered by deterministic unit tests | `NetworkingCoverageTests+Presets.swift`, `+ReferenceCookbook.swift` | - -### Edge cases (EC) -| ID | Scenario | Expected behavior | Trace links | -|---|---|---|---| -| EC-1 | Offline-first selected but offline queue disabled | Validator returns blocking error | `AP-OFFLINE-002` | -| EC-2 | Low-latency and offline-durability overlays combined | Validator returns warning about mixed priorities | `AP-OVERLAY-001` | - -## 6. State Machine and Flows -### States -- `draft_profile` -- `generated_profile` -- `validated_ready` -- `validated_blocked` - -### Transitions -| From | Trigger | To | Notes | -|---|---|---|---| -| `draft_profile` | Generate profile | `generated_profile` | Base + overlays composed | -| `generated_profile` | Validate (no blocking issues) | `validated_ready` | Ready for production setup | -| `generated_profile` | Validate (blocking issues) | `validated_blocked` | Requires remediation | - -### State to UI/Actions/Analytics mapping -| State | UI | Allowed actions | Analytics | -|---|---|---|---| -| `generated_profile` | Show composed config summary | Apply overlays, run validator | None | -| `validated_ready` | Show green-ready status | Bootstrap setup | None | -| `validated_blocked` | Show blocking findings | Fix config and re-validate | None | - -## 7. Engineering Notes -- Overlay order is deterministic and applied sequentially. -- Runtime policy merge follows existing runtime store semantics (override non-nil fields). -- Validator intentionally starts with pragmatic anti-pattern coverage and stable issue codes. - -## 8. Test Matrix -| Requirement ID | Test ID | Test type (`unit/integration/ui`) | Owner | Status | -|---|---|---|---|---| -| FR-1 | T-19.1 `presetV2CompositionAppliesOverlayOrder` | unit | Platform | passing | -| FR-2 | T-19.2 `presetV2ValidatorFlagsOfflineQueueWithoutStoreAsBlocking` | unit | Platform | passing | -| FR-3 | T-19.3 `productionProfileGeneratorBuildsValidatedRealtimeProfile` | unit | Platform | passing | -| FR-4 | T-19.4 `cookbookScenarioCoalescedRequestUsesSingleTransportCall` | unit | Platform | passing | -| AR-1 | T-19.5 `cookbookScenarioProductionProfileForOfflineFirstRequiresStore` | unit | Platform | passing | - -### Negative tests -- T-19.2 validates blocking error when offline queue is enabled without durable store. -- T-19.5 validates offline-first generator output fails readiness without store. - -### Regression risks -- Existing v1 preset contracts remain covered by `presetsExposeSafeDefaultsAndTradeoffs`. -- Runtime policy application telemetry remains covered by `applyRuntimePolicyFromPresetEmitsPolicyUpdateTelemetry`. - -## 9. Release Notes Input ("What's New") -### Customer impact -- Faster production onboarding with fewer unsafe setup mistakes. - -### User-facing changes -- New preset v2 composition APIs, validator, and production profile generator. -- New cookbook example for production profile generation. - -### Behavior changes / migration notes -- Existing APIs unchanged. New features are additive and opt-in. - -### Known limitations -- Validator rules are opinionated and focused on common anti-patterns; teams may add extra internal checks.