From d286802402bab8d9d3dad0040f45b4eebee024b0 Mon Sep 17 00:00:00 2001 From: r3dbars Date: Thu, 10 Sep 2026 17:10:35 -0500 Subject: [PATCH 1/6] Unify anonymous install identity across Sentry and PostHog --- Sources/Observability/AnalyticsReporter.swift | 52 ++++++++++++++----- Sources/Observability/CrashReporter.swift | 4 +- Sources/Observability/InstallIdentity.swift | 48 +++++++++++++++++ Tests/AnalyticsReporterTests.swift | 35 +++++++++++++ scripts/entrypoints/run-tests.sh | 1 + 5 files changed, 125 insertions(+), 15 deletions(-) create mode 100644 Sources/Observability/InstallIdentity.swift diff --git a/Sources/Observability/AnalyticsReporter.swift b/Sources/Observability/AnalyticsReporter.swift index ba4e170b9..6aae34af0 100644 --- a/Sources/Observability/AnalyticsReporter.swift +++ b/Sources/Observability/AnalyticsReporter.swift @@ -1,11 +1,39 @@ import Foundation -private struct AnalyticsCaptureRequest: Encodable { +struct AnalyticsCaptureRequest: Encodable { let apiKey: String let event: String let distinctID: String let timestamp: String let properties: [String: String] + var personProperties: [String: String]? = nil + + private struct PropertyKey: CodingKey { + let stringValue: String + var intValue: Int? { nil } + init(_ value: String) { stringValue = value } + init?(stringValue: String) { self.stringValue = stringValue } + init?(intValue: Int) { return nil } + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(apiKey, forKey: .apiKey) + try container.encode(event, forKey: .event) + try container.encode(distinctID, forKey: .distinctID) + try container.encode(timestamp, forKey: .timestamp) + var values = container.nestedContainer(keyedBy: PropertyKey.self, forKey: .properties) + for (key, value) in properties { + try values.encode(value, forKey: PropertyKey(key)) + } + // PostHog creates/updates an anonymous install profile via $set on capture. + // Never accept caller-provided person properties or collect an email. + if let personProperties { + try values.encode(personProperties, forKey: PropertyKey("$set")) + } + try values.encode(true, forKey: PropertyKey("$geoip_disable")) + try values.encode(false, forKey: PropertyKey("$ip")) + } enum CodingKeys: String, CodingKey { case apiKey = "api_key" @@ -25,6 +53,7 @@ struct PendingAnalyticsCapture: Codable, Equatable { var attemptCount: Int var nextRetryAt: TimeInterval? let properties: [String: String] + var personProperties: [String: String]? = nil } struct AnalyticsDeliveryBufferStore { @@ -491,7 +520,6 @@ final class AnalyticsReporter { private let apiKey: String? private let captureHost: String? private static let isoDateFormatter = ISO8601DateFormatter() - private let storageKey = "observability-anonymous-analytics-id" private let sessionID = UUID().uuidString private let session: URLSession private let bufferStore: AnalyticsDeliveryBufferStore @@ -518,15 +546,7 @@ final class AnalyticsReporter { private var needsPersist = false private var pendingPersistWorkItem: DispatchWorkItem? - private lazy var distinctID: String = { - if let existing = userDefaults.string(forKey: storageKey) { - return existing - } - - let newValue = UUID().uuidString - userDefaults.set(newValue, forKey: storageKey) - return newValue - }() + private var distinctID: String { InstallIdentity.id(userDefaults: userDefaults) } func trackEvent(_ event: String, properties: [String: String] = [:]) { guard analyticsEnabled() else { @@ -546,13 +566,15 @@ final class AnalyticsReporter { allowedKeys: policy.allowedProperties ) - let eventProperties = Self.captureProperties( + var eventProperties = Self.captureProperties( sanitizedProperties: sanitizedProperties, distinctID: distinctID, sessionID: sessionID ) let now = currentDate() + let traits = InstallIdentity.traits(userDefaults: userDefaults, now: now) + eventProperties.merge(traits) { current, _ in current } let capture = PendingAnalyticsCapture( id: UUID().uuidString, event: policy.name, @@ -561,7 +583,8 @@ final class AnalyticsReporter { enqueuedAt: now.timeIntervalSince1970, attemptCount: 0, nextRetryAt: nil, - properties: eventProperties + properties: eventProperties, + personProperties: traits ) enqueue(capture) @@ -702,7 +725,8 @@ final class AnalyticsReporter { event: capture.event, distinctID: capture.distinctID, timestamp: capture.timestamp, - properties: capture.properties + properties: capture.properties, + personProperties: capture.personProperties ) guard let data = try? JSONEncoder().encode(payload) else { diff --git a/Sources/Observability/CrashReporter.swift b/Sources/Observability/CrashReporter.swift index c4e6044f5..b33675d8c 100644 --- a/Sources/Observability/CrashReporter.swift +++ b/Sources/Observability/CrashReporter.swift @@ -54,6 +54,7 @@ final class CrashReporter { } SentrySDK.start(options: options) + SentrySDK.setUser(User(userId: InstallIdentity.id())) shared.hasStarted = true @@ -271,7 +272,8 @@ final class CrashReporter { } event.request = nil - event.user = nil + // Replace the entire SDK user object; preserve only the app-generated UUID. + event.user = User(userId: InstallIdentity.id()) event.breadcrumbs = nil event.serverName = nil diff --git a/Sources/Observability/InstallIdentity.swift b/Sources/Observability/InstallIdentity.swift new file mode 100644 index 000000000..9e9646e98 --- /dev/null +++ b/Sources/Observability/InstallIdentity.swift @@ -0,0 +1,48 @@ +import Foundation + +/// App-generated identity only. Never derived from hardware, account, email, or content. +enum InstallIdentity { + static let storageKey = "observability-anonymous-analytics-id" + private static let firstLaunchKey = "observability-first-launch-day" + private static let lock = NSLock() + + static func id(userDefaults: UserDefaults = .standard) -> String { + lock.lock() + defer { lock.unlock() } + // Preserve the existing PostHog UUID byte-for-byte so upgrades do not split people. + if let existing = userDefaults.string(forKey: storageKey), UUID(uuidString: existing) != nil { + return existing + } + let id = UUID().uuidString + userDefaults.set(id, forKey: storageKey) + return id + } + + static func firstLaunchDay(userDefaults: UserDefaults = .standard, now: Date = Date()) -> String { + lock.lock() + defer { lock.unlock() } + if let existing = userDefaults.string(forKey: firstLaunchKey), + existing.range(of: #"^\d{4}-\d{2}-\d{2}$"#, options: .regularExpression) != nil { + return existing + } + let formatter = DateFormatter() + formatter.locale = Locale(identifier: "en_US_POSIX") + formatter.timeZone = TimeZone(secondsFromGMT: 0) + formatter.dateFormat = "yyyy-MM-dd" + let day = formatter.string(from: now) + userDefaults.set(day, forKey: firstLaunchKey) + return day + } + + static func traits(userDefaults: UserDefaults = .standard, now: Date = Date()) -> [String: String] { + [ + "analytics_opt_in": "true", + "app_version": Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "unknown", + "build_revision": AnalyticsRuntimeConfiguration.buildRevision(), + "os_major": "\(ProcessInfo.processInfo.operatingSystemVersion.majorVersion)", + "install_channel": AnalyticsRuntimeConfiguration.buildChannel(), + // For upgrades this is the first observed day with this instrumentation. + "first_launch_at": firstLaunchDay(userDefaults: userDefaults, now: now), + ] + } +} diff --git a/Tests/AnalyticsReporterTests.swift b/Tests/AnalyticsReporterTests.swift index bcee78698..141e8da5a 100644 --- a/Tests/AnalyticsReporterTests.swift +++ b/Tests/AnalyticsReporterTests.swift @@ -1,6 +1,41 @@ import Foundation func testAnalyticsReporter() { + runSuite("Install identity survives upgrades and person traits are content-free") { + let fixture = makeAnalyticsReporterFixture(responses: [.networkFailure]) + defer { fixture.cleanup() } + let existing = UUID().uuidString.lowercased() + fixture.userDefaults.set(existing, forKey: InstallIdentity.storageKey) + assertEqual(InstallIdentity.id(userDefaults: fixture.userDefaults), existing, "preserve the existing UUID exactly") + let first = InstallIdentity.firstLaunchDay(userDefaults: fixture.userDefaults, now: Date(timeIntervalSince1970: 0)) + assertEqual(first, "1970-01-01", "persist only day precision") + assertEqual(InstallIdentity.firstLaunchDay(userDefaults: fixture.userDefaults), first, "first observed launch stays stable") + fixture.reporter.trackEvent("app_launched", properties: ["email": "private@example.com", "$set": "private words"]) + assertTrue(waitUntil { loadBufferedAnalyticsCaptures(from: fixture.bufferURL).count == 1 }, "capture is buffered") + let capture = loadBufferedAnalyticsCaptures(from: fixture.bufferURL).first! + assertEqual(capture.distinctID, existing, "PostHog uses the install UUID") + assertEqual(capture.personProperties?["analytics_opt_in"], "true", "opted-in trait is present") + assertNil(capture.personProperties?["email"], "person never has an email") + let payload = AnalyticsCaptureRequest(apiKey: "test", event: capture.event, distinctID: capture.distinctID, + timestamp: capture.timestamp, properties: capture.properties, personProperties: capture.personProperties) + let data = try! JSONEncoder().encode(payload) + let json = try! JSONSerialization.jsonObject(with: data) as! [String: Any] + let properties = json["properties"] as! [String: Any] + assertEqual((properties["$set"] as? [String: String])?["first_launch_at"], first, "person traits use a nested JSON object") + assertEqual(properties["$geoip_disable"] as? Bool, true, "disable server-derived location enrichment") + assertFalse(String(decoding: data, as: UTF8.self).contains("private"), "caller cannot inject personal properties") + } + + runSuite("Install identity rejects non-UUID legacy values") { + let name = "IdentityTests-\(UUID().uuidString)" + let defaults = UserDefaults(suiteName: name)! + defer { defaults.removePersistentDomain(forName: name) } + defaults.set("private@example.com", forKey: InstallIdentity.storageKey) + let id = InstallIdentity.id(userDefaults: defaults) + assertNotNil(UUID(uuidString: id), "replace invalid identity with a random UUID") + assertEqual(InstallIdentity.id(userDefaults: defaults), id, "replacement is stable") + } + runSuite("AnalyticsRuntimeConfiguration prefers Transcripted overrides before legacy Draft") { let appSupport = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true) .appendingPathComponent("AnalyticsReporterTests-\(UUID().uuidString)", isDirectory: true) diff --git a/scripts/entrypoints/run-tests.sh b/scripts/entrypoints/run-tests.sh index 9ffc49861..f9828294b 100755 --- a/scripts/entrypoints/run-tests.sh +++ b/scripts/entrypoints/run-tests.sh @@ -438,6 +438,7 @@ APP_SOURCES=( "Sources/UI/MenuBar/MenuBarHeaderLayoutPolicy.swift" "Sources/UI/MenuBar/MenuBarHeaderStatusPresentation.swift" "Sources/UI/MenuBar/PasteLastDictationFeedback.swift" + "Sources/Observability/InstallIdentity.swift" "Sources/Observability/AnalyticsReporter.swift" "Sources/Observability/DictationPasteRetryTelemetry.swift" "Sources/Observability/SpeakerRecognitionTelemetry.swift" From cdbe8c1fc664f61e0ca38a1a1a4ed83c24902ea1 Mon Sep 17 00:00:00 2001 From: r3dbars Date: Thu, 10 Sep 2026 17:11:20 -0500 Subject: [PATCH 2/6] Keep capture degradation in health analytics instead of Sentry errors --- Sources/Meeting/MeetingCaptureHealthTelemetry.swift | 7 +++++-- Sources/Meeting/MeetingSessionController.swift | 5 +++-- Sources/Observability/SentryEventPolicy.swift | 5 ----- Tests/MeetingCaptureVolumeDiagnosticsTests.swift | 11 +++++++++++ Tests/SentryEventPolicyTests.swift | 12 ++++++------ 5 files changed, 25 insertions(+), 15 deletions(-) diff --git a/Sources/Meeting/MeetingCaptureHealthTelemetry.swift b/Sources/Meeting/MeetingCaptureHealthTelemetry.swift index 89ea70abf..e0d2e9d34 100644 --- a/Sources/Meeting/MeetingCaptureHealthTelemetry.swift +++ b/Sources/Meeting/MeetingCaptureHealthTelemetry.swift @@ -19,6 +19,7 @@ enum MeetingCaptureHealthTelemetry { let durationSeconds: Double let systemStreamPresent: Bool let stopTimedOut: Bool + var captureOutcome: String = "complete" } struct DegradedReportInput { @@ -35,7 +36,7 @@ enum MeetingCaptureHealthTelemetry { } static func snapshotProperties(_ input: SnapshotInput) -> [String: String] { - input.captureDiagnostics.merging( + var properties = input.captureDiagnostics.merging( sharedProperties( health: input.health, trigger: input.trigger, @@ -46,6 +47,8 @@ enum MeetingCaptureHealthTelemetry { ), uniquingKeysWith: { _, new in new } ) + properties["capture_outcome"] = input.stopTimedOut ? "stop_timed_out" : input.captureOutcome + return properties } static func shouldReportDegraded(_ input: DegradedReportInput) -> Bool { @@ -88,7 +91,7 @@ enum MeetingCaptureHealthTelemetry { ) -> [String: String] { [ "capture_quality": health.captureQuality, - "quality_reason": health.qualityReason, + "quality_reason": health.qualityReason.isEmpty ? "unknown" : health.qualityReason, "duration_bucket": AnalyticsReporter.durationBucket(seconds: durationSeconds), "gap_count_bucket": AnalyticsReporter.countBucket(health.audioGaps), "reason": reason, diff --git a/Sources/Meeting/MeetingSessionController.swift b/Sources/Meeting/MeetingSessionController.swift index ec116d7e5..acc515917 100644 --- a/Sources/Meeting/MeetingSessionController.swift +++ b/Sources/Meeting/MeetingSessionController.swift @@ -1727,7 +1727,8 @@ final class MeetingSessionController: ObservableObject { reason: reason.rawValue, durationSeconds: recordingSnapshot.durationSeconds, systemStreamPresent: files.systemURL != nil, - stopTimedOut: stopResult.didTimeOut + stopTimedOut: stopResult.didTimeOut, + captureOutcome: "cancelled" ) ) ) @@ -3249,7 +3250,7 @@ final class MeetingSessionController: ObservableObject { ) else { return } DiagnosticsTrail.record( - level: .error, + level: .warning, engine: "meeting", event: "recording_capture_degraded", message: "Meeting capture health degraded", diff --git a/Sources/Observability/SentryEventPolicy.swift b/Sources/Observability/SentryEventPolicy.swift index 4df3a77b6..ba902360c 100644 --- a/Sources/Observability/SentryEventPolicy.swift +++ b/Sources/Observability/SentryEventPolicy.swift @@ -216,11 +216,6 @@ struct SentryEventPolicy: Equatable { event: "meeting_start_failed", summary: "Meeting recording could not start." ), - "meeting.recording_capture_degraded": .init( - engine: "meeting", - event: "recording_capture_degraded", - summary: "Meeting capture health degraded." - ), "meeting.recording_stop_timeout": .init( engine: "meeting", event: "recording_stop_timeout", diff --git a/Tests/MeetingCaptureVolumeDiagnosticsTests.swift b/Tests/MeetingCaptureVolumeDiagnosticsTests.swift index 111cfcce8..7690cf22a 100644 --- a/Tests/MeetingCaptureVolumeDiagnosticsTests.swift +++ b/Tests/MeetingCaptureVolumeDiagnosticsTests.swift @@ -479,6 +479,17 @@ func testMeetingCaptureVolumeDiagnostics() { ) } + runSuite("MeetingCaptureHealthTelemetry never leaves outcome or quality reason blank") { + let input = MeetingCaptureHealthTelemetry.SnapshotInput( + captureDiagnostics: [:], health: .init(captureQuality: "good", audioGaps: 0, deviceSwitches: 0), + trigger: "menu", reason: "stop_button", durationSeconds: 30, systemStreamPresent: true, stopTimedOut: false) + assertEqual(MeetingCaptureHealthTelemetry.snapshotProperties(input)["capture_outcome"], "complete", "normal stop is complete") + assertEqual(MeetingCaptureHealthTelemetry.snapshotProperties(input)["quality_reason"], "none", "good health has an explicit reason") + var cancelled = input + cancelled.captureOutcome = "cancelled" + assertEqual(MeetingCaptureHealthTelemetry.snapshotProperties(cancelled)["capture_outcome"], "cancelled", "discard is not a successful completion") + } + runSuite("MeetingCaptureHealthTelemetry builds shared capture health payloads") { let properties = MeetingCaptureHealthTelemetry.snapshotProperties( .init( diff --git a/Tests/SentryEventPolicyTests.swift b/Tests/SentryEventPolicyTests.swift index 94d60c200..830b22695 100644 --- a/Tests/SentryEventPolicyTests.swift +++ b/Tests/SentryEventPolicyTests.swift @@ -139,7 +139,7 @@ func testSentryEventPolicy() { assertEqual(deviceRecoveryTimeout?.summary, "Speech engine device-change recovery timed out.", "device recovery timeouts should be visible in Sentry with privacy-safe route context") assertEqual(recordingInterrupted?.summary, "Dictation recording was interrupted by audio device recovery.", "recording interruptions should be visible in Sentry with privacy-safe route context") assertEqual(meetingStartFailed?.summary, "Meeting recording could not start.", "meeting start failures should be visible without raw device names") - assertEqual(meetingCaptureDegraded?.summary, "Meeting capture health degraded.", "degraded meeting capture should be visible without raw device names") + assertNil(meetingCaptureDegraded, "completed degradation must never open a Sentry error even if a caller logs at error") assertEqual(meetingStopTimeout?.summary, "Meeting recording stop timed out.", "stop timeouts should be visible without raw device names") assertEqual(meetingTranscriptFailed?.summary, "Meeting transcription failed.", "meeting transcript failures should be visible with sanitized context") assertNil(meetingTranscriptSkipped, "expected empty/no-speech meeting outcomes should stay out of Sentry") @@ -329,7 +329,7 @@ func testSentryEventPolicy() { runSuite("SentryEventPolicy diagnosticTags keeps issue 500 volume-drop flags searchable") { let tags = SentryEventPolicy.diagnosticTags( forEngine: "meeting", - event: "recording_capture_degraded", + event: "meeting_transcript_failed", context: [ "default_output_volume_dropped": "true", "default_system_output_volume_dropped": "true", @@ -348,8 +348,8 @@ func testSentryEventPolicy() { ] ) - assertEqual(tags["default_output_volume_dropped"], "true", "output volume drops should be queryable in APPLE-MACOS-1B") - assertEqual(tags["default_system_output_volume_dropped"], "true", "system output drops should be queryable in APPLE-MACOS-1B") + assertEqual(tags["default_output_volume_dropped"], "true", "output volume drops should be queryable on hard failures") + assertEqual(tags["default_system_output_volume_dropped"], "true", "system output drops should be queryable on hard failures") assertEqual(tags["default_input_volume_dropped"], "false", "input volume state should stay available as a control") assertEqual(tags["buffer_success_bucket"], "98_100", "coarse buffer success should distinguish expected silence from write loss") assertEqual(tags["output_ducking_detected"], "true", "ducking classification should stay queryable") @@ -422,7 +422,7 @@ func testSentryEventPolicy() { let enumReason = "preferred_built_in_for_bluetooth_headset" let shortTags = SentryEventPolicy.diagnosticTags( forEngine: "meeting", - event: "recording_capture_degraded", + event: "meeting_transcript_failed", context: ["reason": enumReason] ) assertEqual(shortTags["reason"], enumReason, "short enum-style reasons should pass through untruncated") @@ -430,7 +430,7 @@ func testSentryEventPolicy() { let freeText = String(repeating: "a", count: 400) let cappedTags = SentryEventPolicy.diagnosticTags( forEngine: "meeting", - event: "recording_capture_degraded", + event: "meeting_transcript_failed", context: ["reason": freeText] ) let cappedReason = cappedTags["reason"] From 3751e43f57836c3efa052f0ef9e27ef517c3733e Mon Sep 17 00:00:00 2001 From: r3dbars Date: Thu, 10 Sep 2026 17:18:32 -0500 Subject: [PATCH 3/6] Complete failure taxonomy and correlate reliability reports across sinks --- Resources/analytics-events.psv | 1 + Resources/analytics-reviewed-properties.psv | 6 ++ .../Meeting/MeetingSessionController.swift | 34 +++++---- .../Observability/ActivationTelemetry.swift | 4 +- .../AnalyticsPayloadSanitizer.swift | 3 + Sources/Observability/AnalyticsReporter.swift | 7 +- Sources/Observability/EventReporter.swift | 5 ++ .../PayloadSanitizationCore.swift | 12 +++ Sources/Observability/SentryEventPolicy.swift | 2 +- .../SentryPayloadSanitizer.swift | 2 + Sources/Observability/TelemetryContext.swift | 75 +++++++++++++++++++ Sources/Speech/DictationSession.swift | 1 + Sources/Speech/DictationSessionTypes.swift | 3 + .../Overlay/DictationSessionController.swift | 14 +++- Tests/TelemetryContextTests.swift | 43 +++++++++++ docs/privacy-first-observability.md | 35 +++++++++ scripts/entrypoints/run-tests.sh | 1 + 17 files changed, 227 insertions(+), 21 deletions(-) create mode 100644 Sources/Observability/TelemetryContext.swift create mode 100644 Tests/TelemetryContextTests.swift diff --git a/Resources/analytics-events.psv b/Resources/analytics-events.psv index 1398c3078..5410aea49 100644 --- a/Resources/analytics-events.psv +++ b/Resources/analytics-events.psv @@ -80,6 +80,7 @@ onboarding_reporting_toggle_changed|available,enabled,reporting_kind,step_id onboarding_shown|analytics_available,crash_reporting_available,entrypoint,has_target,meeting_recording_ready,mic_status,model_state,pasteback_status onboarding_step_viewed|flow_elapsed_bucket,model_state,step_id,step_index product_friction_observed|elapsed_bucket,failure_kind,model_state,result,route_shape,stage,surface +reliability_failure_observed|accessibility_permission_granted,app_version,build_revision,correlation_id,failure_kind,failure_stage,input_device_class,mic_permission_granted,os_major,output_device_class,screen_permission_granted,selection_reason,session_id,trigger settings_action_clicked|action_id,page_id settings_capture_library_changed|location_type,page_id settings_feature_discovered|feature_area,page_id,source diff --git a/Resources/analytics-reviewed-properties.psv b/Resources/analytics-reviewed-properties.psv index 00b3ab442..2acba4bae 100644 --- a/Resources/analytics-reviewed-properties.psv +++ b/Resources/analytics-reviewed-properties.psv @@ -7,6 +7,7 @@ # against the compiled allowlist. Marked `merge=union` in .gitattributes; run # python3 scripts/ops/normalize-analytics-taxonomy.py # after a union merge. Lines beginning with # are ignored. +accessibility_permission_granted action action_id action_kind @@ -53,6 +54,7 @@ completion_flow completion_kind cooldown_reason copy_reason +correlation_id crash_reporting_available crash_reporting_enabled cross_app_capture_status @@ -82,6 +84,7 @@ enabled entrypoint failure_code failure_kind +failure_stage feature_area first_artifact_kind first_dictation_saved @@ -101,6 +104,7 @@ location_type meeting_dry_run_completed meeting_recording_ready mic_boost_prompt +mic_permission_granted mic_processed_peak mic_processing mic_raw_peak @@ -158,11 +162,13 @@ route_stability_warning runtime sample_flow_started save_outcome +screen_permission_granted second_artifact_kind selected_input_class selection_overrode_default selection_reason session_active +session_id session_kind session_stage setting_id diff --git a/Sources/Meeting/MeetingSessionController.swift b/Sources/Meeting/MeetingSessionController.swift index acc515917..0db6905cf 100644 --- a/Sources/Meeting/MeetingSessionController.swift +++ b/Sources/Meeting/MeetingSessionController.swift @@ -856,7 +856,7 @@ final class MeetingSessionController: ObservableObject { let pipelineSnapshot = capture.pipelineDiagnosticsSnapshot( overrideSystemAudioStatus: capture.startFailureStage == .systemAudio ? .failed : nil ) - let failureProperties = meetingCaptureAnalyticsProperties(snapshot: pipelineSnapshot).merging( + let failureProperties = TelemetryContext.enrich(event: "meeting_recording_start_failed", properties: meetingCaptureAnalyticsProperties(snapshot: pipelineSnapshot).merging( [ "failure_kind": meetingStartFailureKind( from: failureMessage, @@ -866,7 +866,7 @@ final class MeetingSessionController: ObservableObject { "trigger": trigger.rawValue, ], uniquingKeysWith: { _, new in new } - ) + )) DiagnosticsTrail.record( level: .error, engine: "meeting", @@ -883,7 +883,8 @@ final class MeetingSessionController: ObservableObject { stage: "meeting_start", result: .failed, failureKind: failureProperties["failure_kind"], - modelState: state.diagnosticName + modelState: state.diagnosticName, + context: failureProperties ) transition(to: .error(failureMessage), reason: "capture_start_failed") Self.runtimeDiagnosticsRecorder?.clearSession(kind: "meeting", outcome: "start_failed") @@ -3004,29 +3005,25 @@ final class MeetingSessionController: ObservableObject { if failureKind == .speakerFinalizationFailed || failureKind == .speakerNameFinalizationFailed { activeQueuedTranscriptionJobID = nil let queueDepthBucket = AnalyticsReporter.queueDepthBucket(transcriptionQueue.queuedTranscriptionJobs.count) + let failureTelemetryContext = meetingFailureTelemetryContext(failureKind: failureKind, transcriptionTrigger: transcriptionTrigger) DiagnosticsTrail.record( level: .error, engine: "meeting", event: "speaker_finalization_failed", message: "Meeting speaker naming finalization failed", context: baseDiagnosticsContext( - extra: [ + extra: failureTelemetryContext.merging([ "failure_kind": failureKind.rawValue, "session_stage": "save", "queue_depth": "\(transcriptionQueue.queuedTranscriptionJobs.count)", "queue_depth_bucket": queueDepthBucket, "trigger": transcriptionTrigger.rawValue - ] + ], uniquingKeysWith: { current, _ in current }) ) ) AnalyticsReporter.track( "meeting_speaker_finalization_failed", - properties: [ - "session_stage": "save", - "failure_kind": failureKind.rawValue, - "queue_depth_bucket": queueDepthBucket, - "trigger": transcriptionTrigger.rawValue, - ] + properties: failureTelemetryContext ) trackDetectedPromptOutcome( .speakerFinalizationFailed, @@ -3039,7 +3036,8 @@ final class MeetingSessionController: ObservableObject { stage: "speaker_finalization", result: .failed, failureKind: failureKind.rawValue, - modelState: state.diagnosticName + modelState: state.diagnosticName, + context: failureTelemetryContext ) activeTranscriptionCaptureDiagnostics = nil Self.runtimeDiagnosticsRecorder?.clearSession(kind: "meeting", outcome: "speaker_finalization_failed") @@ -3082,7 +3080,8 @@ final class MeetingSessionController: ObservableObject { stage: "meeting_transcription", result: .failed, failureKind: failureKind.rawValue, - modelState: state.diagnosticName + modelState: state.diagnosticName, + context: failureTelemetryContext ) activeTranscriptionCaptureDiagnostics = nil Self.runtimeDiagnosticsRecorder?.clearSession(kind: "meeting", outcome: "transcript_failed") @@ -3163,6 +3162,10 @@ final class MeetingSessionController: ObservableObject { MeetingCaptureVolumeDiagnostics.measurementScope, uniquingKeysWith: { _, scope in scope } ) + if let id = activeRecordingIdentity?.uuidString { + properties["session_id"] = id + properties["correlation_id"] = id + } properties["gap_count_bucket"] = AnalyticsReporter.countBucket(snapshot.gapCount) properties["route_change_count_bucket"] = AnalyticsReporter.countBucket(snapshot.routeChangeCount) properties["recovery_attempt_bucket"] = AnalyticsReporter.countBucket(snapshot.recoveryAttemptCount) @@ -3173,14 +3176,15 @@ final class MeetingSessionController: ObservableObject { failureKind: MeetingFailureKind, transcriptionTrigger: StartTrigger ) -> [String: String] { - (activeTranscriptionCaptureDiagnostics ?? [:]).merging( + TelemetryContext.enrich(event: "meeting_transcript_failed", properties: (activeTranscriptionCaptureDiagnostics ?? [:]).merging( [ + "failure_stage": failureKind == .speakerFinalizationFailed || failureKind == .speakerNameFinalizationFailed ? "speaker_finalization" : "transcription", "failure_kind": failureKind.rawValue, "queue_depth_bucket": AnalyticsReporter.queueDepthBucket(transcriptionQueue.queuedTranscriptionJobs.count), "trigger": transcriptionTrigger.rawValue, ], uniquingKeysWith: { _, new in new } - ) + )) } private func trackDetectedPromptOutcome( diff --git a/Sources/Observability/ActivationTelemetry.swift b/Sources/Observability/ActivationTelemetry.swift index eac0eb5d1..2e3c9463c 100644 --- a/Sources/Observability/ActivationTelemetry.swift +++ b/Sources/Observability/ActivationTelemetry.swift @@ -524,7 +524,8 @@ enum ProductFrictionTelemetry { failureKind: String? = nil, elapsedBucket: String? = nil, routeShape: String? = nil, - modelState: String? = nil + modelState: String? = nil, + context: [String: String] = [:] ) { var properties = [ "result": result.rawValue, @@ -545,6 +546,7 @@ enum ProductFrictionTelemetry { properties["model_state"] = modelState } + properties.merge(context) { current, _ in current } AnalyticsReporter.track("product_friction_observed", properties: properties) } diff --git a/Sources/Observability/AnalyticsPayloadSanitizer.swift b/Sources/Observability/AnalyticsPayloadSanitizer.swift index 1d19d982f..67a4107c9 100644 --- a/Sources/Observability/AnalyticsPayloadSanitizer.swift +++ b/Sources/Observability/AnalyticsPayloadSanitizer.swift @@ -15,6 +15,9 @@ enum AnalyticsPayloadSanitizer { for (key, value) in properties { guard allowedKeys.contains(key) else { continue } guard !shouldDrop(key: key) else { continue } + if ["session_id", "correlation_id", "install_uuid"].contains(key), PayloadSanitizationCore.uuid(value) == nil { continue } + if ["failure_kind", "failure_stage", "start_failure_stage", "selection_reason", "trigger", "quality_reason", "capture_outcome"].contains(key), + PayloadSanitizationCore.category(value) == nil { continue } let cleaned = sanitizeText(value) guard !cleaned.isEmpty else { continue } diff --git a/Sources/Observability/AnalyticsReporter.swift b/Sources/Observability/AnalyticsReporter.swift index 6aae34af0..cbe3c8d9c 100644 --- a/Sources/Observability/AnalyticsReporter.swift +++ b/Sources/Observability/AnalyticsReporter.swift @@ -520,7 +520,7 @@ final class AnalyticsReporter { private let apiKey: String? private let captureHost: String? private static let isoDateFormatter = ISO8601DateFormatter() - private let sessionID = UUID().uuidString + private let sessionID = TelemetryContext.launchSessionID private let session: URLSession private let bufferStore: AnalyticsDeliveryBufferStore private let userDefaults: UserDefaults @@ -561,9 +561,10 @@ final class AnalyticsReporter { return } + let enrichedProperties = TelemetryContext.enrich(event: event, properties: properties) let sanitizedProperties = AnalyticsPayloadSanitizer.sanitizeProperties( - properties, - allowedKeys: policy.allowedProperties + enrichedProperties, + allowedKeys: policy.allowedProperties.union(TelemetryContext.keys) ) var eventProperties = Self.captureProperties( diff --git a/Sources/Observability/EventReporter.swift b/Sources/Observability/EventReporter.swift index e206ccdb7..e4e41a610 100644 --- a/Sources/Observability/EventReporter.swift +++ b/Sources/Observability/EventReporter.swift @@ -214,6 +214,7 @@ final class EventReporter { mergedContext["build_revision"] = AnalyticsRuntimeConfiguration.buildRevision(infoDictionary: infoDictionary) } + mergedContext = TelemetryContext.enrich(event: event, properties: mergedContext, isFailure: level == .error) let entry = ObservabilityEvent( timestamp: isoFormatter.string(from: Date()), level: level.rawValue, @@ -246,6 +247,10 @@ final class EventReporter { if level == .error, let sentryPolicy = SentryEventPolicy.policy(forEngine: engine, event: event) { + // One canonical analytics counterpart for every allowlisted hard failure, + // including low-level engine failures without a product lifecycle event. + // Both sinks receive the exact same UUIDs and failure taxonomy. + AnalyticsReporter.track("reliability_failure_observed", properties: mergedContext) CrashReporter.shared.captureObservabilityEvent( level: level, engine: sentryPolicy.engine, diff --git a/Sources/Observability/PayloadSanitizationCore.swift b/Sources/Observability/PayloadSanitizationCore.swift index 0274b1392..e68b45772 100644 --- a/Sources/Observability/PayloadSanitizationCore.swift +++ b/Sources/Observability/PayloadSanitizationCore.swift @@ -11,6 +11,18 @@ import Foundation /// Generic free-text patterns live in `PrivacyTextRedactor`; the app-specific /// path profile stays behind `ObservabilityTextRedactor`. enum PayloadSanitizationCore { + static func uuid(_ value: String?) -> String? { + guard let value, UUID(uuidString: value) != nil else { return nil } + return value + } + + static func category(_ value: String?) -> String? { + guard let value, !value.isEmpty, value.count <= 80, + value.range(of: #"^[a-zA-Z0-9][a-zA-Z0-9_.-]*$"#, options: .regularExpression) != nil, + redactAndCap(value, maxValueLength: 80) == value else { return nil } + return value + } + /// Sensitive-key fragments shared by every off-device destination. A value /// is dropped when its lowercased key contains any of these as a substring. /// The Sentry, Analytics, and local sanitizers start from this list and diff --git a/Sources/Observability/SentryEventPolicy.swift b/Sources/Observability/SentryEventPolicy.swift index ba902360c..e98938804 100644 --- a/Sources/Observability/SentryEventPolicy.swift +++ b/Sources/Observability/SentryEventPolicy.swift @@ -16,7 +16,7 @@ struct SentryEventPolicy: Equatable { ) -> [String: String] { guard policy(forEngine: engine, event: event) != nil else { return [:] } - var tags = context.filter { allowedDiagnosticTagKeys.contains($0.key) } + var tags = context.filter { allowedDiagnosticTagKeys.union(TelemetryContext.keys).contains($0.key) } if let waitBucket = AnalyticsReporter.durationBucket(fromMilliseconds: context["wait_ms"]) { tags["wait_bucket"] = waitBucket } diff --git a/Sources/Observability/SentryPayloadSanitizer.swift b/Sources/Observability/SentryPayloadSanitizer.swift index ca637512a..59a22c665 100644 --- a/Sources/Observability/SentryPayloadSanitizer.swift +++ b/Sources/Observability/SentryPayloadSanitizer.swift @@ -21,6 +21,7 @@ enum SentryPayloadSanitizer { for (key, value) in tags { guard !shouldDrop(key: key) else { continue } + if ["session_id", "correlation_id", "install_uuid"].contains(key), PayloadSanitizationCore.uuid(value) == nil { continue } let cleaned = sanitizeText(value) guard !cleaned.isEmpty else { continue } sanitized[key] = cleaned @@ -34,6 +35,7 @@ enum SentryPayloadSanitizer { for (key, value) in context { guard !shouldDrop(key: key) else { continue } + if ["session_id", "correlation_id", "install_uuid"].contains(key), PayloadSanitizationCore.uuid(value) == nil { continue } let cleaned = sanitizeText(value) guard !cleaned.isEmpty else { continue } sanitized[key] = cleaned diff --git a/Sources/Observability/TelemetryContext.swift b/Sources/Observability/TelemetryContext.swift new file mode 100644 index 000000000..b129be2f8 --- /dev/null +++ b/Sources/Observability/TelemetryContext.swift @@ -0,0 +1,75 @@ +import Foundation + +/// Shared metadata contract. Only UUIDs and categorical state cross the reporting boundary. +enum TelemetryContext { + static let launchSessionID = UUID().uuidString + static let keys: Set = [ + "session_id", "correlation_id", "failure_kind", "failure_stage", "start_failure_stage", + "app_version", "build_revision", "os_major", "input_device_class", "output_device_class", + "selection_reason", "mic_permission_granted", "screen_permission_granted", + "accessibility_permission_granted", "trigger", "quality_reason", "capture_outcome", + ] + static let deviceClasses: Set = [ + "built_in", "bluetooth", "usb", "aggregate", "virtual", "continuity", "wired", "external", + "hdmi", "displayport", "airplay", "thunderbolt", "firewire", "pci", "unknown", "none", + ] + + static func permissions() -> [String: String] { + [ + "mic_permission_granted": String(TranscriptedPermissionAccess.isGranted(.microphone)), + "screen_permission_granted": String(TranscriptedPermissionAccess.isGranted(.systemAudioRecording)), + "accessibility_permission_granted": String(TranscriptedPermissionAccess.isGranted(.accessibility)), + ] + } + + static func enrich( + event: String, + properties: [String: String], + isFailure: Bool = false, + environment: [String: String] = permissions() + ) -> [String: String] { + var result = properties + for (key, value) in environment where result[key] == nil { result[key] = value } + result["app_version"] = result["app_version"] ?? (Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "unknown") + result["build_revision"] = result["build_revision"] ?? AnalyticsRuntimeConfiguration.buildRevision() + result["os_major"] = result["os_major"] ?? String(ProcessInfo.processInfo.operatingSystemVersion.majorVersion) + let session = uuid(result["session_id"]) ?? uuid(result["dictation_session_id"]) ?? launchSessionID + result["session_id"] = session + result["correlation_id"] = uuid(result["correlation_id"]) ?? UUID().uuidString + for key in ["input_device_class", "output_device_class"] { + if !deviceClasses.contains(result[key] ?? "") { result[key] = "unknown" } + } + result["selection_reason"] = category(result["selection_reason"]) ?? "unknown" + result["trigger"] = category(result["trigger"]) ?? "unknown" + for key in ["mic_permission_granted", "screen_permission_granted", "accessibility_permission_granted"] { + result[key] = result[key] == "true" ? "true" : "false" + } + let failed = isFailure || event.hasSuffix("_failed") || event == "reliability_failure_observed" + if failed || event == "product_friction_observed" || event == "meeting_capture_health_snapshot" { + let fallbackKind = failed ? (category(event) ?? "unknown") : "none" + result["failure_kind"] = category(result["failure_kind"]) ?? fallbackKind + result["failure_stage"] = category(result["failure_stage"]) + ?? category(result["start_failure_stage"]) ?? category(result["stage"]) + ?? category(result["session_stage"]) ?? stage(for: event) + } + if event == "meeting_capture_health_snapshot" { + result["quality_reason"] = category(result["quality_reason"]) ?? "unknown" + result["capture_outcome"] = category(result["capture_outcome"]) ?? "unknown" + } + return result + } + + static func uuid(_ value: String?) -> String? { PayloadSanitizationCore.uuid(value) } + static func category(_ value: String?) -> String? { PayloadSanitizationCore.category(value) } + + static func stage(for event: String) -> String { + if event.contains("speaker_finalization") { return "speaker_finalization" } + if event.contains("start") || event.contains("mic_not_authorized") { return "start" } + if event.contains("transcript") { return "transcription" } + if event.contains("stop") || event.contains("health") { return "capture_stop" } + if event.contains("delivery") || event.contains("paste") { return "delivery" } + if event.contains("model") || event.contains("prewarm") { return "model_loading" } + if event.contains("recovery") || event.contains("engine") { return "recovery" } + return "unknown" + } +} diff --git a/Sources/Speech/DictationSession.swift b/Sources/Speech/DictationSession.swift index 8dedbb646..f4f26a253 100644 --- a/Sources/Speech/DictationSession.swift +++ b/Sources/Speech/DictationSession.swift @@ -504,6 +504,7 @@ extension DictationSession { for (key, value) in extra { context[key] = value } + context.merge(telemetryContext) { current, _ in current } return context } } diff --git a/Sources/Speech/DictationSessionTypes.swift b/Sources/Speech/DictationSessionTypes.swift index f54721ce1..d8e609984 100644 --- a/Sources/Speech/DictationSessionTypes.swift +++ b/Sources/Speech/DictationSessionTypes.swift @@ -16,6 +16,9 @@ import Foundation @MainActor final class DictationSession: ObservableObject { + /// Correlates the engine-start failure with its owning overlay attempt. + var telemetryContext: [String: String] = [:] + // NOTE: this type deliberately does NOT publish its own lifecycle/state // enum. `DictationSessionController.isDictating` plus the overlay's own // state remain the single source of truth for "is a dictation session diff --git a/Sources/UI/Overlay/DictationSessionController.swift b/Sources/UI/Overlay/DictationSessionController.swift index 302c6027b..b0de3bca7 100644 --- a/Sources/UI/Overlay/DictationSessionController.swift +++ b/Sources/UI/Overlay/DictationSessionController.swift @@ -163,6 +163,11 @@ class DictationSessionController: ObservableObject { } isDictating = true currentDictationSessionID = UUID() + dictationSession.telemetryContext = [ + "session_id": currentDictationSessionID.uuidString, + "correlation_id": currentDictationSessionID.uuidString, + "trigger": trigger.rawValue, + ] stoppedAudioRecovery = nil stoppedAudioRecoveryPreservationSessionID = nil stoppedAudioCheckpointSignal = nil @@ -263,7 +268,8 @@ class DictationSessionController: ObservableObject { result: .blocked, failureKind: failureKind, routeShape: analyticsProperties["route_shape"], - modelState: ProductFrictionTelemetry.modelState(isReady: appState?.sttRouter.isModelLoaded) + modelState: ProductFrictionTelemetry.modelState(isReady: appState?.sttRouter.isModelLoaded), + context: analyticsProperties ) } @@ -2057,6 +2063,9 @@ class DictationSessionController: ObservableObject { private func dictationContext(extra: [String: String] = [:]) -> [String: String] { var context: [String: String] = [ + "session_id": currentDictationSessionID.uuidString, + "correlation_id": currentDictationSessionID.uuidString, + "trigger": currentDictationTrigger.rawValue, "audio_device": appState?.sttRouter.inputDeviceName ?? "" ] if let routeContext = appState?.sttRouter.dictationAudioRouteAnalyticsContext { @@ -2074,6 +2083,9 @@ class DictationSessionController: ObservableObject { private func dictationAnalyticsProperties(extra: [String: String] = [:]) -> [String: String] { var properties = appState?.sttRouter.dictationAudioRouteAnalyticsContext ?? [:] + properties["session_id"] = currentDictationSessionID.uuidString + properties["correlation_id"] = currentDictationSessionID.uuidString + properties["trigger"] = currentDictationTrigger.rawValue for (key, value) in extra { properties[key] = value } diff --git a/Tests/TelemetryContextTests.swift b/Tests/TelemetryContextTests.swift new file mode 100644 index 000000000..97d5d0f78 --- /dev/null +++ b/Tests/TelemetryContextTests.swift @@ -0,0 +1,43 @@ +import Foundation + +func testTelemetryContext() { + let permissions = ["mic_permission_granted": "true", "screen_permission_granted": "false", "accessibility_permission_granted": "true"] + runSuite("Failures preserve identical safe metadata across both sinks") { + let correlation = UUID().uuidString + let properties = TelemetryContext.enrich(event: "meeting_recording_start_failed", properties: [ + "failure_kind": "mic_unavailable", "start_failure_stage": "microphone", "correlation_id": correlation, + "session_id": UUID().uuidString, "input_device_class": "bluetooth", "output_device_class": "built_in", + "selection_reason": "preferredBuiltInForBluetoothHeadset", "trigger": "hotkey", + "transcript_text": "Private content", "speaker_name": "Private Person", "audio_path": "/private/audio.wav", + ], environment: permissions) + let analytics = AnalyticsPayloadSanitizer.sanitizeProperties(properties, allowedKeys: TelemetryContext.keys) + let sentry = SentryEventPolicy.diagnosticTags(forEngine: "meeting", event: "meeting_start_failed", context: properties) + for key in TelemetryContext.keys.subtracting(["quality_reason", "capture_outcome"]) { + assertNotNil(analytics[key], "failure has required field \(key)") + assertEqual(sentry[key], analytics[key], "same \(key) reaches both systems") + } + assertEqual(sentry["correlation_id"], correlation, "join survives filtering") + assertEqual(sentry["failure_stage"], "microphone", "preserve precise start stage") + assertFalse(analytics.values.contains("Private content"), "content is excluded") + assertNil(sentry["audio_path"], "audio paths are excluded") + } + runSuite("Health and friction distinguish unknown metadata from success") { + let health = TelemetryContext.enrich(event: "meeting_capture_health_snapshot", properties: [:], environment: permissions) + assertEqual(health["quality_reason"], "unknown", "missing measurements never imply good health") + assertEqual(health["capture_outcome"], "unknown", "missing outcome never implies completion") + let friction = TelemetryContext.enrich(event: "product_friction_observed", properties: ["stage": "dictation_start", "result": "started"], environment: permissions) + assertEqual(friction["failure_kind"], "none", "normal friction observations are not failures") + assertEqual(friction["failure_stage"], "dictation_start", "stage is explicit") + } + runSuite("Free text cannot impersonate shared taxonomy or identifiers") { + let properties = TelemetryContext.enrich(event: "dictation_start_failed", properties: [ + "session_id": "private@example.com", "correlation_id": "private words", "failure_kind": "Private transcript words", + "input_device_class": "Jane's AirPods", "trigger": "Private meeting title", + ], environment: permissions) + assertNotNil(UUID(uuidString: properties["session_id"]!), "session fallback is a UUID") + assertNotNil(UUID(uuidString: properties["correlation_id"]!), "correlation fallback is a UUID") + assertEqual(properties["input_device_class"], "unknown", "device names never become device classes") + assertEqual(properties["trigger"], "unknown", "free text never becomes a trigger") + assertEqual(properties["failure_kind"], "dictation_start_failed", "failure fallback is a code") + } +} diff --git a/docs/privacy-first-observability.md b/docs/privacy-first-observability.md index 5dcafa0f8..eb3d19c21 100644 --- a/docs/privacy-first-observability.md +++ b/docs/privacy-first-observability.md @@ -81,6 +81,7 @@ This list should match `Resources/analytics-events.psv`, which `Sources/Observability/AnalyticsEventPolicy.swift` compiles into the runtime allowlist. +- `reliability_failure_observed` - `app_launched` - `app_unclean_shutdown_detected` - `app_session_stall_detected` @@ -300,3 +301,37 @@ packets and the already-allowlisted meeting analytics/failure events. They add n process identity, audio content, hardware reads, or newly forwarded events. Live audio compatibility requires the receiving-participant checks in [Meeting Audio QA](qa-issue-500-meeting-audio.md). + +## Shared install and failure metadata + +The app reuses its persisted anonymous install UUID as PostHog `distinct_id` and +Sentry `user.id`. The Sentry sanitizer replaces the full user object with this ID +only. Analytics-enabled captures update a PostHog person through a fixed `$set` +object: `analytics_opt_in`, `app_version`, `build_revision`, `os_major`, +`install_channel`, and `first_launch_at` (UTC day). For existing installs the first +launch day means first observed by this version, not the original installation. +No email is collected. GeoIP enrichment is disabled on new PostHog requests. + +Every app analytics event can carry the common `TelemetryContext.keys` allowlist: +`session_id`, `correlation_id`, `app_version`, `build_revision`, `os_major`, +`input_device_class`, `output_device_class`, `selection_reason`, `trigger`, and +microphone/screen/accessibility permission booleans. Session and correlation IDs +must be app-generated UUIDs. Missing route observations are explicitly `unknown`; +a missing observation is not evidence of a healthy route or a denied permission. +Screen permission reflects the app's cached System Audio Recording grant. + +Failure, friction, and health events also carry `failure_kind` and `failure_stage`. +Health snapshots always carry `quality_reason` and `capture_outcome`; cancelled +captures have their own outcome. `none` means no failure; `unknown` means missing +measurement. Every allowlisted Sentry hard failure has a matching +`reliability_failure_observed` PostHog record using the exact same correlation ID +and taxonomy, even when the low-level failure has no product lifecycle event. +Product lifecycle failures remain available for funnel analysis; do not sum them +with their canonical reliability counterparts. Meeting start, transcription, +speaker finalization, and dictation microphone timeout preserve the same operation +ID across their existing lifecycle reports too. + +Capture degradation is a local warning and a PostHog health observation. It is +never a Sentry error, even if an old producer accidentally requests error level. +Hard start, transcript, audio-loss, stop-timeout, and engine-loop failures keep +their existing Sentry path. No audio-quality or routing policy changes here. diff --git a/scripts/entrypoints/run-tests.sh b/scripts/entrypoints/run-tests.sh index f9828294b..57321501e 100755 --- a/scripts/entrypoints/run-tests.sh +++ b/scripts/entrypoints/run-tests.sh @@ -438,6 +438,7 @@ APP_SOURCES=( "Sources/UI/MenuBar/MenuBarHeaderLayoutPolicy.swift" "Sources/UI/MenuBar/MenuBarHeaderStatusPresentation.swift" "Sources/UI/MenuBar/PasteLastDictationFeedback.swift" + "Sources/Observability/TelemetryContext.swift" "Sources/Observability/InstallIdentity.swift" "Sources/Observability/AnalyticsReporter.swift" "Sources/Observability/DictationPasteRetryTelemetry.swift" From 610b260ae903409b4cae415f2affe2c4ac12b10d Mon Sep 17 00:00:00 2001 From: r3dbars Date: Thu, 10 Sep 2026 17:24:33 -0500 Subject: [PATCH 4/6] Add local Usage and health settings with content-free support diagnostics --- .../Meeting/MeetingSessionController.swift | 3 +- Sources/Observability/AnalyticsReporter.swift | 25 +-- Sources/Observability/UsageHealthStore.swift | 165 ++++++++++++++++++ .../Settings/TranscriptedSettingsView.swift | 6 +- .../Settings/UsageHealthSettingsSection.swift | 90 ++++++++++ .../UI/Shared/SupportDiagnosticsBundle.swift | 62 +++++-- .../Shared/TranscriptedSupportActions.swift | 7 +- Tests/SupportDiagnosticsBundleTests.swift | 4 +- Tests/UsageHealthStoreTests.swift | 49 ++++++ scripts/entrypoints/run-tests.sh | 1 + 10 files changed, 377 insertions(+), 35 deletions(-) create mode 100644 Sources/Observability/UsageHealthStore.swift create mode 100644 Sources/UI/Settings/UsageHealthSettingsSection.swift create mode 100644 Tests/UsageHealthStoreTests.swift diff --git a/Sources/Meeting/MeetingSessionController.swift b/Sources/Meeting/MeetingSessionController.swift index 0db6905cf..a97131040 100644 --- a/Sources/Meeting/MeetingSessionController.swift +++ b/Sources/Meeting/MeetingSessionController.swift @@ -1214,7 +1214,8 @@ final class MeetingSessionController: ObservableObject { "trigger": recordingSnapshot.trigger.rawValue, ], uniquingKeysWith: { _, new in new } - ) + ), + usageDurationSeconds: recordingSnapshot.durationSeconds ) var healthSnapshotProperties = MeetingCaptureHealthTelemetry.snapshotProperties( .init( diff --git a/Sources/Observability/AnalyticsReporter.swift b/Sources/Observability/AnalyticsReporter.swift index cbe3c8d9c..4a078f485 100644 --- a/Sources/Observability/AnalyticsReporter.swift +++ b/Sources/Observability/AnalyticsReporter.swift @@ -303,8 +303,8 @@ final class AnalyticsReporter { shared.apiKey != nil && shared.captureHost != nil } - static func track(_ event: String, properties: [String: String] = [:]) { - shared.trackEvent(event, properties: properties) + static func track(_ event: String, properties: [String: String] = [:], usageDurationSeconds: Double? = nil) { + shared.trackEvent(event, properties: properties, usageDurationSeconds: usageDurationSeconds) } /// Shared bucketing for `duration_ms` string context values (Sentry policy @@ -451,7 +451,8 @@ final class AnalyticsReporter { fileURL: AnalyticsDeliveryBufferStore.defaultFileURL() ), userDefaults: .standard, - observePreferenceChanges: true + observePreferenceChanges: true, + usageStore: .shared ) } @@ -465,9 +466,11 @@ final class AnalyticsReporter { retryDelay: @escaping (Int) -> TimeInterval = AnalyticsDeliveryPolicy.retryDelay(afterAttempt:), persistDebounceInterval: TimeInterval = AnalyticsReporter.defaultPersistDebounceInterval, analyticsEnabled: (() -> Bool)? = nil, - observePreferenceChanges: Bool = false + observePreferenceChanges: Bool = false, + usageStore: UsageHealthStore? = nil ) { self.apiKey = apiKey + self.usageStore = usageStore self.captureHost = captureHost self.session = session self.bufferStore = bufferStore @@ -518,6 +521,7 @@ final class AnalyticsReporter { // Config is read once from env/plist/overrides file and cached for the app lifetime. private let apiKey: String? + private let usageStore: UsageHealthStore? private let captureHost: String? private static let isoDateFormatter = ISO8601DateFormatter() private let sessionID = TelemetryContext.launchSessionID @@ -548,24 +552,20 @@ final class AnalyticsReporter { private var distinctID: String { InstallIdentity.id(userDefaults: userDefaults) } - func trackEvent(_ event: String, properties: [String: String] = [:]) { + func trackEvent(_ event: String, properties: [String: String] = [:], usageDurationSeconds: Double? = nil) { guard analyticsEnabled() else { clearPendingCaptures() return } - guard apiKey != nil, - let captureHost, - normalizedCaptureURL(from: captureHost) != nil, - let policy = AnalyticsEventPolicy.policy(forEvent: event) else { - return - } - + guard let policy = AnalyticsEventPolicy.policy(forEvent: event) else { return } let enrichedProperties = TelemetryContext.enrich(event: event, properties: properties) let sanitizedProperties = AnalyticsPayloadSanitizer.sanitizeProperties( enrichedProperties, allowedKeys: policy.allowedProperties.union(TelemetryContext.keys) ) + usageStore?.record(event: event, properties: sanitizedProperties, durationSeconds: usageDurationSeconds, now: currentDate()) + guard apiKey != nil, let captureHost, normalizedCaptureURL(from: captureHost) != nil else { return } var eventProperties = Self.captureProperties( sanitizedProperties: sanitizedProperties, @@ -643,6 +643,7 @@ final class AnalyticsReporter { } private func clearPendingCaptures() { + usageStore?.clear() syncOnDeliveryQueue { self.inFlightCaptureIDs.removeAll() self.clearBufferedCapturesLocked() diff --git a/Sources/Observability/UsageHealthStore.swift b/Sources/Observability/UsageHealthStore.swift new file mode 100644 index 000000000..5c85fee09 --- /dev/null +++ b/Sources/Observability/UsageHealthStore.swift @@ -0,0 +1,165 @@ +import Foundation + +struct UsageFailure: Codable, Equatable, Identifiable { + var id: String + var kind: String + var stage: String + var time: Date + var version: String +} + +struct UsageDay: Codable, Equatable { + var day: String + var startedAt: Date + var meetingsStarted = 0 + var meetingsCompleted = 0 + // Aggregate rounded minutes only; no per-recording duration or content is retained. + var meetingMinutes = 0 + var dictationsCompleted = 0 + var dictationDurationCounts: [String: Int] = [:] + var failuresByKind: [String: Int] = [:] + var captureQualityCounts: [String: Int] = [:] + var seenOutcomes: [String] = [] + var digestID = UUID().uuidString + var digestEnqueued = false +} + +struct UsageHealthSnapshot: Equatable { + var meetings = 0 + var dictations = 0 + var meetingMinutesBucket = "0" + var qualityCounts: [String: Int] = [:] + var failures: [UsageFailure] = [] +} + +/// Bounded metadata ledger, populated from event enums only. Never scans a capture or a log. +final class UsageHealthStore { + static let shared = UsageHealthStore() + static let didChange = Notification.Name("TranscriptedUsageHealthDidChange") + static let storageKey = "observability-usage-health-v1" + static let durationBuckets = ["lt_10s", "10_29s", "30_119s", "2_9m", "10_29m", "30m_plus"] + private struct State: Codable { + var days: [UsageDay] = [] + var failures: [UsageFailure] = [] + } + private let lock = NSLock() + private let defaults: UserDefaults + private var state: State + + init(userDefaults: UserDefaults = .standard) { + defaults = userDefaults + state = userDefaults.data(forKey: Self.storageKey) + .flatMap { try? JSONDecoder().decode(State.self, from: $0) } ?? State() + } + + func clear() { + lock.lock() + state = State() + defaults.removeObject(forKey: Self.storageKey) + lock.unlock() + NotificationCenter.default.post(name: Self.didChange, object: nil) + } + + func record(event: String, properties: [String: String], durationSeconds: Double? = nil, + now: Date = Date(), calendar: Calendar = .current) { + let relevant = ["meeting_recording_started", "meeting_transcript_saved", "meeting_recording_stopped", + "dictation_completed", "meeting_capture_health_snapshot", "reliability_failure_observed"].contains(event) + || event.hasSuffix("_failed") + guard relevant else { return } + lock.lock() + defer { + lock.unlock() + NotificationCenter.default.post(name: Self.didChange, object: nil) + } + guard AnalyticsPreferences.isEnabled(userDefaults: defaults) else { return } + prune(now: now, calendar: calendar) + let key = Self.dayKey(now, calendar: calendar) + if !state.days.contains(where: { $0.day == key }) { + state.days.append(UsageDay(day: key, startedAt: calendar.startOfDay(for: now))) + } + let index = state.days.firstIndex(where: { $0.day == key })! + var day = state.days[index] + let isFailure = event.hasSuffix("_failed") || event == "reliability_failure_observed" + let kind = TelemetryContext.category(properties["failure_kind"]) ?? "unknown" + let correlation = TelemetryContext.uuid(properties["correlation_id"]) ?? UUID().uuidString + let outcomeKey = correlation + ":" + (isFailure ? "failure:" + kind : event) + guard !day.seenOutcomes.contains(outcomeKey) else { return } + day.seenOutcomes.append(outcomeKey) + day.seenOutcomes = Array(day.seenOutcomes.suffix(2_000)) + switch event { + case "meeting_recording_started": day.meetingsStarted += 1 + case "meeting_transcript_saved": day.meetingsCompleted += 1 + case "meeting_recording_stopped": + if let seconds = durationSeconds, seconds.isFinite, seconds > 0 { + day.meetingMinutes += Int(min(seconds / 60, 24 * 60).rounded()) + } + case "dictation_completed": + day.dictationsCompleted += 1 + if let bucket = properties["duration_bucket"], Self.durationBuckets.contains(bucket) { + day.dictationDurationCounts[bucket, default: 0] += 1 + } + case "meeting_capture_health_snapshot": + let outcome = properties["capture_outcome"] ?? "unknown" + if outcome != "cancelled" { + let quality: String + if ["no_audio", "timed_out", "stop_timed_out", "failed"].contains(outcome) { quality = "failed" } + else if ["degraded", "fair"].contains(properties["capture_quality"] ?? "") { quality = "degraded" } + else if ["excellent", "good"].contains(properties["capture_quality"] ?? "") { quality = "good" } + else { quality = "unknown" } + day.captureQualityCounts[quality, default: 0] += 1 + } + default: break + } + if isFailure { + day.failuresByKind[kind, default: 0] += 1 + state.failures.append(UsageFailure(id: outcomeKey, kind: kind, + stage: TelemetryContext.category(properties["failure_stage"]) ?? "unknown", time: now, + version: TelemetryContext.category(properties["app_version"]) ?? "unknown")) + state.failures = Array(state.failures.suffix(3)) + } + state.days[index] = day + persist() + } + + func snapshot(now: Date = Date(), calendar: Calendar = .current) -> UsageHealthSnapshot { + lock.lock() + defer { lock.unlock() } + let week = calendar.dateInterval(of: .weekOfYear, for: now) + let days = state.days.filter { week?.contains($0.startedAt) == true } + var snapshot = UsageHealthSnapshot() + snapshot.meetings = days.reduce(0) { $0 + $1.meetingsCompleted } + snapshot.dictations = days.reduce(0) { $0 + $1.dictationsCompleted } + snapshot.meetingMinutesBucket = Self.minutesBucket(days.reduce(0) { $0 + $1.meetingMinutes }) + for day in days { + for (key, count) in day.captureQualityCounts { snapshot.qualityCounts[key, default: 0] += count } + } + snapshot.failures = Array(state.failures.reversed()) + return snapshot + } + + static func minutesBucket(_ minutes: Int) -> String { + switch minutes { + case ..<1: return "0" + case ..<15: return "1_14m" + case ..<60: return "15_59m" + case ..<180: return "1_2h" + case ..<600: return "3_9h" + default: return "10h_plus" + } + } + + static func dayKey(_ date: Date, calendar: Calendar) -> String { + let components = calendar.dateComponents([.year, .month, .day], from: date) + return String(format: "%04d-%02d-%02d", components.year!, components.month!, components.day!) + } + + private func prune(now: Date, calendar: Calendar) { + let oldest = calendar.date(byAdding: .day, value: -14, to: now) ?? now + state.days.removeAll { $0.startedAt < oldest } + state.days = Array(state.days.sorted { $0.startedAt < $1.startedAt }.suffix(14)) + } + + private func persist() { + if let data = try? JSONEncoder().encode(state) { defaults.set(data, forKey: Self.storageKey) } + } +} diff --git a/Sources/UI/Settings/TranscriptedSettingsView.swift b/Sources/UI/Settings/TranscriptedSettingsView.swift index 51e18461e..62fed49ee 100644 --- a/Sources/UI/Settings/TranscriptedSettingsView.swift +++ b/Sources/UI/Settings/TranscriptedSettingsView.swift @@ -2342,12 +2342,16 @@ struct TranscriptedSettingsView: View { help: analyticsFootnote, info: GeneralInfo( title: "Usage stats", - message: "Anonymous feature usage from a strict allowlist. No content, ever." + message: "Shares feature use, duration and count ranges, error codes, permissions, and an anonymous install ID. Never shares recordings, words, titles, names, or email." ), automationIdentifier: "transcripted.settings.general.usage-stats", showsDivider: false ) .disabled(!AnalyticsReporter.isAvailable) + if anonymousAnalyticsEnabled { + Divider() + UsageHealthSettingsSection() + } } } diff --git a/Sources/UI/Settings/UsageHealthSettingsSection.swift b/Sources/UI/Settings/UsageHealthSettingsSection.swift new file mode 100644 index 000000000..677a956f9 --- /dev/null +++ b/Sources/UI/Settings/UsageHealthSettingsSection.swift @@ -0,0 +1,90 @@ +import SwiftUI + +struct UsageHealthSettingsSection: View { + @State private var snapshot = UsageHealthStore.shared.snapshot() + + var body: some View { + UsageHealthPanel(snapshot: snapshot) + .onAppear { snapshot = UsageHealthStore.shared.snapshot() } + .onReceive(NotificationCenter.default.publisher(for: UsageHealthStore.didChange).receive(on: RunLoop.main)) { _ in + snapshot = UsageHealthStore.shared.snapshot() + } + .onReceive(Timer.publish(every: 60, on: .main, in: .common).autoconnect()) { now in + snapshot = UsageHealthStore.shared.snapshot(now: now) + } + } +} + +/// Presentation accepts metadata only, so previews and support never need real captures. +struct UsageHealthPanel: View { + let snapshot: UsageHealthSnapshot + + var body: some View { + VStack(alignment: .leading, spacing: 12) { + Text("Usage & health") + .font(.headline) + Text("This week on this Mac") + .font(.caption) + .foregroundStyle(.secondary) + HStack(spacing: 28) { + metric("Meetings saved", value: String(snapshot.meetings)) + metric("Dictations", value: String(snapshot.dictations)) + metric("Meeting time", value: minutesLabel(snapshot.meetingMinutesBucket)) + } + HStack(spacing: 14) { + Label("\(snapshot.qualityCounts["good", default: 0]) good", systemImage: "checkmark.circle") + Label("\(snapshot.qualityCounts["degraded", default: 0]) degraded", systemImage: "exclamationmark.circle") + Label("\(snapshot.qualityCounts["failed", default: 0]) failed", systemImage: "xmark.circle") + } + .font(.caption) + .foregroundStyle(.secondary) + if snapshot.qualityCounts["unknown", default: 0] > 0 { + Text("\(snapshot.qualityCounts["unknown", default: 0]) captures have unknown quality.") + .font(.caption).foregroundStyle(.secondary) + } + Divider() + Text("Recent failures").font(.subheadline.weight(.medium)) + if snapshot.failures.isEmpty { + Text("No failures recorded while usage stats were on.") + .font(.caption).foregroundStyle(.secondary) + } else { + ForEach(snapshot.failures) { failure in + VStack(alignment: .leading, spacing: 3) { + Text(failure.kind.replacingOccurrences(of: "_", with: " ").capitalized) + .font(.subheadline) + HStack(spacing: 8) { + Text(failure.time, style: .date) + Text(failure.time, style: .time) + Text("Version \(failure.version)") + } + .font(.caption).foregroundStyle(.secondary) + } + } + } + Text("Usage stats share feature use, duration and count ranges, permission state, and error codes with an anonymous install ID. Your recordings, words, titles, names, and email stay private. Turning this off clears this local summary and unsent stats.") + .font(.caption) + .foregroundStyle(.secondary) + .fixedSize(horizontal: false, vertical: true) + } + .padding(16) + .accessibilityIdentifier("transcripted.settings.usage-health") + } + + private func metric(_ title: String, value: String) -> some View { + VStack(alignment: .leading, spacing: 3) { + Text(value).font(.title3.weight(.semibold)).monospacedDigit() + Text(title).font(.caption).foregroundStyle(.secondary) + } + } + + private func minutesLabel(_ bucket: String) -> String { + switch bucket { + case "0": return "< 1 min" + case "1_14m": return "1–14 min" + case "15_59m": return "15–59 min" + case "1_2h": return "1–2 hr" + case "3_9h": return "3–9 hr" + default: return "10+ hr" + } + } +} diff --git a/Sources/UI/Shared/SupportDiagnosticsBundle.swift b/Sources/UI/Shared/SupportDiagnosticsBundle.swift index 19640abd0..af1a02e85 100644 --- a/Sources/UI/Shared/SupportDiagnosticsBundle.swift +++ b/Sources/UI/Shared/SupportDiagnosticsBundle.swift @@ -24,6 +24,9 @@ struct SupportDiagnosticsSnapshot: Equatable { var meetingShortcut: String = "unknown" var reliabilityPackets: [String] var recentLogLines: [String] + var installUUID: String = "unknown" + var buildRevision: String = "unknown" + var recentFailures: [UsageFailure] = [] } enum SupportDiagnosticsBundle { @@ -31,14 +34,9 @@ enum SupportDiagnosticsBundle { static let maxReliabilityPackets = 8 static func text(snapshot: SupportDiagnosticsSnapshot, now: Date = Date()) -> String { - let reliabilityPackets = snapshot.reliabilityPackets - .suffix(maxReliabilityPackets) - .map(AnalyticsPayloadSanitizer.redact) - .filter { !$0.isEmpty } - let recentLogs = snapshot.recentLogLines - .suffix(maxRecentLogLines) - .map(AnalyticsPayloadSanitizer.redact) - .filter { !$0.isEmpty } + let failures = snapshot.recentFailures.prefix(3).map { + "\($0.time.formatted(date: .abbreviated, time: .shortened)) | \(TelemetryContext.category($0.kind) ?? "unknown") | \(TelemetryContext.category($0.stage) ?? "unknown") | version \(TelemetryContext.category($0.version) ?? "unknown")" + } return """ Transcripted diagnostics @@ -47,6 +45,8 @@ enum SupportDiagnosticsBundle { App Version: \(snapshot.appVersion) Build: \(snapshot.buildVersion) + Revision: \(TelemetryContext.category(snapshot.buildRevision) ?? "unknown") + Install UUID: \(TelemetryContext.uuid(snapshot.installUUID) ?? "unknown") macOS: \(snapshot.osVersion) Reporting @@ -77,11 +77,8 @@ enum SupportDiagnosticsBundle { Queued meetings: \(snapshot.queuedMeetingCount) Meeting shortcut: \(snapshot.meetingShortcut) - Reliability Packets - \(reliabilityPackets.isEmpty ? "No recent reliability packets." : reliabilityPackets.joined(separator: "\n")) - - Recent Events - \(recentLogs.isEmpty ? "No recent in-app events." : recentLogs.joined(separator: "\n")) + Recent failures + \(failures.isEmpty ? "No recent failures recorded." : failures.joined(separator: "\n")) Privacy This diagnostic summary is designed to exclude transcript text, raw audio, file paths, device names, meeting titles, speaker names, emails, tokens, and raw URLs. @@ -90,6 +87,11 @@ enum SupportDiagnosticsBundle { static func sentryContext(snapshot: SupportDiagnosticsSnapshot) -> [String: String] { var context: [String: String] = [ + "install_uuid": TelemetryContext.uuid(snapshot.installUUID) ?? "unknown", + "build_revision": TelemetryContext.category(snapshot.buildRevision) ?? "unknown", + "last_failure_kind": TelemetryContext.category(snapshot.recentFailures.first?.kind) ?? "none", + "last_failure_stage": TelemetryContext.category(snapshot.recentFailures.first?.stage) ?? "none", + "last_failure_version": TelemetryContext.category(snapshot.recentFailures.first?.version) ?? "unknown", "analytics_available": bool(snapshot.analyticsAvailable), "analytics_enabled": bool(snapshot.analyticsEnabled), "app_version": snapshot.appVersion, @@ -115,15 +117,15 @@ enum SupportDiagnosticsBundle { // Sentry. (The human-readable diagnostics text, built separately, still // summarizes recent reliability packets for support diagnostic payloads.) - for (key, value) in snapshot.audioRoute { + for (key, value) in safeMetadata(snapshot.audioRoute) { context["route_\(key)"] = value } - for (key, value) in snapshot.runtime { + for (key, value) in safeMetadata(snapshot.runtime) { context["runtime_\(key)"] = value } - for (key, value) in snapshot.storage { + for (key, value) in safeMetadata(snapshot.storage) { context["storage_\(key)"] = value } @@ -143,6 +145,11 @@ enum SupportDiagnosticsBundle { /// suffix is sensitive (e.g. `runtime_file_path`, `route_raw_url`) is also /// dropped downstream. Keep in sync with `sentryContext` above. static let sentryContextAllowedKeys: Set = [ + "install_uuid", + "build_revision", + "last_failure_kind", + "last_failure_stage", + "last_failure_version", "analytics_available", "analytics_enabled", "app_version", @@ -175,12 +182,31 @@ enum SupportDiagnosticsBundle { static func allowlistedSentryContext(_ context: [String: String]) -> [String: String] { context.filter { key, _ in sentryContextAllowedKeys.contains(key) - || sentryContextAllowedKeyPrefixes.contains(where: { key.hasPrefix($0) }) + || sentryContextAllowedKeyPrefixes.contains(where: { prefix in + key.hasPrefix(prefix) && safeMetadata([String(key.dropFirst(prefix.count)): context[key]!]).count == 1 + }) + } + } + + private static func safeMetadata(_ values: [String: String]) -> [String: String] { + let keys = TelemetryContext.keys.union([ + "session_stage", "session_kind", "session_active", "previous_clean_shutdown", "heartbeat_age_bucket", + "last_event", "session_duration_bucket", "route_shape", "default_input_class", "default_output_class", + "selected_input_class", "selection_overrode_default", "input_channels", "output_channels", + "input_rate_hz", "output_rate_hz", "recovering", "format_ready", "sample_flow_started", + "known_stale_model_count", "model_cache_total", "known_stale_model_size", + ]) + return values.filter { key, value in + guard keys.contains(key) else { return false } + if key == "model_cache_total" || key == "known_stale_model_size" { + return value.range(of: #"^[0-9]+(?:[.,][0-9]+)? (?:bytes|KB|MB|GB|TB)$"#, options: .regularExpression) != nil + } + return TelemetryContext.category(value) != nil } } private static func render(_ values: [String: String]) -> String { - let sanitized = AnalyticsPayloadSanitizer.sanitizeDiagnosticContextForDisplay(values) + let sanitized = AnalyticsPayloadSanitizer.sanitizeDiagnosticContextForDisplay(safeMetadata(values)) guard !sanitized.isEmpty else { return "Unavailable" } return sanitized .sorted { $0.key < $1.key } diff --git a/Sources/UI/Shared/TranscriptedSupportActions.swift b/Sources/UI/Shared/TranscriptedSupportActions.swift index 2edada555..0c41b66c8 100644 --- a/Sources/UI/Shared/TranscriptedSupportActions.swift +++ b/Sources/UI/Shared/TranscriptedSupportActions.swift @@ -20,7 +20,7 @@ enum TranscriptedSupportActions { static func feedbackEmailURL(appState: TranscriptedAppState) -> URL? { FeedbackIssueBuilder.emailURL( - rawLogLines: appState.logger.entries, + rawLogLines: [], diagnostics: diagnosticsText(appState: appState) ) } @@ -94,7 +94,10 @@ enum TranscriptedSupportActions { queuedMeetingCount: queuedMeetingCount, meetingShortcut: meetingShortcut, reliabilityPackets: ReliabilityPacketRecorder.recentPacketSummaries(), - recentLogLines: appState.logger.entries + recentLogLines: [], + installUUID: InstallIdentity.id(), + buildRevision: AnalyticsRuntimeConfiguration.buildRevision(), + recentFailures: UsageHealthStore.shared.snapshot().failures ) } diff --git a/Tests/SupportDiagnosticsBundleTests.swift b/Tests/SupportDiagnosticsBundleTests.swift index c41ae9a9f..2365fa491 100644 --- a/Tests/SupportDiagnosticsBundleTests.swift +++ b/Tests/SupportDiagnosticsBundleTests.swift @@ -43,6 +43,7 @@ func testSupportDiagnosticsBundle() { "2026-05-03T01:15:11Z meeting.stop recovered event=meeting_recording_stopped route_change_count_bucket=2_3 path=/Users/redbars/private.txt" ], recentLogLines: [ + "Private content without an obvious sensitive prefix", "Opened /Users/redbars/Library/Application Support/Transcripted/logs/app.jsonl", "DIAG | capture.dictation_toggle_requested | source_app_bundle_id=com.openai.codex source_app_name=Codex trigger=physical_key", "DIAG | dictation.dictation_started | audio_device=MacBook Pro Microphone route_shape=built_in_input_to_built_in_output", @@ -56,6 +57,7 @@ func testSupportDiagnosticsBundle() { now: Date(timeIntervalSince1970: 100) ) + assertFalse(text.contains("Private content"), "arbitrary log content is never copied") assertTrue(text.contains("Version: 1.2.3"), "diagnostics should include app version") assertTrue(text.contains("input_device_class: bluetooth"), "diagnostics should include coarse route facts") assertTrue(text.contains("session_stage: recording"), "diagnostics should include runtime session stage") @@ -64,7 +66,7 @@ func testSupportDiagnosticsBundle() { assertTrue(text.contains("Speaker review pending: true"), "diagnostics should include pending speaker review state") assertTrue(text.contains("Queued meetings: 1"), "diagnostics should include queued meeting count") assertTrue(text.contains("Meeting shortcut: ⌥C"), "diagnostics should include the active meeting shortcut") - assertTrue(text.contains("meeting.stop recovered"), "diagnostics should include recent reliability packet summaries") + assertFalse(text.contains("meeting.stop recovered"), "diagnostics must not copy raw reliability packet blobs") assertFalse(text.contains("/Users/redbars"), "diagnostics should redact home paths") assertFalse(text.contains("person@example.com"), "diagnostics should redact emails") assertFalse(text.contains("Application Support/Transcripted"), "diagnostics should redact app support paths") diff --git a/Tests/UsageHealthStoreTests.swift b/Tests/UsageHealthStoreTests.swift new file mode 100644 index 000000000..c4c5fe8bb --- /dev/null +++ b/Tests/UsageHealthStoreTests.swift @@ -0,0 +1,49 @@ +import Foundation + +func testUsageHealthStore() { + runSuite("Usage health counts lifecycle outcomes and deduplicates paired failures") { + let name = "UsageHealthTests-\(UUID().uuidString)" + let defaults = UserDefaults(suiteName: name)! + defer { defaults.removePersistentDomain(forName: name) } + let store = UsageHealthStore(userDefaults: defaults) + let now = Date() + let id = UUID().uuidString + var properties = ["correlation_id": id, "failure_kind": "mic_unavailable", "failure_stage": "microphone", "app_version": "1.1.59"] + store.record(event: "meeting_recording_started", properties: properties, now: now) + store.record(event: "meeting_recording_stopped", properties: properties, durationSeconds: 20 * 60, now: now) + store.record(event: "meeting_transcript_saved", properties: properties, now: now) + properties["duration_bucket"] = "10_29s" + store.record(event: "dictation_completed", properties: properties, now: now) + store.record(event: "reliability_failure_observed", properties: properties, now: now) + store.record(event: "meeting_recording_start_failed", properties: properties, now: now) + store.record(event: "product_friction_observed", properties: properties, now: now) + properties["capture_quality"] = "fair" + properties["capture_outcome"] = "complete" + store.record(event: "meeting_capture_health_snapshot", properties: properties, now: now) + let summary = store.snapshot(now: now) + assertEqual(summary.meetings, 1, "saved meetings count once") + assertEqual(summary.dictations, 1, "completed dictations count once") + assertEqual(summary.meetingMinutesBucket, "15_59m", "meeting minutes stay bucketed in summary") + assertEqual(summary.failures.count, 1, "paired analytics and Sentry failure count once") + assertEqual(summary.qualityCounts["degraded"], 1, "fair capture quality is degraded, not an unknown or hard failure") + assertEqual(UsageHealthStore(userDefaults: defaults).snapshot(now: now), summary, "metadata survives restart") + } + runSuite("Usage health excludes cancellations and ignores content fields") { + let name = "UsageHealthTests-\(UUID().uuidString)" + let defaults = UserDefaults(suiteName: name)! + defer { defaults.removePersistentDomain(forName: name) } + let store = UsageHealthStore(userDefaults: defaults) + store.record(event: "meeting_capture_health_snapshot", properties: ["capture_outcome": "cancelled", "capture_quality": "excellent", "transcript": "Private content"]) + for i in 0..<5 { + store.record(event: "dictation_start_failed", properties: ["failure_kind": "failure_\(i)", "transcript": "Private content"]) + } + assertEqual(store.snapshot().failures.count, 3, "only the last three failures are retained") + assertEqual(store.snapshot().qualityCounts.count, 0, "discard is not a quality completion") + let persisted = String(decoding: defaults.data(forKey: UsageHealthStore.storageKey)!, as: UTF8.self) + assertFalse(persisted.contains("Private content"), "content fields cannot enter the local ledger") + AnalyticsPreferences.setEnabled(false, userDefaults: defaults) + store.clear() + store.record(event: "dictation_completed", properties: [:]) + assertNil(defaults.data(forKey: UsageHealthStore.storageKey), "opt-out erases the ledger and blocks new collection") + } +} diff --git a/scripts/entrypoints/run-tests.sh b/scripts/entrypoints/run-tests.sh index 57321501e..908032080 100755 --- a/scripts/entrypoints/run-tests.sh +++ b/scripts/entrypoints/run-tests.sh @@ -438,6 +438,7 @@ APP_SOURCES=( "Sources/UI/MenuBar/MenuBarHeaderLayoutPolicy.swift" "Sources/UI/MenuBar/MenuBarHeaderStatusPresentation.swift" "Sources/UI/MenuBar/PasteLastDictationFeedback.swift" + "Sources/Observability/UsageHealthStore.swift" "Sources/Observability/TelemetryContext.swift" "Sources/Observability/InstallIdentity.swift" "Sources/Observability/AnalyticsReporter.swift" From e1312cffa9d80ffb4b39b8b818dd25a1b5c3ebea Mon Sep 17 00:00:00 2001 From: r3dbars Date: Thu, 10 Sep 2026 17:46:41 -0500 Subject: [PATCH 5/6] Add daily bucketed usage digests with durable consent-aware delivery --- .agent-review/visuals/usage-health-empty.png | Bin 0 -> 58239 bytes .../visuals/usage-health-populated.png | Bin 0 -> 87626 bytes .agent-review/visuals/usage-health-review.md | 8 ++ Resources/analytics-events.psv | 1 + Resources/analytics-reviewed-properties.psv | 6 + .../MeetingCaptureHealthTelemetry.swift | 2 +- .../Meeting/MeetingSessionController.swift | 39 ++++--- Sources/Observability/AnalyticsReporter.swift | 104 +++++++++++++++--- .../PayloadSanitizationCore.swift | 6 + Sources/Observability/SentryEventPolicy.swift | 23 ++-- Sources/Observability/TelemetryContext.swift | 15 ++- Sources/Observability/UsageHealthModels.swift | 40 +++++++ Sources/Observability/UsageHealthStore.swift | 96 ++++++++++------ .../UI/Shared/SupportDiagnosticsBundle.swift | 20 ++-- Tests/AnalyticsPayloadSanitizerTests.swift | 43 +++++--- Tests/AnalyticsReporterTests.swift | 78 ++++++++++++- Tests/E2E/TranscriptedE2ESmoke.swift | 26 ++--- ...MeetingCaptureVolumeDiagnosticsTests.swift | 2 +- Tests/SentryEventPolicyTests.swift | 15 +-- Tests/TelemetryContextTests.swift | 6 + Tests/UsageHealthStoreTests.swift | 42 +++++++ docs/privacy-first-observability.md | 67 +++++++++++ .../entrypoints/lib/shared-smoke-sources.sh | 1 + scripts/ops/transcripted-qa-bench.sh | 2 +- 24 files changed, 496 insertions(+), 146 deletions(-) create mode 100644 .agent-review/visuals/usage-health-empty.png create mode 100644 .agent-review/visuals/usage-health-populated.png create mode 100644 .agent-review/visuals/usage-health-review.md create mode 100644 Sources/Observability/UsageHealthModels.swift diff --git a/.agent-review/visuals/usage-health-empty.png b/.agent-review/visuals/usage-health-empty.png new file mode 100644 index 0000000000000000000000000000000000000000..4a2d03dd00b9c3532c7cfc776fe796d2a5dd04a0 GIT binary patch literal 58239 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 mo&Nu)>_3B$|4*EaR{()0bkO%4FCKt zi+cwL|IgR)aB#wGa3Fub2L^s(|GfcU*kk_siJyV{_q)Ms8F;_mjX#iq|Lb?We(Yhg zAz|POafqEC>A2zG(B8s+;mWK2v4w*J#gTt-SKS+TZIYno+TiHHHkD+>edmjppWcQ% zy)ewu_}Wh)8C!rtc-&meByr1AJr^%hn6sDH0orS7-yYpFIaysjnmk#1ERo*Z zy%E*xWhsK`a_dPltdHPs{1A*sawp^WZyLlAYJ&r?&%eL+=UbjM1dmbO#U-Ftf&TAX z$W;hgwjI=s_TMk#5|Cv}5&r9);F#>Y4Z&3x1g)O@_5s+B0w3)5pO>Kj_rd>+=>HAH z|06?TEJ;kAozT!iXLK#id+t@!T=|4+XJOwxQW|LyOtPUQ0k$|!z{vD0~ zg)J@tTr0x{Z-t|vhTs2iA%TDm?}sYxZa7aJJ$L=d&!$r5fStFv9(O;UB$M*g@LqFm zzdqZ>9K@-3Fy*%y8ph;%C&h20(&YSff4G&SXf(ioDi$mpz}~ z_s(Fik@sxqR=)yIsFtD6Ek&V0^-!PVon28{y9zpAok8f}^~(ZF4f47`-Mg$;h1UphA~dwihS8rpfoGgMxNa zg3b;&Snb3=FBPG95g1S5TQ1I!Rn`xjR%i7-79PGQ@J7t5~xNSL?!XMn*}v1STkVo*VC z4@n=dQB8TzM}8Pn;Hh;#K+Bw8L8N+)QOpN{nX|PSbS`l+&wRerhmer?F24GiibDEe zhIjTkl_g=@KApbvUHuJ_X@8W-wY#gA3wU{D#;?=+bzV#hN?^Hnf&ck!3zieuuuk^UG|{k@o7BP z!(tE&Z7U0hv;M5XXWdSLRA~Wwqp**ahlLwetF4|hEsmTuV1CW^2A#Rpy#H>q_#GdS zX!_cD!lf-V1fP(WVbW%#TVz__U81t1cs@#^b$M9VF3)-3iJS*qggm>2rF%8^_iz;~ zfT9PiF^+aEda`i~T;(Gl%z3+?5}AY8J*BU)pgT#ZKe=@cgzc?oHi+6qxSeU60UIi1 zu^wE6Sw`wb#1&;v@BK-yi_<~p0q~loRWJ#epOp;ci^+UH-mGU1JY6+vW2(Z;R~^+X zBa&%Z-`^D3i(6Ya^jp^`ykTtM)-6PeitJ!OzM~^W=qzR?7+#H%IXi%bpZ|zaZrU4n zG7?O*oJ$Wn6WiN@p=~Bk+G%|`(Uyc|_>XD!5*CcSCOtA)b5^%>$yTaxxfOq35vr6j zJOo zWLCB}W=Dsp1>emO@yLaolMS!>T(;xX#ojvA+Eo6IKVqo!5P@I?u(*A3;^hcf3Cw7Y z1)oOnpK;`ssy4oL(K7JR5M3*5?l<3#GyGN(ZJ6x;v!j`W<1=OMpxZUo*@-(eOIbGk ztAZ#+s@@QPA-DLp%0{EJiAR{9x_Ng!4W~JNLp@M8(wz$SX^4d+sVL1;vEsTM->pKE zX1v}C7_P{AuG;lxMVy3hFBlx@1HnR_6>ZWhwZFENVli}|!J4Gkqepr#+=TF&Gc>~% z1LA~ETR3TkIq)FlNh-;xCi1yeNxpe$&JCD}Xe0OP`!^l8g3TERsgd`o9-&DaZ|h8x z3y-i^epi-QFKzlm-DZ*R$sq06OO%~x=LuNQny*-;TR?t=64-Phv5zd<@xlAvHJ!CnY1{o~3v#x}v16)PC%)nua6|975@DC1Iv8Ut9pe zoD{7wdXBv3wS6B-OG0Ph{$$oIM`ZA7tZq~@&2OkK;}GJj{{oJ~ntY43fz>P(snRPn ztY7qPZ+F;;`QW3J1ADT4u{>D{QQAT01(f1_<3uiZ-T23KwLG13I%Dp^523Z+*mIwQ zDbJ%cwH#;sw!ArqvH@WXH(}$Q7mBkln6%NZ9QNC+6|t-iSGqrwz84);ksXzKwv?C` ze~$&`nQHr^R0I9ufv{Q^e~E~oW5w(Xiri7J=>XD3!J9|>o6NaCvtEhze3|srJEC_I z{$%EbQWBwgYQvg=;(a=!zEl|H**9r`6k6I?cRBnW->S%jFeSWa4Zh`k17H67($T8X z`Be&;lPzotrH|z*>Eh=rNhuy`5zT>}f>j72UiqQTp*ZIdM>tstA?{U9c7^|sW4<~g zk3o%aPv@3)f9E!=m;Ha{(~LPG_JUv_S97A z+AL&1xwHLJO61;Zj`?^p>Vmjll=RV(^Qc9F1tEbWi8-lM4w_U%+4~xO+w-XkvA~na zYhuOCpE5bc6$M`~BZcUj2gRy?UydRkhUDKYDyrby)ul;w_-+WN$X31H?t3sBHqYER z4^jhT?nP9iR{2&oD^eQ$ta?rGa38XMIQ7*kJz(#|JcPXq633>OmO9gXQKl;}ViVJ7 z7Kn#;4;hGgl;;{MC-?okE(uEq{rT+0pqJ}eAe*h;)9{$c#ycIdJ>W6L@6 z@SHMj?mwu(4GEC0P(~rN#u~Hk-FDfTfN9t}3JTt5E3a>PA=F%6MgFg6k%~n=5XaGb zgeIii6~>+_vr_ZLxAe&2vt3@>Ep&uqBmLYW4BAGjOyL1<1ktg26aqel-S~tIBNzXl4}sD_Ws-6YmWRxX-{vZ!Aewv=@s2EmYBF(S$^H!+v8h8~pv;=Y7nrYm&Botv1jaMi5@;fdI#@nS_N~Fzi zyKJW~E1j&c*(3Ry-H1rtW;}&!)Q7k|doJb7T0#M;IJgMX^)2aU(d5C*EUTgWEJ^E( zDGSW*eRo6lhH`FOBO?;n5ADUJ!i`*7Xk04Im2>I>+OLPj!fWq`=_o!J#MnD0S^6I> zXU%_1^u9*5$-#C==2ngoSj=(|S?>y`@ug+fyXDm09qs?#68AXmEc+*H)>5v#@8kYr zvJ;a8>%6yH3TAzx1f_MHhFr=a2`Iye*Zv*~J7ggU;%v2xs8?|--^U%R9sC8UM;8d0 zyjd*meST<^uLvbAcns;>?xw}pN-Ik_)7~It_E(`#-+XERjV+6;)QffLafloIU?sO8 z3YSmE4jBbPWYk7ION%M*fu^8dd(_2iqP%C{8?|#*rn;JyLhPaPH2xXZKu$^XvDe71`Dk)vm|ra_4rso2#h#=}H&W{QOpQO8v1Tl(|nz#QNZrO9X7{1eYkqk zcBu%9p!V`!fnG@)l`pmVrTvdX+9IEjr$4=^@trunQfGUkdegI=1SHal>$`}wG{Q9D zD8v5Bw+#ZH#pqfEll4EF2_t`C+CCOViLYdr+Wsr}GL%5<-B)^%2y0r>M!vg8saRNx zf=PHJrocX1ODNq|&pFv@zF0&eq*1j@EKbAURJ~DUcSK)4YMBhvvBaCBR>ZVtxrPR( zWK?ML^gvqOafAV0jo1I`kLjj^fkX^TaxLySH1Jp>kc-sT@ZhU@PsN^^_)fBxXd~ZnNiBbhX#%K2 z+x;^CBo^?n%!`eh$9N>ZU}_}(O!O%1A~Ta;5sZqnxcvqnLubh|HJ-7EWLsoTQ{eGB zokZnh)K577_^3(gWbJzS_?+w6c688JvPEnb&$ZiMMrEm$&7bg(To9kXF%RFYP3eu0 zS^Zqn1WLSA1BZmo<(Cns2A~u%Z55GeRut%_ZXd$b*oyD#nuOAj#C%%WFO}ob`g#2B zysPHW%aoFbt&dKe@_s7;&`>PR-1t~t=*a3TVOb8((fzA$9Dh0 zAOmSs^mMvx|QTL$AnQ=_|s)4>k;4<%>>|u?7v=SLNfJ z#LsVZDpG!neBRl4wzPC3gzBrDCs)ze%OULOoqI|z4%WX+bFW?1F+-Z_{f8jaRR;VRtS#a*Ppvwp zBL6YTVEgh~?wGybtxnIYzn5kz(2xx}YozeNWPSDImkVPeD0$l6ybWYul+V|fmoA_T z?ur@y4(+L}O>qSVCY_xfYqndV$twwlzHEC@N(_el-*@Je{4AK}2Q`w{97j0ma(-W7 zNbab};o`4|n^xWV&)N4b6@K$NzGkOeyI}l6d>B?0QwaLM1A(trQ8~!yW{`y z_gUjXOw7q-U21U^JJ$wF;2iInE6}Su?i62Y~;h_eM8g+qJ{*fttUCLkKOkwKw#1yNx+afxGh4jViXR6#g~9mAQ;L z)t?G)X~|zdxPE}}-yPI)xTF#jX_em5Q*P?B^ge8En^Dx+ct-YdxnOfq5TKh#(d&RU zyR?|GVE3MB|SdaSBg3H}h_Pr9$6c-buQO&r^TmDrhNP{?@bEtw+o%%*N)+ zDWGd!0K!j>ZtM6cV2l`18 ziq(PK=^^QqUCHA&a17vfTXXaK0f!T_@+ zr6|7qiSOl2D63fo01_9F$B3T*CVP^=hnj#f>-hehnudl|@7DWN}_>QG6Ov3isWU(mUitE8G240gi z5^Hm;&Al?`*RZ7VL0b`+3LgDG9vay&ce{7V9iCu%=lAB|JZ7$I1(l0%C;*HwmhN&l z{9v>4-61`%s_QXEUPm>6h5Z_wG~*BAWzD^L283dCZ6{CN!CZ*V&(Hv=^-`4Scwi51 zj~VhMM$Re#+(kf`@cG2a?1jsr^LO=pdT4r*%7%0~I4gg=JJdMP`8_#S$!9F!+wv!2 z`F%1~V$OC7DoV9(&qYA&MJ-9~pj6?iFaFev9?^ z(N-#+?T`|$()IAf;&{k&NIYZ%r6{0uE$I70x|SIi@SWW5bCGP9YYdyr`3c5MI*m&HZeYr8_xG%;o3;dh6bb-QuV{Tug2wyA#5Fx+QzU~-)gg?`b727q<8#TY)X7>WCNbQ7y82>Z=c zWR8(=raSy##Tjoql!JCxiEZ*30_I&}Aimu(eDnA0pHYX$b$)H7Y}l}xlJbd$h{aMn z#7l#yfo(LZcj=EvIlFbR8X0JwDh-x{GVuCK1f?+*6qPF)+O-biPc?g``)2HjEHaVG zAHhBk0@h7@kB{9hI6Krj30R>+jI0xh_cLd z(jmPB$Wf^KB7^88GA_e%7iJRc!P39v-q8bG;0uJKQsE?KOX~?L8suiS5(!u3m0j_AddHN@Q z4gXp+&Ezr`1O^Tj?QDk?27ly}p!$VHEy`>pq}rDD8r7?na2+cxR32hW>*x@^B<-gR zPqmQn7!TGF^FwobwZ7D+Thv=Ik)Mz*<-8H^$Pvg?X|CvuMB8!58whGNna~EVT40lE zlU#ywX%1eqgR@F-2PqPrSc2+TJGH7`F4ZDIYb791Dvs$GaxR5wIholEhy_2bU1DC4z4!5SZ=ARGpQi@T-N7D6OkE5kD5H`1!By67!03k=n5l^be?dJ2D*wcIjrJx#ln%k>I ze09037r1B$`cV!8@x;OGK}{4=mUyS$ZuvcrJy1e!sC3?knOqr@;TZ|we2g#SDSAN~d zIyZ%U1w5W=N#Ef*bedn2hL!Y7d6q}xhpwZ=0E+e>|C$?Qwl z{KW0&wPzo^k#Jw?%YP{zk+uF4=o#0>1>7WUL|}P1NHe#zICl5XveiE2VSB?huJ@nX zi#U8xoArJp)t0)hfY!ab`7ebdA|RxkBAZI2kz~;Nkb&R)(HeCGP7?cg&XFTr`1~<# zGI9z6h1^r0&LrUtZGg|JpWcG@Xr{uVQ8z}uX&LksJnIFEPCTFUOapyk%=KICAGw4? zNJe_{NmjoG2S*2V(E7edd|Lk&m5V+eXw;KRNHU;Z-#GkeCXTlX#3-I}MyyKEd7)|4 zVk^xpn5o`J@M`Mw@3>b)9sqh+{kOI7b=twuX$7a?`_l$1M>k;Vz{ zdruntQV!iA1=Yd7tO_gKeLqv*4YJ?c+XAHeML8xPb249)E0LqB#@yrLU`3g>CC_0< zs(CpvTuqjr;5|S~Ui$vR8L)M4;t4gq@lx`O(g(((Ki=HOtM$!NUEi^(qZU;oPijcr z8wJ&rO;;Mi=5cMY5%pMNOeQSU_J`!v@Ov+6B}j1DZxL+l>Jy64Mv2b71X>n`C62o9 zU6OJeZ)ENdp|UvE5=GXe2sd5JqC*?J2Hy&sH@mm&V1wKK^4$da^=C&-ZdFMy=I<8F z3>$bED2=BYMW_F3S>z8X?^dB|fAhBP{Os6__Z`2V`8m-X(etCdb-lMYLim&P@jlx3 zDTY~;$}H+MLu^KoLy$R1G(6%?2qiA{JTXFb=pAw_M_nQS&Qr@T6Rj)05UXa&8o$z! zn^&%T)nLb?FXJvB!(+|ZHtn~OSzJ7Wv!laUsWQU)ds3S-i$kpqMha-u4J9cyn`vc? zF)eQTPJpl6;y!Bt>LK0s}`ZwQ+(jt%ze{8gkJp#YMLClaiE( zPPWin9`o;FGU+~xNmy;J#V1Lc@nU(kM(zxHf3WwrYO?ex6rH=rI4LF_*e8T1k#hDhZGN zp-IK6@|e;8t^DHmM~ATQ$ka3s^Zn!qiB$=J9PxT(EDuM~`T_yvygbovH|8JrB_OX; z9bl|E#f>`S*Q$z}ebQSqhffYptj4ZWl~Q$4Wh{Ov8FB3|+=0g@ok!sJrX#5M9Me8U zjT6cGFnL$nj@cw-8WMjPHH;|MI7A758WubU0+VT$`3NGo&Zs_c+mfR9um{$Aj1_~* zGcA1`=TxlqYJs>6VQRGa-7phUbi2>aJ#P4X$cNEy)uBr0$wEBmtt$)mlmg|#u`_QK zg&UtBH}oAR2Vq8zI&o1SpYbEvvTT3E^@y;diqg$n)Pzp>bTo^6Y{sJ`gf3UvF}4gP zGsj2WZVX(h;FeK36G)A5$#?w^ayK(-2=-k|u9p2zfz8CySDyNAH`ol#{d-LT3N1lk z>!}Jk$k+WH1W~JGcmQjV-u_7C!fy{1dnh?IZ-ygFUGDEC5jfP^05A|kQl5^L|Dg!* z!RIi%+(iB!3}8*x)dHvWh)&hZ|Gn5Fxzh&)pL1cZ+V(#;Fhm1RJ86cF|2-}J-{t>3 zO8+GKBQ%enmGBD3Mh5by`ckTHsvZjFlk9WZ@qcuJR4 z0LJf~Ek(gdf%KrJBxkY^r$wPZp$!78qyf__!|YhQI|REKujg2a)izPq>-9lV>Hy68+lN??dON0o9R~+OqF(qhWLJ*+pskw7{cftiz-Q zWA@<{D+yMnJCWL$t`DLgN4EmU3j@|If&`^36EMNrGpr;sDX7TTLM_Dj zUdt#zCeOr-#Me;gFA2seSsP`zbA}Bn%37p3PYEY6UuxB0um>a$| zP#MmG(q*kQnF=OqG8%jY><%R0tbQy42|SQx;LNeORs$>qVfE*yoA13grw%(#Chil| z0rI7Af66aRVzZ_M#Zg*>Rjdy%>F3w5dSFxGhVOmn`qljE7X|faRn6>JdrQM~ zq{tLKH}EVtf4wC4JTqPw*(@0Nk1TFWQ%LTS=h~A6B_&D${;Y@3mh**AfsR7j{DPe0 zsv;VVbu-MD7S!(P2F9lQWi$Yi;R;sc`_Skf=*0s{!a`vDd7&(Q_=gNKKYbHea0&t7 z@ghNBIIcgd?G_$)Zx=*oZI}F7=u{%1YUbH(0Cn>MAwW}qA=x$?@E~`1wvJW{!hp@G z=;Xn}*30fEfYv9&*4ZX&SogsY0JMeRo(YR%zynsU0I+~`zQb~}{``y&Uz>g@nTgGL ztC5f}3dq?&(bCLa{Brm{jEMxxQkd+3kG%vFTZr|B(OekXCRe(Qb)*p5if1+*KK8&o zq}BODCHd*vE?^cI%61^T z$-hR&CNoYPg0lHzJr__BClrJBNyD3FmsflWL?+$UvZkl)dAph1p6%?{p9h2mAAtaS zdBqfO%a_(x?aifL+TvbwM2Q8~h6czB&6#sj{()G#{7P1PKymSvq@|XR?eC~TY10(N zPRaXBrY|dSUP{pWye_JAbLlHr9Efh7t^HA}^cG#_wO;TrFAI-Zf8?7gFQxdUon;(D zblEcyW|`oLAv5ll)nT>9(B8Ty&=5-eiTf9&N}+dKH-hmgmMCYaWodoxTUm_q1D3%; zhIWdIQxV+6^>I(%r^KvxBKDPfsWN2y;CA^sBYfhF9GaFOCK^iFyffvMy;2xP-J;hM z?Tb6`YKQ4KCYF}Rzc{T$rffs+qD%ZO}49{YXNR8od?N<}+^-KuB<^BmNLL6zVbAsVr_p6}yEkIRG zVa^2Pqb;KluvlEL0r4M-i{)ApO4enLBQE1kj;L!9(~8&L%Ge6U&}@af_BL}bmD9BlZ#9g-q(z@Qr;{dcbuJ%mf`M##SJ=qnJnK* z5}BAmvN~__mN5H9;M~PpJoQGH9ZQ{I9&#NrYVe-^Zv==@MgH-aP5nH$LzicrFu?II zfZ~~_#;Z)@jD#m}oYY&&B`-X`mh~qz0;Z=@#TQ5E=O)E|(`K8@r!rZtg7_I6kNP8(u<{@VG9ixbZ2! z5RZFxJ55yDO)y{&I9u;kfW@^5P(XSaGkjX=WqFxh;F@Q5P|6o?r~A9Ek&AK717=#$ zX@0e0K8k_6V&*9<73z`Leo;!7Wtzi(Z1n8(IP84?{LJV}8UwJ|X(fW25I2Vh?N?8? z7}f!|)b~=hBAX_#yc!&&Ougv_Qey%BdDejH(jNlHjAnjI-6_bK9Osz#xvnSMZDH5% zzo2HlIq&dmwhmER9|glnnZ{sNq#8<{W%8Y{RX>8UyTNTZ+Xo-ii70`sG<~ONMGF~K zQWN{0yDT_8@Q7_MRx!>HdFUzErj|LhzO_i}{j=k?S-B_B&B?HN$DXq{eDR5vJBg;< zd#mjM6FQ-r*7ND{a>0t^tVeiD!v#he0t)au=5~(e!wQy_@%KF8XeYEn;0PF$%k9rW zGWu~SKaUnerf7Rf&wf{b?B=Fb3>=*U;kQ z`TSLci>;xu71Kkkz5FtK?7{NHds%#qCUCFITf_BdLL1D3&B`GE@Qn?Fd6r43QUD0m zS%53Z2^rm0Op+#E-V#XZr2szR#aUp_<&r!KuM_p%-!MolfQztY^!DD@aCo|IU-Hs* zy(&T1Hs|EV$*)!hu0Q$)aD|e{kOLN^bVrg`Td-ls0quK7u41kAm@7(`)Im%Q`jtOB z@NwM0N$5H7n^M=Ovk8EtD$VqIXkRiD1b=I&QW(tj%+vw0Se?o-91$ z?B&gf z3P(heC0yk*qrO{zl?F)!BW^JYI4LwtEYz&!2(m+WWzV< zKpLPxonl?L)QSSQwQE$~My?&VLYIA7^Km^wK5|WO`Tqpsm6YV(h#BM94yv`j8qYQ( zr}BZu7Ds-?jXv`bIrDAKC2Y=+PKLA3oFDcEiC>G+wR3UTe#!i|ES-@@00%-2l%?}6 zmwv-I#zyn&KFM?KNugC}H9CJxT0koLbT+zp?lnhi@X=d2rdR1G8TD6;g4QZWBg=7y zuS#%*95izJSNBV)G@|JnZWdXKD8KjG2N>ehkH9A%uaC z_;P5P34|sKzvNy|$BvUjUAmU|W!_&UTSf|2TgRsGF!Yq``%++#jUT6LW6NluC)uh< zvwwfY?SN45zFI!0t=Bp(EeW!n% z&o^^YSff*|1fRAXOkuk9vg9$?&ZBg$XM`7f0(btA_EpNDYJgRgkOBq?@insEy-)1# zAL0^-lPChOD$OnjG%wm-^NnX(4&$99>&McfroQ}rqir~uWmKYAX=d5XKU0A9j8jTE z7!$gJ8bz~X$QH2)_kLkl8m7ytxNr2G%$MSM+W_VGlwC;x?n56s8~$8d^%nz97PIwN zg8o&@2vVR6njbCw52M6jFUsBXL2qLYa1~t(Y5VzC0dRJ_$$YSi>}zDkRbiL3wFX`U zW#)Mf4et&n%VX{gw4?76#5tbrYf5t&01=CZ7ybS0eD%wB^h1C*r2am%TOeCZ%er2X z!NPH@&lB#(YUF1E0MI%uJ%4{nXO6Q#{JD(hh)EdKX~v#8b?>cGFRl1AhR;do(l1*r zlnYOZPDM@W4U<(^M_SmeJIBcT|PdGxny~?x zGHHCNc@$}^Gp*lc860AzkC4A={{haeX;}v$o)2{b?;duRe_sVagm+cL$SeT?6$^>e zG8EJI-);%P5i@E(p&>NAaX8N z-9SLA&7vQ8xDXQYW5TW1K=5E`vf68HAt^#wq{% zg}b{|#K|~@&g!PbCC^sCagl>H3aMd0fAsNBp%dbItfb~x{c}3u1ie->>N26;K2-QE z$}A21*A+YJe^!p&JbiXMj3-9$Ac4tyfLQs2qhrJ5_rOXGf&-Z_FDwbUck@fHhX^cq zs$NXQdrtsO99=rcO17TB@Z^9e5)&&|vHdvj6;@-NI(d&T6aG(`q70Z7U)w6qUt#(; zJS)Fsjbxv8__z`EPgU@DeOm+E7oE)ayndt5v4}3?5^!-us(7XTUFiO*CAfHidE{qU zALBo@*k22j^>gq!HwO%|eqG652S7VXfqCTQy8D0o&#qnvpF`v^{2z}?-%oH_t{|ex zKT+lXzJaNss@BFMnA66jWeDphLhcHGa{QnqC4g&wZ-AzdS1SBU%?pW1Z=? z1j^JL&;68NhM}u0yoUHgqc(A%dB_5r`sTK<9K_DbU@l5<6c?0{fd{}%cyDv6w)vV{ z*IUNHH~U8Z+Yf<$PSph5j#QpN=&LXwAAKWiw2bXfF!Wu{ECkW~9Kie1NZGyPn8{@< za#I9Z-sCkKqMd@f#win19#>w5GtoR{-YXE4RlPqm1-y8(z=}rB@wxDxHZYY|ds%QB z{QY=ZA7!xNt$?-?0ez77T;#ASHZPD`;wN*k`K<=HH29oB0u1Pc>Gi(7D7B+<0D2>u z8$$4#AykSg+Jba&tnK6E1XLeaROrlGHH?qen9sBR@! z0}^V8=sqowLJyLXN&U4B{D2d&X=}E*88m#+E+$!}HSO$*JL#O|eVMvS3!rK7^kmcP z&YEor%?nwtWU<#n_;F(25|BXQWe!zUm6D5kvzPzQ>&A)%aPEx2zbzJ;#c*~y*KTcW zvykNk=a-yF2m*{kAs|t_M$C#qLI?`*{_$4MRHZvI9YEAtUL}syFL|YT1T#!Wt<%dJ zQiU;!{PNo7Zy#29-TLPVt=#By#})ExX*vJKQ7~acxctfDNBi5<7gmIC?goNtes1y< z%Y>8{v2w_1R%!$Kz^Vz`ekL1vm4-Fl|0nOTzFUh;7Jx=gKcc0NtO4S=jkhk!rmoEF zRI}ZbarYUX12Nk_HV=*KSlQc66r?l!b?sqovq)!P7m=bg*=af=u3O!}mV*5TIFxVjX8k=?UN+m<1zBqCT7IShd`Qb%Vx(>B~L-A%safuznF!kGWT@@H^a5>y~~a8ycEAxAH!0`GSNJ z3hKt1=0J77uI%?)!1~cTFDvK#xzqCn!5oECD*(`iARo4wwec;E*q04zhT8l@J6wGm zMLZo2WG5EN&-0SrTQlBi0tVu3sQsrb;L>|YQbNRq)j;E{b@PPydn|?T}*NTe}@}7vt^~|F_W3#pODCHKn z{CCsKYlkWw`!CL&L^lr@(KVkw9UjPpPHUbi`vB8X^K&W~{n75KmS~n!^?JEzijO6* zJRKZ&IeFBn{|qt;WcnrTLajm&B-xcWl^eQ3A-I=Q{Vh5{Ez5-856C=8SPWyEANLz< zMVUBjX~?}soPYR{OIP|4mX0&k&{$l!5o#RIUi>)-JU~wK4jU1rnQsK-p}}vUp`7R_JBuDAN$55BT}gIhRP_Ylf~8b&`oLUS+{k?EzNwn50X3qkwjiv zX{M(-D}Z|`gnMxMHedoPVCA76YAs)KU4iA|E6Xzm2&8}Fxp69}s`N*ESM$s$Tlhy7 zfuoqqQKsBp8L8T}=YXyB*G1Q@)r8ERx;{--7rnrl?1Lf?w~fBi+3-?$mEL7Sv<{-? zts_o4Q#xjw+mg(hG7nIl(-pbk2)?$Ir>j^}_nZh$-0pI|kYF??xSaB%#v*NP|3g0p z5x=QDQStqpQrr1sSd-Tqx7V8&){DZ(88nF=(f1ynf>M*P?sy_&?zqD%Zsp5KQ7xDP z6fv!;%lkK#rYQ=}U|RbUsWP|O52u{(hhy9u3FtVx(oSU-K7@0V0K(Jd#L=}moCSWK zuoU~&Es^1YfAnFkg&3Fz3MV~?P>rJ?Blwio-wFH<7Z%3QEt7JUs&rk5QJN1ZSD8$n zG48mPzY zG(OC)Ur#vIKRSTxT*Kp>ii-7d|`gtT^f0AxU->qAeA!!Kf z4!M8|=PK7G3nrqdu&x3`pIPu5Bk2&C#vRZeO42#|VFl2|k*N^%|$4(uIm8C3MCrhuwaQ3$lzFELKa)OWR_f+akEpI3+E}1Eo7#%#o$|h zAtnC}YQ(&#DpJf+U&Sm=epO?Q>z|W71S9gEaGN1=G5szECTbr?JKvkh-_pQM<+Tsid9>lIi{N_BLuKt?Z zLXz!weQOoVG@nI!@9i|wX!Ukm)ocm{UZwYFM`^XeB;(kb8YYXSzW020Omxhh)y75f zzN-_JcHAjEh48VBE6v<*NXP3Flq!IT#o7m^7t4oJYGv^Z6#mTp`$+hvT;QS9X-PdQ?i}XT zR@ty=bR?4xx`T`4FRacU$2&%-Qr+=TZ9nJ9iReB*?iUaE%g~&0n^aKa#Z_xO9}5-n!uiGVc-7pLbHXJA4j z0R4+uM(MC7`6HvPNEPe#E?Zcb-}z?Ogd37Mt;meanWS{RU>uv^ape{GygEiMvK(7|eBq zyZD(ozM7}FMS+g2P+P<@>MUz-n5>ib1%ry!!?WWw+dh3~HKG@f1 zmxNL_`KE1pzEF#rXDJPip`T!$5=|KPD9$7!H?@#i$*$a++2v~|(+a*vvj<0FBy z>0wdw;lHRjXaEGxpMenP!5ziFC`tLuR-EJpJp%u|YN+iu>Ze`)9zCX*H?^54&Un3! zoly((++K^Y=#JykQ%EU9W?7)~li6+l@0lcY^6LAGPVH~57f}g6wJUDU@%6~@K{+F1 zF>-`fkRgDW4cBPk<-P+%#>s_qVy&YM1kI)dYQ-sQ7wM8F?qslgdrp-`$1#<-qe2>_ zjfk?XF}BN`(D=lj$~y*wx{pTfd~MOJntK;$HCl5B?n2WxdEZ!|>*-QT2Gc}1!`>MU zol~rz@SrIHeG1X2DG3e8_=Mq<$RFEZsAJaoXqzzp4RtJGFuCcTV`i{iX=r<93vS~) zU@*UP`7hUkHDL&1HJ0;zR(Zd@Ic9#lIBWUjglr&jw3v&+{@MPb_U36EV_kL3y*s(cojE~bNZz9ce}MC4&M z0dzOuae0+UdajhEVsG80PjBxkhdSIg?%KG2b-1VC8pjyK&F#78N@>BT{bScq8_}}~ zoLLw3(R+FUXFo-r^zYvY`uH&(!^)N0zo~u1RAMmd7PFpZ6+}{|Y0DNDC}XSRshrd{ zz5KHtwGibdK8%@8cEt>s=^yo|V}gvNhF1Cis#cbA@RX9?Ur1hKbqG--WM6K|8wg=} z^P{p8auI8YBHJNqAUaZww+A4bs|$8)hUAEuoO99EcaK@ltP)5PEjBpp5~{U9MJ2Qu zZY$cyKlv>1sp&D&|Ng~!b)(E$(df^QLy;Rfj6PRogB4`qa`cyiu{~);URr5lk9qH@ zJdmx>H#q^q4CS+YYzL(C`-eCwzG&8lEXuF0oP}cZ5dw2n*10|1q+O3`n4%w6+n;pU z7T1oduJg^~#mad;i97%z{HroLQRVEZ9;Hj-stSYtC95_G`;aPkZn@b5bAdhe1%%K2 zO3+m(N2yrwQ*t^b7!E@pFuA~J!OCb(lQFoF|Ol~vvd4p-PNyu=M0u`p33d@Ss}pbTen)YOWDb^ zR`hNa*;*v$KS{m&KGx z^NW_fddO>aCB&!T5YLXodwv$&{gvKU)%^l_N!;~Z%Wl5i+#KWz1vb|j$wUa+ON*2s z7t|YX*?EJe3PcA<0oGAwfB!KsX)!o=F-du* z(KMs3Ps^#DQQP!)2}fs7>Ix0VB(sulC@SEj@(f-RShSeNs@&S7A<$HYYo#rV6){i# z$lxyLI^5m++B-oe-}dHo9zoK^B6S0)9p=^YSrjdLu`;K5|0UCym}g0kxLs-1ky|;X zx{JWe<>fyjHs#nsNDunAX%mk3ew@heX!2sU=soN zmkF%Y=Uxk$g{VVRM#HmCd>;o`PH*2J;`n0J5odgJI!Q0bLQ7ndtGeGzR`cqY$5A?& z<1_Az1fs0&d#ye#Th4Y%9szgK`{Qr99Gsq z#d+9Id%hfPy0b;9PA99PhTXh{5WD&XovMElbs1Sif*vn?72gm)LYcX->rl`g#nHy6 zl6!Nc7EHK9PD@W&n;!}70Ln-F0-w4VEb~Y4x@WMV$U+op*!-GN+?WG2)*~p=IcY zx#zHicXxJc*@Ob5!nDkEdv|{^j=ry<(cLOv=5Rmwu7k7dmNN2cXy?38K=l3Tqwb@} z4a#De-_ZSg&D8{SC94F#|adpUt(lm51zq3|3HuU#=n~Kv46Y;w#8Ct5Y_%4o%k2{aRprC zg8SbJ?EgLuN(?UQGB3q{%x@|gDd5Cnj6mY}-+QsYsoVoDH%P>@|CryX@7xCcaj-U* z|1hW|v6Q?S+_>|`|E6eh3BD22t0bUYQvS`L{@>;Q#WepvKl%;c3$nt^6igm}jop?=u-USToc!|Ei8Wb0Jo%rZ=XC8{Y7#&0hlDyT6Vi4$sOqM zVW5ncAY%~b%=^$*{ z2Q-LBm0-7JngupBQouqx35Dl+k6E5Z=6VyRsQi6zSGoJ#bAsKA#DKRZ6!e0(A}4)r z71LwQbM(M)PgaiY-*-OU9d7HW;>ZS2W5p+@7`wq2Sly}ymg29S0luLvT&Eb;tqR{g zX+VR4zj#wNf@s;|#Fm_(78z@NJR&>b=2*Uv7YXB0&wPlF%bOt*6UaTaG11 z)tCcf5_q$kK$qg>XSUM4{GFTz-I}}J+&``BYk?+Ao9YGn?K#9JLUyT^R!&&dTD#-z zuP~t7qGitLv3pofxn8*aj|K#Pkdfnii0Bqv-)vW;s`1pwW_?8YaCZz4)-o5=uw8*#C4+)# zWF$Im;OP7%X1cog4H|GIqM%{18BpmG4iCK2nduC@O~?>gDX!lNJ6Mz5b?fn|p&28G z$wyD`u*S9u9UmS8&by@>yB7qwn3iViFFiTRTGL=8u`|0wP@Ap63ii($}e;(I_!lucmUaV+bu$*3v55({}>= zy#sn7(w-welYe`3H==t#E`@o`D`#bS(kIZ_%nG~D51>^YAKg_E61K+xa)|b{1pO7; z)alBH)jA|^Tfb-sbdy$@ELVTA1zOXiP?*~HaJ!YRO{}8tP&ex7+nWX70Rh3l3OW)C z`yX?JVttic0l-}rNivSXHb;W3;UuwEx@}q70HQ+2BZ>9|Fz0mR)2CZE&h*z#GGAk` zTYtp^KL#}Bt$Kz0BNQ&2vd=S7m#*9UOd7aSLomUZXdvYze5GhPkkSKULjT5B8+I`* zz+tC&ySW1{@J^TB2Knluh#lBB!vwfsqR%fnQ}j>E)|ECn=?5mC{<3-GUGEdb^{`FX zUwbO4EZ^q^dP7@{1@#8uKE&BNno{_-V_z>@|Jl)KY<6gFL3AVA&`@4jY8{{Mn>#}G z{X8SyJFM%@(h{pLd*4k$EW}0iEhVyhyp)YU38gc98Kkd*b=NX3=6L!t$+iBhtUx!V zLSL`n+=83GlP^y0@54TY0mnPT&CXt_%#e@ElAmv7)PW7j=9FlI#cqS(to4S)iihQJ zHS*10`!SJh{o&Od-+YCOWF=c1SB@KZd8ApFO8{W=%~+bmy2TR5a$M8g?!PwNO^P;t zMP}n!kKOhTTCGEZCY{IjR33a8(zR>B;j2Ca!)}|9K3QJ0TQ_nT&KGjKUI+3W+ZgDd zsZ8YP%mwVa+uoTD_T`v_>4!ay?o{Cn3MtV!T!P~U-Ids;&Q>?Eb*IWD zw>J#3z-wc9!n6)-Q`_e+1cP!+1F(v6am(ls;!5<{0DHPYQ6_3ryT$Mb*m)BEj>-*Y~B z9B1a9eeZj(wXStt*J`{UqumiXP>fId6Lis^`OD<@;f1~SHX1G^Xnb7tSa_ofeZ7gb z1;z&mj>+%iA7njx8|nsg9aAphz|^0lN?T{OvI&X?3}d=NKv?$lPO+~>{9uT&Bp?lk ztS>r@@$%wRW|jd%IrQYAU$kDpJX+=;HDxF<^QoODfkLd?4<)eJcj5}atbp!fz=OXg z{?<2A-b*6F)O70Gye$FrJ(y4nVeO*VH6wf1l=D{DlM? zs!?#Bi=H2$5o=gkK{rv3C7uvYjE8+ZHT7ftAI(KCV+AHaRxKZvdQkiv7f$C==IDGfA+$m{Sl2rv;Fo8CtQutY_gNPW6WlXT}pDxq=`_Z<*rnE(YmBtK!sthOC-5Q2*P(ztiU^IbU4%9n<=yJ63=y zy+zAgl?o2(uug_j$!D9E^gvE&UU#{P__6AIf+ImxJ^&AkUGI5cC%?_*pna1K^GU}ceud4+ zvo#rYI`~ZdyY`}tF;T8(B8oEi)K1j?yfnFNSowji(+4W{5?h`iI&P)M4?dU}y$X0N zz^p>wrLYjbj5XC^teTbLx`I$;KJk#>%}_^Os*fCR7OZNgaciaN#>%==1#zP}cY8wE z5@a$4zMrVC?+iMHtdFWJvY%6t)?YyFTb!&|ZKv@g%r~ceeCXrE>9(~+^WNw+SR41tT_|UT2R<1%E;Y2Yx{zGK>JU&YbxlS_4Tppvv5m^ig2uS+(lqPNJ<;| zMIcv(i1_6MmGPoJ`S8;w6zavw8R&LW%MfmbYcjn~Bar&+;ovK6{8{5KV&>BZFPJY& znPX2ZxOe;&k9zQw2UYj^Q(bO@xC8mb%spfbBg_NzSk~*_gruwE5_Av!xSsL$hFY|q z7%a+o3JI_+kNs%R?EP-*nPD`#lMs5TT}&5JqAT&64{&DT@(x#&zSSpAd6O)ckb+9t znZ|yV5mj^P;jn$%Ja*R%a)t?Sk;WTa^BLE-JK-ob{7tauoQ{vr$X}#(B{ygo_w4W! zmQIXWE?PTH>}=+VsPF8?{_GA>yw3kFha#1A(&nSh)GlQ_DopJtq-(?fm$){~5%RHG zm`e_#V`Ebt$p>IJr(UKjcUT(*pkk&aZ$kz&W!!9RE1x>QJ=1%!KKlXAhx6!h#B`{fn7tAKrajT?pbPgqPLrxPtuQ`A4SX^A(%7YEi`9Ab*2 z2ge-0m3lR3!9?42b%dhhvMKoiX~5p;ssG{7$cGI3+A-(A?Vdf#_5(ni`cIX+4=o4N zgq(@2)?b8FDljrLS_R5JD0{3Rx?Nif_&B7|OjWn&Of(Ji2HebnUq*5}G?31tZfR&u$dc3q;T{e69R8>jwsdDiyV*?iE5LK8;CgGyYCo6q{ zMa*G?PnVi>a|cov?kp%f#Yq&rg&N0-&k?u9yNs7rqpfnANbY?>)fxN^xZto1PG z>0?Tiq3(Y|!ymYmDLA~K_wzr|_Q24xAqMVM+QicyIn93k0WUwvGCzH^v5?oGQ?zTr zg4Uh#;8UAWL4MY1mq)Y|$CPn*-lTeB4_Xv&-socE6>iY^)xmv@k%=iMLz}s?Ix_QL5U4SK)7WH#kj&&_c8boWKCMDzw=vGF*yMd`9lNWJcUt1T*vQck3) zb%gA1$Q}|%>>s#48XTN`S|9%5mMp_-g$JtTgW1RLSW~SSCZ_La4jo5SgoPGE9+r)z zIr7mPa51@pqW81QWPMFq^~97hp`1-5BHfD@risdKD^CaZ%BGcv8wLg71dNQ&6(7|- z8=TaKNDjRG8qE9+CBrd;T(xc&x!RuJ3X9L4ubtjTciVo5k*^Va|LrMSKKh(kue1)N z95MF!2U>S3^Vh0|E-?8+-z8t*pqcP*C({;eQO16k`Ip7E3IndSiZk7%YRfkFy=Il4 z$-;X+s?SD+^bqaFF{f;HzqQ~?>rhtjGWk~do{;{puWH#wn!@!#`LGEcHlfb4wQI?; z+g0~mUuK!;y51K9$r5(*5{LHPm$hF{XT9Ys`eKr>?J$^1_@d0S7Y!oU^^cc1W$*h3 z9d)tQ=C^ziZlTodOs2I|KbWf-b8=cdeq!knjVB-0HrKB_zP6E-ltUBi- z#a+^mkas7AR;4-@jTS+qtsp4gE|c4^`I+@9#>C~;Ksu7xRmL}pyOap6320n}>4a@!JCOrA2Y?|`0!T?$B<^<^@P{hGq;F){@GS>Z|r z97eS&Mr{cs*42;~otkAZB zIe|8yH`f$qhBae_Vo~JWZVDl6EA6(V7fAnpr_Rg7x;y;J{E@NSm0pzknh1Y>)v<)v z)k!V6%r^^(T ztei_%DJC`(k56SuUYcP&|H^<*!NY>fb}ZM|ic4{dl_Gd`^8Sa^*EiL*iftkp-IQj; z33`rYC{0l^3(NsCS0ClxNL1?#VM0wq@}6UXL)eDtWyJ!#5qT1!{VB!X{mhC~`?tF> ziI4Vjx_@HL3F-=nEn|k98GFh(Z|bskDYJon2?|y+J5~Q_X3x`7PgsK`O5ZMqbjx7)(`V#l;)NIka<2OI$Z7hHJ50I~Z;( zy^qc->szL{bnnum&a?~{nP4))%jRry@&Suj>nJNM@~{)&trD;{ywix1!8(O?QS=`DA=KlotF7^T#N5s z=P?q;C4Hu|jAIGPqbFgnUt8WMReGs7= zIsC9B0h1XpHWcH~7vErfC+9qNC$4kH{n{yzW&(*Nr51)mbBTlBxr+Q@R=iUOWUJ_0 zFf~s?xOK|vGe`FePE5N3F~yZ)w2lVbqPp3Jf97<`=qb9r%&vpIp@}U*| ze{S{lkZ{)Qg)fjihPQV$_>av3B(T}aseT(s@)f_GTDw2)soKq@{jQ~fT*^(SKg8)Q zeJsGwFXX**-5(#w$_gmX1OB}s{{)j?o7OZ6?ELf}+C_itK6!{Ly*F!*{-cpVaT|ON zi_7RAyRUBmqDp%lO(OodRB6yh;Fp`)|6})+m4UC7rxoS@MK1o8WIutJ-v7J&|G%z2 zhfccMnrp4h41mjEQ27q|>p_5j3vhjZKtIrW4fMk)$q9bPlMcVD_iXB(~>bHG}i}gW<0>9#)n1}EDh(W z_b*eCH3PpTN`PoLYyoheL;y^LxbOwk>FTZO0e+zScGa*dAD;UG*=SV(yPfN$g+OY? zsYlD9Cl2w5v0FTTGYq_52IMv`a~c2;ANP#)iWe}$YkdM}m`ZdO@F|31@pvYmbFd$1 zuQbhu3T?n3$$Ay|w|UP;pb-CN8Mujw<(G8c-DYZis;c;Gm}{UFbM|E#R(^Ga{vXj| ziKWjWVi{Zv*Z;X8x*Vw{vsg#dJNYgA~=mO!e$H*HHD}k#Uqm?faYhx zxC%T-vrHHC;=a7%`aU38vUdY=KlQ@qN(+F0i6DwNS-vwbE80REf477!_pv%92e)g)y&-vligp zFs;9BQB>!21ezeA0r1Ko>K^72APC!OEdX?CbU90Q1Pv_3Vcb1Ds#=qv@SYPbEd=vj zc#?oQ+$`t=UFYuRkj(#?GV*B43m8+jQeXq)zu-&G4W4JspcO+TqzIwP0~y=M5j~!L%q1`4>qiUus5zwsRf3C z6@x{E^%N}HHqYI9$Spx1FcViedV$1(6ta#yIs|SFehsJd><}z2f_aYVNDS9<1G<6h zU5tYOObzpAt(@d~#Tc)OF~H-Iz~N!6nR$KOj;m(8xqeILkq2tqINhGqoOg>w9{Uoa54d3?h`~425gf{q?^DE0!P^&^7{*s;} z8v9~RWITGy!SO4veF4KA;&Zh6s%|mVUl+<@aX3wkqh(DdaeK!!H3wx0yD2K zHv_WsO_@kXBW+@g=Wu#Tpqo5ZAm}hTmow^)C&165FHP(OO;&1uTUGjJAq_j7_rUZ6 z?|pFlNXXiHVAmh4gC^D>L)*`Lnq~)Y8;@IwlyRVI!k&0d9)^)lz(Pa}x|{R*fTe3o zR*oAgywh=%;ssesNpL7GBpWKR&kLUiNjRmv{`UJ41P6)_8s&%`&AhqjoziUls*>bZ z3+9R8Zanb00#oYqePa<$NAL4CeDcr*}Bvu(L zmdSxU5y(My1&n-e)2OvRMaqzG^2`p6ft8J8-ZDt;vuOUzKc@GFN;Je2Ei@trKW=3| zr;7m(-SlY)>NODG-6JFkd8ip5l88?~MkXPE={eQcMH`xE(~MOcN>^6x$~JB2=b+>E zR<9kl+Xh&)5@k->rhlpDv;gx9PXF(*UsW0$aP&YE$oMDF#iooNOP=*>-Z+F&-`0@2 zY@;~FP2vtL;}w8&=pj+dwJjg}Fw-01uQ6}YUBZ$T$AJF$Qz8wG$*ZF!>eyB_vFWkt zrd>-WA430`FsfUY+OHiRjFx!ZE(krsv2mejf|>wpUw1(!ev}zgaut(&8&+)F{RM;} zVUTY&zAxc{j$b_yUoT?aj*Gjw=vr5tYzXJtGeZe@^Vecr36z!@lEc_ycV)SPRQQ5Jt(+qVbc+8mYNN)SMzY|Q{W976*L%5@iLF5~~;%MxU7 zP`A?5bItuc8&2eKZ@IPHKy%-3j`Lxk7&tGr4U^Dg=UQ{#ZP?IB%xql0aSNS1>PV7I zzIFlz@I;C^wik!g812+R9x!g@;B?=n+EDD^hn!GuGl?0N)Wi|9Zf=GB_j28$^Cp2g zB&8YStONJ;sGuDnky)@UL9o!e{D=@PwT~kF@IvX;+rtub*qsWf(>$!rsGT=~GLY0A zoK-Ol=9ISm(xhpWW!s$DG7%(0`{Yr;F(3iJ1>()=hV9;S>1;ofrm=z3Pa{UK4o$M0 zEECMKzM`ARO?r;(rVN#;1NHRW4}jb|DpcfPto8nE!XFo>h6OIJ&5d$)#CPLHZqHh8 zaDg=Dms`(+FBuEpM;l?0zIrLa>dU$~^U26~Njg8;fuedtoc6Mi)NPEPKo;IHJI$$2 z&Yi*?pcs@@cUcCWYavqN=%f z5lZ?8CR3{2LWS}BLg1R_Ce>mQ#$6>9uGFZOHsFs+c%xkY@yvesEK z#}C1ZOW%9)`32|7J>gT%K{prb=7mp&qTNqJIR}M^3LfV0msS_m|6Dm183|5r3qbV! zy1;)Pg1xum?bznc?(v_<(PwRt*PglyB&`*0;r|H9vJ`+U%gUgHwUw)=8^N z>UzKZY;a(*<}Y@tpVE~0HDACPM3_#77|wBvf^xOgfn|90*PF)aZ7^OjF!sl;%8qf< zdxMqS7@rx30`2h)rV}@tISpj9-}qACOg9-5X4=KhZAr?yY4A*@*1x}__lpHyNbYw( z9;dy*>-;b=#BJ1cS&{nUpt4~-p*pGf0P+g?24-%QR)v1JL`9pjgAmcY=dwff>yiHF zHxd9A{$`E&>{*kq2lqYR58sh^7JxCL+#Pckqt1E4HHeA%A|}AEpmX!CDDXlQu)Jl#mkd)~ zU~n{gXH^{BO0WweoNh`4ydrw^oz<|BOR}9vC|YD}i^YVK`SchVJUBwr19tR>cNxB~ z930W4oXcMBhnx$;cx=gvC?-I3lU^AL*cD_SF(eHFGX7to zGQ8-3s4cE~G}d;vcbxRibRBA+W2 zjkUz0ixJt!n&g9KZ1x|fSK#8(U;JtqTrcVOSZsHB-=XBco~>_E;t#$vh3iQF`GPb% z#`Tr{yn5Dx-w&=!SY?5)eQFfx>Vp4#Ay@~Kcqm=nRDf(Ga_D4jy)yD+BpU-=b>(3WBTnQgP4%6AUGQ0i9?*t<}h=;5&mW}@F_x=4x zy*-AsG@81Vi2=FaPrq>SMDYC+m;+t^1lfPz*|_vj{DFw;aAjunpX2UlGZ&GyeI)|8C6xYj2FVK66egAi_8_Ub>b6B8s!P z#SYL|DuF6>`E}NKU?48Zh0;s?lz+Djv>m)>74?F9MJt=w=78WZykgq(glG7vY9@5{ zd7vq$$&6wEXQyy4ar%_04y4~^px9Q1)aas%0C+HigPL&w2#uow#Vdj5_(x}5Cyp=GsZ4bky`nF;xsb%eTfp^j1OIfg2yl2GT<>Rbus3Gvp{S>u+JB3W+ z#V_vFFqIyF)GZ5CwtNuPNF?PLC|9$AA7O*j1>l+{K-dJ&7h=`DM1Qhg9s>&eJ+|7l z_tL>+5yrr$3nU}#nb6?Q<8;-$WFB!SHFpsO?pUrj zhc1rV#RdSHGG@39kyL07zu>hAF=EQhV}+r+ZtG8t0V6IU6;mDw2Pkhr1IJy zmKk6Kew|&MKMpBmtijT@ZZn{A)@|WIG-}E$=izLs>DvMp2)s z1nz(dB%t(nuLE-l;NL-#q}iuhFFm*>hs3=nvVAd9Ss%S+$GwpZn$ELtmjQ9`5tyVW z&o%+rEh$}YfNi>1ya`nM(g~m>9s~r>V zccmt`?mByLsXsoutksK&kmTzcp=(fy>)RuebH5W z^^J2p^XPAp#@qXL-xHsNM_&m9pt_r+9!m50DHM35HK0ilv2Pb3IAnkm#@U?9h$-ka z7cmLgd4r&=isFBZFwWG#PhMlv4iARJlKI^~o*N2{x`WvucA^Zp6>qP& zZM1xDnxQtU{XG-8nj+q=C|UP~uGFK;r4<;Fuy$#2 zdK5lzqw%e3u{Z@T4uOEWJ-FSP9ACDIvw5F^7@v3kos!>^Oiky4^Qo()7w4N7C>n#E z8i1?K-`Xx?RMB@?EvQ_u<9Gp=ZhvSN@CuVp!WqDy-P9b2ylQ^3u26!|?I~NDA{9vm z)zJXgfrS?M1i>)XAnB^ql*gU0#ozOXI-(_k2Z~4QU_fzv%bS)&vWJZ=ff`c8j5(CN zt#>h~Px`|=qf7`*a5#u!x8D^)MZ0D=-_MN|C;`8xK*MPdvdHx8M;xjkhU8nZv9TE8QWYe^%PV_X2N}dG8D&WavP^&mizt zP3X-&CwC5um;xawvHi6@{%0DO8M4sf-$L3YES|rShj?iR{n-$duINR}NhYB%f;Z3b zIXbT9wthb{0h=DBF`m1PGDUttyrJZ6_9t{wknlVD0){&l2=gHTj-m|KT?fzh)t>7Q zE5JtXP37`U;@AdO9Cvj6`d$s>e2cB$%WSo1_VZx{%t%I~$Z#`>ADnHBv3w_MrOqK1 z>|G$jI4`!0farL?yUQN(D`6u+(oz}b^HE{FEplrYoEU58q{{pK{JUZ5D=u^0oXI%> zkog9{lb#B>E^6U5H?(w7fuBPhZn(8(fT$-dTrYu8p0KVq3zYxo9l$`~)IA6O0`d^? zRFy`#Ga#Eh0wraJ&?9|eS$x05t;;^W)cnvX#QPgRIQE_7?;1a67rG&kL)EL5LhO@3 za14yliYAyh;^TzbLQ@Sx1HC~W zFO+gT2R=8eodf6!vY_uuoZ5;w?7Y;Mg6+?n@IAwwxgXt(H0L!y$Y9+zQQ$9C<7N~n z)k61&6t{+GnK%genO??e>ZyBt_SqT?j*z3DLf^FTjX!+J8S#Q@V$k+g?ElFK+H_R? zet7{v^)>)McN|E>nAs((VQ!(N(oy5?JhWs0gpFpY7Fi}M<12{qLHA`iLB5m!*2mMm=6;^ zAc^atqxi6D7tkH!zue*G;vOMkw<&F_dX9`l(7NzG8;+ZWp7xCd%)FY0)6leYlyiL~ znRCds8WhMQn1 znEdkNrPt5F)Djy89He`tO}Zl)k*BRNH$2t=hR?dRH z=b0ORSu_X&HmQY8W+e|LQmUO=I{WH=Ab}|G z9~^{$HRdGrv**F3?a&CuZVoPdYn_I-hY4`(P3|It7TCVAO7#Gg1wwYxlV;5p{Y$bR zl&VZ1Q(5=A9mI$~Xh9(bn9_@+{j8k3PI9v++)3v#0JQvF7FFV=2lQa z7Oh;NCc-;_%bZLw4|^&_NIT^mY_mw+8iI=H!I`A$ZB-9$m(Jn53K#AQ9-oiaMPAJ_ zN0uf$YU`lI*aFo9${(8hz6^Xu6hVDpLxT=lq?`>gxry&#Z4}}$TsG&XY}R%z9g{A{ zSV%Z&NIro+U2!^Suh3`{tXrSZdxKl@mkuE6?;Jye+erpt-%|mWQ9YQB#A~@S(;@WP z#hOX- zC2sBF3$?R@+C-wDZ$D6AOwNCLwZDR-{0Y*v&M9kTO7JKRlytcWHb)7@e(lNqxCS8d z9s@5x|5EHmOtG!9_Bds7l8t&ZCgVHEMbVGe&EFOt52^1>3dKhbN-^&7y+Fh-*vxCk z^#6vb1fwveUpx?jtJfI)*SYjJ_UBzsv=k7&Sx?$>@6RIrHRjbAP;%?JB>sV}Uc$m5 z0`P&pN(71IZzToz#Q(kJ|Hg`mdl5j6Yy;39pp899`1efH>mVYljg$lVH*E$Wba9D* z@VpF_oly0Z06feGL6C@Pqf%Y>0%hrKGKhti{M3qZejW;@kkeaXB@jhfW_WDRgH0tz zbqLz`S&+dFE6`Gyfl4>zyT|kQ2cT^dv6&7`n>l(wrfvpOKn3tuwqc_lfEPaiL`5k& zpb-US!t*CqptRZCK2jSH4~OiwmH|VcWA3FNxpBvFf?ekwH-&6!A;_j{4?K36;1Zd8 zfEvF%*0SQXob=co4Wx`QDPW}D9tb!Y{8I=@e|KZB1sLm~Q3i7ePRpW);sz5%;xkF&5Hd~% ze(}7MpSveeG5=*2DtRDduycjn4;F)s3xk2u^}xp5D-#-i+vMaP+~){%1Qj6h&S*76dMD8Y$J&eL*mN2H*}wtPV3KpU8USO)U@OmOUX%`a3GhyjV#GC00WTtQX2V{HPs2y0I3 z#$D6v7`m2e=fDYbZx0$VEcEj2O*L8FlW(H~WQM{0R=Twt{ty@d;B3d^pD+@*^vc?% z*M(c51$+gx>a%MkYbr!Qqj4FCQM;zWRqj?`(PL7NKGXx81)Xv&**#Ey%R?GHrOIqn zcwzU!;B}WwBK&l8CK0A%dT#>!R>&z+?jjgzEU$g|z>#*kdK5woUey9zp`^96z#MXN z{sx-4ZtFktQqa!7&QW-*xD3pk!W^OjGn5f16G-5M&4d2?ds7y){s|bfov;;-lM%s( z;T7?aWEu8AB;3fFiS^@k$X01BdTkvEHVkvnG8hZV(f`OhmZX=1;S_Yhl|Xv&qASVl zC1dcmtEw&t>4<~4fjd5nJHR|mR_v$=|Av`ABeHd$*DB|qEn6yci?_N*oAg`JB%3xWCM+DWgJSvH1g7zz#WHKO6#v zxRH`l_yMhu7j{Uf82W4iG23#|vY&`nZ2qgdu} zBEnPO&%Je+I@$eG9963M3+SXfBGCV*E`` zToq&>&uhZl2ttC{%-KN6z1d``?pcu;DkKRHi5W}(5q>QghiIQJEa8CFduttFBNchJ zt;GnnZ61a)({6j*0b?y2v&u<3`Gf8oKjlQBzVYOP*M}y+w6KdtzHM}l&`4~N+b?!+ z1t;vTj+oT-X5Y|j6jmZ3CswyoZ>K}b7B}^em(c)IHP+_aseoP13^k>ivn}9mCG#OI zroinMz`Ghi+Ll*wH)lIZ<=T*u*`)F$Y4GZ8WHs>+r)bQce2{QSs{2D(r+a2LUV0G! zs?sg-=UZ}`X(x(9R;sGe<(!qT39Mobv7dKphVBJdUF0=9L2i>7=E_@un-g`p0u&C} z5JDT4y6Ei7U&D`fmi52{hve%o(P>-NN2JS3BKRen8PLZ)aq{W*ugGmKatg-4l$r`b~l~u zm&HzhAagm?5Z3TzSMm+MzFfLr@w?!N))(&xMY|mZ@mp3OrdLpz1UYX4thMLF>=vPf zT>D?6Ekv419S>WGE}6Pwfoey0`r`>RgfI}-xK|K_NJw37 zaK_~7)r8*y9H7IbutF&dH42ZBp`-+%Sc+rCp=3(>X<>Ag^)c=)d6lHk4kHweDoA=5!70WzZ2IY%BJL(biBGoFFRw-u3mJVT(I zqmyDxMy>;j<@b`1r6~|g?CS+mIHV|w0+yrIp5O5zK)5rX#!EXUr!7zwO0uwoJR3Oe z^*WD4fwW6&W9y*oy)x!?ocgnMYiE~|nwuG89Cz9EsIEKbXG=3pm{r+PIgYZSQ^bep znL7J4egE1I`}`^Kf#?qT@dm4yNM=*r8v#ik`>_5GH+=k7Z7&{&Ir`LB?n#%k`$}53 zmp}Jp5O?K9(4QXl>H8kWg$A1wi`(F$hAsG9#rm-Pvm-k$OX105@=`0*fs=CM==)4y z;dS%EK>r96c`qgDfY_hgy#%SlsN*@8jhdPm}lwHA-@ zRs!MkpxN?V?gPdwPGJ_Fnr-QUv6j_|fVYPMTR%*5`nMK@hz|S6GHo?^DNg8j1V!7T z81%l%_-YO`W)u1`XA|q$iXb<&{LyA<11wp#c#KjC8hOW)0E48sF&Lr5*hTO-bFp5S zy`tTAzFutRzX&q8Rvq;WH5r6Bdk=E@1k>7-ZJ@$p2}%;r&9&<)=lY6X>dy^CKGc zK4TPZ{}+2Y;WAq-$3Trt``2)Zp%{|cDS6iNr`6T#s}58Gt%o*~;L|ABRZTA#c*>dXm3$1Zw zsGE036boZ-jZ!(m&=7XNloUo&)XqTlqeTfpDvToD7N;eidFRKXEz6VGPf&sm=Km74 zaI7$>XFpt}pNs3$M{)<#-jaeh=WHVgJcIaw@^yk!_%-JUhxL&G<*GjCv z66HrcTXU|CT?A7BpG>K~*wX*iVHn^uroNB#YfNYSz$3*K@|42PktP(T$lj|@Ifvn? zU+^RH@zE5;pzXSG?CkRD9ZW%r3|pg0Gc7W{)fp^GpTsl$P>Kc z2j2|>$`M9G00GrKkt=f71IB_cASy%AFcrEd$+Ii; zPKj&Bx2jF3W0M|^T-kXSn}ElH>5$aR{o3SO!!uF(u~pRODj!)Nr{7~qQ`=Mq33R*~ z>Pr6#cTx5Z(ak3AupAONag~_H>99&Ao;n(bF7}q;?Hk`K+m3h{gEIH?En6WJSCeMS2*tDeJ;+oy7~u z&pmf<{{!8KZ(_owAItE?=iJjVBAPO1viUF@v`Arne~K{pD%Qsg)JpTRDRPKc&)`O} zH1bwJ2AQgihLbW0O`4ro`8N4UGdK0L=7?3$S;X`aimT#bO~FEGws4GDd5O|_P7AzO zWHh}v@S)&NEXl=Vr{?8A;zsO8#FE1d$!fcUvJ^%EE9zi~1{)`}3w?JgUVN@U&%xnh zNzwe~9qX;o&o&YW!eOaI0*qk9JEaV#Nql-ILT1F5mge-1T_52+2W3VE!q)*bD0*wD z7QM6eW35V+-yT`Vp^~e{h>D z1&Ph>7p^YN5Mc7^Z}7C87;J&U4j6*?ur%J&qHgKfudXyOxV2L9JS?KO!#O@%dXS|{ zg;|tf@&_(H+0=$NF=;^*ty1vtV&&V%1t%fVNg6U`bIC11{-XkI#n(n>v_y&`PP_wK znwu>srVSsx#74WX;&2lA1Xyqm`QYKfzq`V-H{c%Q(z3$z%?N?z9PCY=xtcPyRF`?) zPRCAEDbcn9@ug&VRI>YHb&xXKA7sQVwz>)fUp(>&L-O8_E8KSv@Ux93+X>^J#)N#h z)?M7R>k}$ewe7|{cRk3y%U3t;bz2f|kO9Ur}^{H9TGuSIXM^u7rX z&9>j{hdV;NV}2Ux8>_9>F3C>{xW5wWJXv{5{;A6$ckw;56pqfc)x0SA-*gJtSVA*Gm)?}9RB6l=+RNv7im)YLc(LRkvq8W z6_boqAD1NGp=GpTUN7>AQJCGpa+CkpuDBsW+YlMvl-gknDIG+NT1X8Om+!Q#a_``Z zVDf45$!xeJ-q*vchnnR?cl^epN~Z>GyWDa<$-sT}pGfCD9@RL#3a>t0Q2YnB^L|1^ zW3f3MJYt>Wri-m(fTDP;poRFq#~S+xMKQ@OpX^MG@^i6P|3M%T46CBLel6^FWA@`VdP(5BPbkth`=cP6CLo z=%#_%(M~-hPRt3OH*I9c7v2QLH=kbx?=ld_Z%)41Ms1MEbRKw@8F%%b`NHU)maoZ5 z1QTL}aTNIVF~*U{25ZdIz$^|t19lC_XQw#HH7&z#ml`vA9(lx08hYIiRGHD(@c^a5 zV$bE98`LfVE|hE+?sM=I=1F^_#mnvc^YNt1t?%*Dfe)83H4usjJ{7EJxd(S*;1N5B zmS0I-WN%P5u4fBzOEATjIuv)5i&`#KI^yj;hBxVoRGgwjx=RCGUlU4;*c1)Aw47;E z^kyAh5cNJkXbHz-^26kGb1`#>BjCNXYECTSlJsxm3WpksbxgsDepf?uDO>|2gx!!j zzHNlK)WUylvzb#cBAKI|MEJm8gC!5cJ2?FK`=)0se z&E_g=P>)w}9V0{g*mUQK%v35NPCbd5FCAJj^fcyUS~L#}5k~UKdlbnAt+3kUJ>RzX z%x&uUe!MR{B_dbbA{lb3NskpDOOFV~e4uE$v1vl?LEuhVM=K{PjE^Y4ITD#Bz4b(N zgI&sl-21UkkfSF*j1HN1>M3V!us?Qwgc50tn^+RQ?ya3Q41y$5I=>|aZpbCPKoP*T zOZ9;<9E*^VM+|i*_QzZGhufzQd3H`dq`x(}(@ex!zuiU|n~k#s|Cn-dMYuN5)qE(R zG0h$~-JUlk(_zonx-={tk+Sa4xJ8*I8UA&>b5X?Ih3K-RG9sGcf|*PnwfzG-J-vX! zD9ZUr=GUV|FO$ZI$!nUX>r-X10;35rnjQ;BgdMvC0BHmz5sm}v_AL(yS$x$c(yReRsOs)n56T!|sFHyE) zp3u#nmA|_e$L`Egf*QrisB1{es9K&-E1uK{rTbm0Jf$>??IpsbBS>R^B?4rOJIBg< z_5LUlTxE)K$I-iCMs-v{^2vlw&&yT%zKBlgD~<7o=t#WcT#2Y*r~GPZeH(YD{=3t} zyxSFhYBL*lRlep7G_OQP|0`^#;(VQk_#O4$kj*F~02e({pb0>POtT}Gc^+udy~-Qu zK6{+fWrGbbta7r}FAcE5(S4C;C zRq_495OWb!pb?dF_D#bCNdjOE(zab3yp9-NR+y>J?C1I?l=EK25=8JOR4RMd~~<`g%l0r=PBiQZ;J7TH04K=0t-zBg(zK z^}+Dj**VE*xXgym@~ig9Icx^Apj-0B(sE!kNs)8rK3RkM_JCe>^IRL6Koc z-GOvuRHEOho?q)o^oQj6>^B&jLi^8Mk5&haUN9a?Dldi>PRQ(cTliu3N(dJS%j7bY zMy!UDuG9|iQa(x_H$>?0+igB8>awTGTXi=SI^=86PuZ6u;W2P%?SByPS2j9p!|N&I z=r-BE2y3KxJJ!m1<5T9G6GrNyaZ!xy6j$>Dwt-6$GJ#CLb@F7>24N1Z884HsemTVv zy(Gc6y}*)nt%0rE`?v1qW92l)w#TC;1s*TR(O=1y&tYgQUA8)vN3S_6hab&#p8Z>R z{lhgSv&2Y$*m0;Q$>;Iws{XCJJuz{PX*?ax1AqO^KiqX$y}tm<^x9t~kN#|6HEuBi z{^S4N^1l`Pee?c*a>H4V}6%G2SX8J zu|5YN%>ZcTaLpJLvRgv>9EgdT0bD1!z)UGikLsrrz0ey5LBD8X)v+D4o%WzPk|98g z8HiQ}-9ZQNL$X2g=P51(qXBxNzd6F-RYp+3x(fD!$vo~qA?_E*K1xrM_mw^?^^jU7 z6zm$i95{sG3~kN;G^pngtM?sH2j`8ixgr1iP%sV$8`W?#7DFK5Kni{i&DB60`!?M`I0Gtk^3@B=*Go&RYl%sP)D|u z-_H9A$bd zzDdtq42e&$ji0`S9PIY$Ha*z=iM^KPb>+m)mX!yAav|x;K453X0;Yj(2ITU029%2m z5PX13-M)ZzV+eQS!|`q~t09m|DEh#I*^)eoS%zP26nIis7a= zxHdrM_gdf!m?Ih)fw`}@koV+j$cRh&Xb>8{P7~<2>jqSFftg~)oFSE)8IekI zV3vobdOS2-6}fJqd-5C_oiYFr&_qaHTX^Pp50(tZI_JGoU+0tPN+f*eOR;OVko3*h zZG6mMl0A%3@Oc#X)GL9B}ZSZ>aTR zJM|2?B(;=8I3!lfbXxGcp6w(#4Lx%a`HeH$cOB|a0Z`W&b#fo@Sl`EmVsLVfgC=?s zneq#zca9*z&xTyDSi$^hvoMhZS$`@!S+G2L15hhDM#CVm3~;@THdlbc`9|!@^EtCq zphb!Zlj&0y-fkT~31M~u4Y^G?o7&b#Xz(WL!y=@SpHk3?0h4m}K*C+APv14R1@1qy z2WZ}#{bK3fir4^sut|Lz@b+xrB*@So|60bgHGt3zv{KjMq0!bJt1p(pkR=6ZQs|B# z1mn4`6}JxnA|bjAGN(5j)rCN7rT_W@Z2Bc(ftN* zl_;6;ur0E5_$O^z_8`^sr@&2z$*6se*dl>h@c+utM24DKKgt`21pQ=_Vu+A*9NYU{ zIq2!r1^z&I72p8hFnn8`3b>s@(8Cy;UFKL9@4B)~v&<^lGr4J-3|z79p8>M`*Mz*&3qNu9X|-I|lH# z!FC_qznI1MTKa}MMpPaEYK#N$5LoDxLMmz`TG7+o~d>4X}gXi}~`W z0$zjWUi&u8o~g;8xezQjE1$FC&~W(&+ofSum^Xc5+*^W5&Ua!Px}N~T zZncRzTim`2#3Y7k2BU7E(2^z{5IIjx( zzx4PZrT+2{4Iul zVMg(S)4uCO)UUxdu+`p|WyYK%;8eWz*Oc^5w+q4?XvkZruJoBhL*f^NOW_6ur!#f_ zjB0k46*`{`b$W=U{c)EGZ2JA4e``U8clV|aDi>4n#*>b*muLo$07x~%HToNHs;C&E)6OsqryoqKZhVZxgwMZgrr zENFnan<>Plry`Lzz0WP*3qJCgSHO3aA4j@i1agQ}tbXY& zhJ!mV$k5H?w5WStuulu$$f$9_X+djWy+oEKjP%RnX%4wqZ8*=H9NL06Q?pwq(+{}a zRsOz>SVHgX1$QkZ_ahCkBZ9MU_Fp~$iUXn4dSL#{6ldt`#>lda(=-v*cwgk}aT>`* zI1`Pn6xEYU4(7i5fm)q)h z(~CPa>w-suijQ`JQjv zs(P(tAs)hm$WWN=Tjt2ehEs|jz?8sPzYLJ*ozRj?i-6xWU?nscU0lOc2I=LcOz&g7 z-+I+br8AHV1lvPMY$2T4mTVrWYqM>HuY$%3xSM?mzd8_epb$U|u)y7C9r;tMMF>^Tj!{qb?Jo%c)TUmeq>dIZiX$9_{q1#!8lxX+m*L2ekxAc79 z$Xt`AYJb1IrghoSfLFL^{l-vm@nW=Pu(~nQ(ahu1=nhz=u%8e6{NqCHk5K;?dv6&Q z<=VD?DHoae zy?+9_uX!7;4atRm-oQ|xx7hqM4zESj756) zadlE!0$3kqQuS=Oykmm-l!0qRfGK%j>fiZlz3YS!bJXiU4wi2rw~a^r8;1FdU^5S^ zErp`B_j;pdaa^Ioh{;|KY#tkPlppUGIGwG41>Q~yMz zZIUu|AUTcFpOxRJK@h9FO7v-H*v4eBejt7-IT0OONX{rWpEK&w`#9-)_bV&gYvGNN z3Gnh0pj)|(wKVGIz)WIL%T=a31~lO)-Z0w3l*hiXU%8*27kyjV*%MnXPf#sFh(qHl z@u3wa{umiDj*%ZE9}WgyC+-`G$qL6;PE|hpsQ2`?p~_fZ>!Hq{eUd-6fz=y8^HIeK zr+>g=JX6|CDmoV%x+%cVtM*h?-rvT-HNRC{DulO5t{Dn<)L4C92ToZd9Q2GG1Sb9H z*h*oT#Vt>3K({?a_m~gDS-ZZ@Fbd4pv{4v;tHrzv_y*Znzrjvgy5=b&rfm3jzIANu zP8Q0t;>`|HIib~B8^avaw$pXwZs3&aA?pFORqfC_GFj+2|GZXn+)3uqAZQ!T=C%rS zlsoE9?8`0J)UV`39*cd)%`+L9vDckg`wNwU?&lp==s2RppC|P?P$*O0bQL{>i*220B~m$%0a^)zZ{&r!6)AEJuA#oU$b|u zS85xgXs%jlI)~&J{&)H9(HANq|8=WN>C4n%w|z<5at`TBG#}0<6V%``SpC~E8#m;? zbCOtN{I`}F4G9aW$2JN0g|A^gAx~H+0wZ6>o!a2-7}jV}6hU?%(25G7DzK}4Ij70D7-5kxU$Z_scQ+_X7X1O{CKQ3RRh_|ymx{2Oxt%_Z^hW=vclI`c$ zYsPpLQ3?E4qQ8C*=JoxYpp)>-dsBSX4!&+}_(*seO;xtkl3l9464~aAq;uM4b^Xj>_Par0JN#>zHT zLS{g;e4Q&fuvBe7`vH2}LA41(`yZb~Q7I1JuFViI`o5L-2Zy`SB>9? zf>X$PoF2#46^f^k!yzsoo@0cI&2|s&-rIUqtNaIu6LX_1)_Sp5LaFGa5a@(p>dc=HP%8&^FyKL(je(8L%{4Uw32*0;k39R4-h$hX+FS@qnDY zQSWO82$`-P{9xmBXWFd4Z%Qe2xR?XPtKCvN?d-&12?Zv$cItJqBz<2h6YidsvOi8y z?fG!OAYMG`FfTtL-21ETU*sm|6%TGV)vWDmeMwycxN8?ZuhhHayv<+@iPubK9k5wG z25FZ>(u-0*vl8t_-;^ei$&EbEc~&z$c$H3|Yn&*d`M7=a~J6;5{JS9l&8VBN*2f$yA&)QA_u-yh2 zVPt^~YCAnhcJhHdal#>@eY6>Z z=e2AL(igRg z$;3kK9Rbkq+ec};t)ZZPACU9LCKo`uS;M~Mr90N<-_gs$kL+tJ$f)N#_-DYwIn_@l z1%cQv15{X9cE2F8qMOzUO9gihz}^dL-D!S*J>b_M`eQ%?itV8w0q5&?km!dd=bOvv z;VICEcLa%0Z-ImP8#k73y`PNrww7d=A-cD|A5<3crJ!H8k^$L)X{q;!tmJcYcG5l5 zbufTjTaG!+U@cI%f+Hw*s=3_2f3qMJfH{bC(A2hHJ$UJJ42bt&MS=Y0Hx*c4POG-Z z-=5#UKz<4xNE!7W(115M02Hl}b-=?cI)1FAdwsFU?bP&I{rp8p(p}aqvNETPwsNvq z8C(U3brL-=PkQ55@UiLA64ba7IzPra{D$)EWi!-L9dQ9oM?$rF&=eY?KLSebEGSHR z{X=2>P6qckdPGus|I15332d!S*|A_gLZKqFPt{=32$IKgHV9vi09y>L$TfX{CBu7y9IouU+&68JxrdV{pLwN#F*z)1%i^Gm*Rm#Ru zro#buIG_ffloAqku+rkRD$M`$-E3s z&Y+g$&s5N1%)#fN(OD8aqFoZf23}3O$5k=yDN`$Cdcwu)`s-vphdOEPUod>!Uobp# z?twXW^@nB!Xj(r}*{=S*ST#ogs&^TR`3y9cxkwU5Fr1$HCk}rhQdO7wZdIG%o z-98NK+qwc~;)_v|-t&+euza?bg_BbHAOS2$m5h#kvXmL%hFx)RDs_W#`x{Gf34w&3 z!hy=HVCW>0tAw-%ch=gd{kNyj=3!>uFPDPky!#uiAzWd_XVK(l5R`foXx#rD*i}}q znXe%cp6Qa}qwLvBDH2M|YoEcFk5*kN^-zp_%?0#-jn)@|i_YMe8A#7@%^mVK48{wf z16X{@pIH3URwi^(FzgR3KFT!xX*t@7P=(Q8#`ObjEmaRiQ@_(Ca|>D2Zj`Pq0}~Y) zOIoF}>VTSpA8r_h_cU4Nw|NVmt${`BbS{fONPIwRS7E44ca|*+lR~2DwSdvZz{|kf z>JvK0iKIg4>>O0^rqhp+=2G9cwri5j^`@JZYX7PVd4B%E#5V@_u@UmprT@vqUl5?) zhM4#u^_l>5rCh|cv4nTDZ!2-1pZ#TCKR*xa4YS^qbY33`0*Hw(WkbVsr0(8#0zspz zCK8sMO&5z8q=<^>hOe7rE=Jpd4*IKgYHaZwMG*}>Cqf2_k=fIjacO4maP2lC=3e?n zMgJ7xML^|!3&+b>M`hNPrroIhb>vg^cKVpbhJznM^_tN^HMgZ?bC{_^QQANwPXPh( zLA?+VKLK{7dIQOa8h30ZbfPe)EiR3B%h{_Og0xF(^6CI=`E@5+p94yRezKAi(MHxz$D6UVGv*Y|z_Z=;euFgj zc+cu#RLvhRo%yk4(=aac{_A+1Y7>DkvZv^znM2*BIEdlaUf}HBkKeU8E0}c z!{<$H&Y%Ul9`z{r!mQ=I^a8D#4EZG;aUp*J#x##9KAc!1w!|PHEIekDUZHUbjyOcy zdfRxLhAcZ=U1;Hxsyj?>wn&^}?a@mjv}LL;pSm_YClsEUmaA~h-3?*Y_I^>|h9D@DQ zSqYlK^JJwa%$~V8vA#EhH2YghrIDmzO*f=bA#Gz|zSw_z%zq6UbS(tV5GCKXiW4!nTh;7W`PK+!;GQ3di)1-ffB7d@QOz+YhpkU;4LqhE7qxkJJ+88K zQI)W-c^9YzTSX+Nv>ZQ-wyxu;`P?N)C104yDpC3vS6wBU2GDTFLh(*Lmt`vWq! z>Ix>M#i(<87Qej!ZZrX>m(-v0^ZQ=~mrJv1`YV0PFY|U=e)GTBRC^fIXF2@BGY%16 z-+GTS@5EP8@}UtuEmls<=hGHKT8aNYjRCtX@4S=}KQ0m+mAmh&azh$;t0zQZ*EZ4q zJDoR?KZNPm638C6@P>)loJ#_y-5lyqP1Y9}tAIK1f{~yD(vv3&eLL7WVkrXp4tD?> zNym`ZJ-MjyPI%9F#ncQ!RM8ap0W)(pvSOgl7m&#F&AKfk-r8F67?<-fB{hUM?=H!m8nKLX zfh$;-1rb^m<|to&5)_=#rs;VX>tx4CIYPoU&_K0B0?m2*P5n<%|HZwfo zuJE)RDrC(0q)vj`Okx|s$VA;nj)SK~4F3Y=gW)&bPU>b72!_deY-zQ0E)NCsGU^x) zUhwqOXYA8|v}Um&lIV$ev7!37M}IS(QxsRPuVWkCjUzJtfcK$@2^(9>ezOt?&F>LLPc zIcocLZ!ul-1^6uawlDYR;>mD@9GYRd7!IVtW~K}?PvNt2Vr)>%JG>{flu{lDUzoqk z(;ia-gvjLw(l-V|Gm4)rT+H#Ex8fRuQEw2L-c0sx+8>$@HWjK)zi@tq{RTgE!Jovu zKcNbZ8gZWkd8@35OM6dtzdv6X%RZf_chwh|%jTtGu88rej~tG68rJ8M09ZyufZa0D@DS>K+$s5Lcn6s>sg2kT-?8}W6|q8%Te zJ0GTDWRtIE*x?@os9V@~#)_40^I^fey-Pk2K2HYld55Rn+r7)%oFdz~4w=$E3smGB zHcD^T`vTgwSwtA}7Fzb(i1rn1e+u>RYiyxCOb^G?YUb?=Dd|LQ-rMCeIxfY$+Nx{g zc9^Hve<|h4KW;J4Pq~h;wo)y!Ihy(O!%c166jAO_7=i2({dDp}@%MKG2<+jKbh7$s zT;dw2yx6&m9_|o6Pk#k1M+D*XQfsfL2k%%x_&kCg;Pdu>;Pbj^0H61u{=b9IOTPOD zK2P&^d>+2?cYK~oMnyEp34fi*GhEKirsTT6p2uq`Gv8h_0dtM5Mgpwt6N>v%1UxTs z8;711YR6C65||i1AuZxMZd$=FufoD;y%_yb+#-yVbCKQLh&)s#d$WW;LS&&$lmYk7 z*LM7upLRmaj}R+Mh{OPCM>zZ#9cjRfCC0jNDqx!XC{+me*n@XHTosI~j+@9oR%Vw<8i8#}8HYbG|>}zsksk^dp z#ikZZ-2Xu9QGZ|_0Ii>wySH8m(0c5BqCSe$0RdX%`xv=gKfF66zeeie(sTtsF_i$*Zoz_jK$Ix19R4(S4&qZb6@@b)=K%&Er*HJ9dR98vF#L&N1n zzX;60*AY!{Qr~~{i#>ozD$1Wf%(XJx zbr8(8>dleM!e?ONMmEW{+@b%5%2Y2RcknR(mTkJmv6h&W6#30si?WT1y`& zyiqE7D)za)t~G&p5WnYA#}hm8&^IrVjJ`P!P}u+gE6a;a{H`B!tE&h_+Cb({y@1o= z8&6`5IO@e9>i#&=iJ(rhC$LIaA&zSG2ev&Z6A~3asSNtEz^l{_HJ|^%$s6~2|?)7IuPJaAeS!o{gAUW zq&!&D{TD#H=7#05vc5tjs>`^B2Fww7tB@>ScJdF@o<~PSXMT`_G*jek@#;k%adXvRSyQC-i^YW== zWyP?5w?)9zP)D-gh3L=F_+R{~w5oNoWqr8Y5FyeKF88+SSo7VsVP%x@O#A#kd_9qC z|BHxc+!mwR5OBeNll0|oFe$kR2Z1d{O0V_IL*r3Bx-uv@753d=v_QP4!?ZS>g|~0!iUEGZ24&?7bjJCx0To^b_eXameY%-OL78my#!$bj7^4MjSRR z%H+wx=IPFU+s5i?Kug-gMfP-AJp!=Z1EpYxwRd*kHh%lDhKUU z{I}H*V4u8wsLW>l@uesZVV_K;;_P?1ZK|b~k{V_H6-!snPHX+HSDH4SpkC7(yjhgT zew#W=z?< zpP6LMphHf>8VCmjM)lJxLAJUP+1)gLCPal`-*r%(t|{nG*Jj)oF9#f0ON%=yeYTuX zQA7qtNwAoEr}+~i`sC$P6$c06C8j6jPrZwVGZ{+tWCgwk6Q@Zj$wLi~aSA}pF_ zPBbufQNY=1zD}*6Rk#>EyA=9T$(ms1JALO}@u*_DB9TPG@$&hV7Qu<~gfx$U2hqSU&uB(-TDwpS8B={e`KwRjAx3>vwOa z@jS=T>aZ6fi+cqqD)PxQb?5`1t+`(yZ98@#`M&fj0^#7#qM_{-2DLNGEkXETy zFWd@eN4_vmDp0kx>dlvSoLloZ-U?>TdQ-!Q6Wse27qwJbsGFxVwP~`QdchCRUd47I zdWVgBKC}3qs(vYI1c*E-RSbkDOCw8q58aItML25r#dJ)sSL{XXsZNOJE@<-4&j`1Q zHSnk1;-je~bx5t#5~~jrh!gX5stj`~a@0xc)6i+99;+-rEZCsUYR)A85FXTh5BF}; za@DVULE5dz-yjUU3epPh<9CQiPB1K?y7tKLg4VnB-Me(xZ6f~C!u~lj5($!!S9&mH ztopTQn&NssMM%g?$BmUH+J8=zc5y{s65F~Ajh07Rrl7;tLU<>G z;{moe>oWYdRGsM6y2JmNSfwg9gs!J*+5h;bku2o;6b6i~_WOqT^Va6JN;x_L z6R=6!wu_Y7p}NkSUv|C~x!!@`cb&1Z+AYW@3xpFP9s=kK;xbJ3=WCPx@x|fshB|1j zp!?qsc`!rvS%3KLf9MLdz~tro&*RgE0%2Zo@a4hP zufvd!ULn90bn}eBSWyA;Kpu!e3Nl=8-2FRcFK(|K3THY7g9-&GCa3<=GMI-64P&K< zZl|ZLeTtf3|C@Tk0}FL*ccF>d7l8_L*^CKbP_YEuxgJ3NVIts%mrQf*?=KuduDG>f zAUEv(sA0VM5r2-p^(t^}zN=<*Y5)WadQzd#KoD{yTP=E3Q}~a!f89e3+PrmsY=IOa z72wn2I=KUQ92VSBZ~>wr%6_N7!i6Id%yo>xJd{gisob{z#wqY&&Y9|m7gFl}NyDpY zArJ!vsH{qO7%>!83-R*L>*UNc$Jze&mcSoV?&Hw0W)mKgNkXw!gcx0HtP-mK&WMLH?12B8y80>Ys(Q1Vxw# znv+4s7xja}NRcYw?K-euw_p1+mWBMgnh?Qqk;|$nNydugKbFdd4c6WhfljX$Trz>r zZA^i4H`CxDcV5Qvej?}twrOICV1jQ9K8E|iZe9PH$Kf>h8IQEU0=5>)a@!V$yvg?- zL5{>rfOPmOKhr4d&r1p)17D>Ye8)tlWE`ZPI*!Xr>!v>h<*b3nlHAgLx|Bb0GWM zMGv*Wkh*#c#^LW|_+;VglaGw!m6{*Cu)uXNPs|+57rz!snr4`NqWr0k_5l+gjHup%f$dQu)` zn+R{=5Cw*i^GJxMp{)x_F+}heX+j*u0H_kfSp|_z{7nwR;@!q&YgArA0T1!a`H0nf<+-4M@um;-OU)@l+$-y@ z`_(^3jINm@_8?cjgwetGy(fH#dIVJu_t1lpjuo_t*j<6g@;3Q;fIHDG|7hh)_qJeh zoa*V(+HZK_U+k5ykVi1ZAmrV5+Y&J#PiH%*fD^X)6)l7rctCJRcLc7YQ%f)#L1;Y* zru^2g_aM_+u?Fu=X#WzfsgB*J_S0bgnV_QLE0K3;87#y#c1?UWKL|9svB1nDj-9o2 zHnufxmvdGPQym-@qW(o-ff)fA^r=^C#ugcN4z0edHG7>AToJ+}BD{yDCp-GF)*9N+89;}W4NqYcAD^7Y-Qm6W|VUW(?25d>y>4dQCo;O{0 zO<8_ONnEDx_Is<_l&}D1lR)dC#mEVl#vQJue>c+BE~G#3w3%Zv{EIlo9a#{;FLE`` z!AIlHJwW_eujOAyR9|HtPtfdiDpl_o8r#i1exCDTntoTc*6YSq;GKNo~WX($eX zsnVIuR^@49`b&Rf+}ouN(X-hObl0%swt@=@)<24e(e+@y5j_C*oy1l`;P(!yq{X2bG6=2}E0}>MW>>mxetz!yr%!Xk z;(figuUk3=^enfUV>kimn?X<+U|pnj>nIya1VrSUuM|WS#I}H4Nan5`1X8FT^NArQA>CDF`#jnvC?C%|HuO|J-| z<@!X&#g-Djz`%xUUIuJ7*L*kv>aMS2d2SnLRuiyUR5;aC5LPE-BqRPd>%w;7E2(KJ zvJ((1FoBYYfb5abY)C}8N}n3daQ#rrMB*6-rY|*-@r|xgi|m` zz5v^8FvXApmw&{pkntAR7Q^YH{GrZd*IXm=e;hItQ)Z>=Xk;Jy3o#C$LL6QQ(CgGx z*LcM~qknpF;}S=HXb9G2sK$tzR|v{{tJ@a2jrpqHO2n@}0={YqNg%GG<&YLit$bA+ zXhe(c77dJ52!a_mwG+~@rqf8P`ejveQqDG#*GwG^m2c%$Da6~eAZwbBz$|cCq-Mm* zipkiDq^1*1xuDbWE4j8bxFn-u&kfr+vC7mq!M#@4NO(=k-5Z|}@qYKrX+8O)tLa7E z>!EsN!-3WK_V&?%)fBW_@hU!IIXO$>I^VK-z%A~&|D1m!D`QTp+HO_H>Dew2O8z{+ z@QEf%|Jr2u#rw)j$c^{FZ$F7{n9#_G52rcdzB@~f@`Tk^(SV#%?nfd71(_Mj9+;FI z*q;3S-aSWT%xBDe8AnQjfZ#mibzEo0m)W)X=2xHUcSZ8>!=(NSA;D-^!4cMouGm+b*ZG*Zz2Aw$2lA0PkCU`~97sl4Kp z>sVjm!e{dJ(;3S84Y@n?&Mens{9{aVbDVL{E8?cyWpGpb_4`f!e#bE>u$b4^=THYY z)8bz4>raspTLnu8)tv@U8L!0ODF!yVA$g-DG{`svPa04DJA!5+FM8%asV{*^fkosG z(B#4U)w#5`%lBO)f-Izvi+-kEvn4j$_7hd}5tYg`B0rT_gP45!w&p?)USso=@2kbge!kB^AJgI-<&&o)FT^d0Medvbs~ z@$#e@W#of@ezJeOyd&*-szI?yiCank<%M@xaQL#FN_C#x`r8fh`?p%O#C@f@kGhb9 z`k%K9_!qtL1Z^p6U3O|Y|ML=q-#Dd}JXCUGmLz?K@ce(gu!IHEb+0O+G1-j&ychr9 z|3>NLr@^7r7Qo{=t=npNf4eg90s^H`#jD34U1$Q}rpuGg+Yk3ag<7d;l=uzk0kz)% zLd~8+g>&o|WJ|2K17ZbJqvOmKG=USf!9JM!9@@o&b;xhLlmURXHM5o6bq7sdz})u@ z;Ja*rP&;wr&5FV#h@*YC@Bw%zW4eId)En^qwt>8}jjJHz#c}2k#0(J6y9j|qBb2Ka zsF@U#i^B2cI*}n_KfwM%XVLH#=-cYFp%h-A_;Y|7J$~Xg(^QR5>!2vPJqogf#O=eJ{0Ja@#z`) z0uDICkVyCqNF=pEV$j8%<_wKc_@xyfP7C6}G3~*CmtXb(a02{sO&5jjUl4Ja1w64e zRKVAoi7lphA+9BUQ-ByyI^!amDBk)l3km!LAvYmky&6+KpSO3Z?U~P5p2d&#~?StZHJyPTf zRPWY=Yu?NV@@#*>eR;kL%n8Q9JDU$I|E8{03ADQU8fwrAWTD|J^rm`Ma3AbrHRjED zUI(B>VFM+3>1}}!8u#ON71tS{xfQa|CYwCi#H|wrEls+2pZWkJyb?s)apiF$SWqD7 z+wVL@KMT4sv(ra2UUX%?XsoVoaH_294D9ybyJ;v|o6utYz`;FP=wCoD*hOjC-c3cOa%?@S<#FEd~2BC~MiILtf09U*wRDbx;JVC967r0pu=z=a& zjZj0(6$fyLm;jhK7DR`^gdrJm3ihu*yf!zm-86O}~)|vqNU2IS*xNXcgBDJ;gUvS=a0Qp}vyj`~uC$bo) z6Y6lb0%DV8PhOHPo@V0B74uV!V!$x{`b`QLTtjvT^ns2$H>At`&&t7Xc^nW~(N(RM z`9%0~bkJf5xMtmbYn3wf-iyD`V!YU;V8XgA2J$<={lT>I;x&y1`6m;=Kw)w$h!Y~A zXNUjxNx(C}4lj8M{@wvx4jkE*raNj+c!Ggzg+@`zrr(3}*9R1z#o_w!szF!tN6`U847K6)ke4<@g;04bImuaiNF zmVm2^j2mU7g$&UK(y+|*9WDE(|U^>xk ziA~O=r-&f7t-h8lQ|AvY(55PsteE<+pdUyx(f zzVQ1qdZoVe@Ob&`^O{gb0;3!#T9fu0zT;w`rrHx?w=OAJW9BOgh(cNKM&HK3Vv`5i zV)%IQYAB4w+>3hsmbrwMFYdiY%6#h<4l3%wIs7m;1$DW#$6Q41h2i)o=b;W>Gb(=X zB7aTO!^;R2l**odl+RgUO)=ewM zLR*a#Krc#p6Ldz_n5RFM)lsUN^zm8(0Wo?9 zRL7n2TIZ;yTzhBJW>BOicKgc zMe;L~P`1#j91T3#d#QCH$LfY_$W)v0N14xlHCj7n&RxVNQZ0`OsBp--K#Qw+4n9j1 z#tZc%=#c)z%U5J}(SIJ6JkI1(Sc!qfHS8Uie)%TeBY15w>qS|Ap0`)zrPeq0QqFDp z;lOVVu(_}~Afyj&PclYG5kJ1dFvJhVGm)_zh^uA zySxnhopvyAsKm`)J%XbK>5)1PKoC1WWwemysG z8o?ZX?&NA1Rm>*db2uZMQWlT;#8>jwJj=v5Vq-KMjN#-&@F-Rvh#ivH!9^ZY_>!Fj z@qTG}s9}NTK6rN&%lq)Gj7Znd{wx4tm*JVJ2zt7z8^f+TI0;kIx# z-x4tr9>Xx^oo3K8u>US%83ot-*|&hoq>!1)BEgWMbYfKlSgS(SR2Rv+!i-o{LhoIx zBBf_3;wj;ch$U_`CHlqqi;b)mwAlFXL)&y#P-H(8Ae`%)F!} zO`VfYw10u|C{f|B4Jw8Qwn*p|7we3mVH5Z%Qr&V{@nFPdh&e6cY9HwRty~q1c!Xev z0KY&C2zyyxEUgJooHcw=V%RTsP4&)gvLD>Q9vQEa7i0Y>-+ zq{#S(z8myvpMh(do<=cm6{N0u!Ii}Ng#02U(h+D|Hr?Q;6P6RIFV$@+Kj``!+aywN z_JIB*21HRlt(*Kop8Hst_&LvaV1k5StJ8ezvUbDcaH(n1NcJ>s`O10W(L#1lR-PuF zyG!)q3}2Xm=-nlx(ztC$O6J!Hixc*^XQ=Khd5h4TM94M zn4dX$!F+-pk8@t4&+q$FT3HecmDlc?j;N{oehX?Z)e_XE8wsRav)>5Y1I^qG zvvACM)8#mb_nn&juM&Gs?J30Yo|!W=B{;n)m`)U6*D$R=)4-zCbo?-hpWWZNQTm}M zjb<`2D{eztFalZPE=U?=yv18b6lC~{I9{Y=+cuA+4Mj;NgvaF!4ljLpZ5Nw{!wP-` z>(J03i`B1Lykqx>((UUbwm`E#PWdU!e}(M>6^r~j z>6?1qRnE5Ke)d}qpRxXKF+PuTkv^~D`b~P~Ga2rMkRy`>)8J-XO-ZHBqwFeA>D5`c zyw`aYUTYD2=n-f1ZB!&cJ;v?%Sa$xy6?!kBX<1?#G^4nOEG`BFdPGFt#G|e8NLAdq zfVT{$4GROj$Z|q$_6Vx3D6V0i=_SgII|dC3oJ=f3Jc~U3*`+?;;yujq?E9)8>RiBQ zspT2uDVvr=tA;BOL6*Jf$G z0QC7`)i-KW8$wGX=_KJIwGZI6N=i@!kk@=h{ssXJ1z&K3Me5ftqCH8pWgOvkr~;}$ z^l<1{TjbjUhoYeu^Z%~6N99wDh)wb?(b#U{$Ck%E)ZM6=sl?C(;$M_9Ze(LmOA8gf zlE8GcRgheoF_#H_Lkog-sbZtqY8W`O?RpRcd-2G=JJlBRpQdP66ATQ9w{xwP z4_`Mkft2CYP$_k?Umt$lTUNd+7KVdj4tVL`zLleu6oF{s83HB_PQ5XoOJF~Gyou#h z!etI_8TW^1P@3yXZ|U_DAmfdA%4T+Z3Ode5UAv}Cz%6$t?1uL_Btrk=r$COwfugPy zLwSSkWviYV*J<@KT2G_v#7uR8%KkzI23vvcxQKy4{TpZm%L1FI8+;Ic5OLwFG?{~r zgZj|3SWf*}&gD0UUWDc{H_&IngB=>;-n6skV@CbZSzwLb8uw@@yWA@;qYLv)Zo}u{=kIrd zIWHI+sLqZUHW5{Npr;)WE%`$9?9%ulB9JLImVa9rsqOdVS(1;j_rhR^2ogad+c0;D z0v6%*c@zZH;??e9of6_tcw*sGLc$cC$p@8v8)HXxvUEPSOqV=Q`9m)%b$HOY#J;}O zHc2woSNYfV+9!Z7T1SN9bgLvo~#eHQ&c}3gdAFue5yr0 zfMMjweOAgVLsEUquYO=={gqa>qAkrHe04NU#HKnye?qAd0(V&x2B)d<=*(ZdI`Lh4 zl2}T9IqC7}#dWh7l@l(5rryTym+qDc4;Vmh9^*@d?9UBJCNsmBWqT)Tc$& z-C!W3HrbZR?I-$v%q6%?l>Q}RJYC*~=rpp2I_L^xGTS{GhL^p3Y7&(%X;(T*a~*kd zFzNViX1W;PAowuq$-_*Hv4&=YAj@c6HrdhZ*nITV()?6g!mrb*U%M{r(_Uosf&s5O zjil(2kFF)9(Z&9}7|3(k=dT_YyHMK6iw*H z3$c=(e15FN^Swi|qub7%hoHN%A1^lYX+r^J0$&S=?j{aE(dAJg4fCIi7=~_l8FZM- zjA%Z7aJDJBr$o^T6{$sxpUPBEl){Lbg{yUxIT+*-sqo5t?pM|v%n{FzPc#-STNazA z1u}+`ENpAQbfgyRSuCMo_~~GJ^bKvKtgSDJih(}Y<&bz;lvkz zHGbmrP{hi^&`7ET|G2_u#c@#)u0;&#fefu~!_fpC8wotN3c+31&#Et7gS; zkA)doR>l%ToX0qjkuOO{u8M~f3K}Dwgud5qC>%3uEje)#5fHlGe2?=Xes2kn+%<@p zo9{p?5}#hKyO&fCw~ur~1cC47=RICr{AG@9zcKVodgnrQv|o!ip_7ZdEv*ksJj?ob zL0syJ46}4eJLh;cuKBQ4u4!jS+O%H;CgO3XZ&rG3rY~ zN_n?ml1gOc|G0XlKx_PBXuSVgDAjlUN!xkN7~jO^@-ix#iQKbK;tS&cGAf5f_>^`>2pZKeVB*=47^hG?7(c`hicy5)Hr40*zZHXIJpX&BkE4U$B2C5l{ z;SRW;Xc+)}TlLuq{TEpmc4hmA57|of zzu2-c@Zinr#5)LnN{9-fyInv2q1K{x!{y_T#|S0Q>5)0IwA+W;4;3;K(hja=Tov!E zEO7XdAjm9mC$o34)X5oQa}UlVNz>?zk5$8dI3^<4jzPdYu3m# zJKDr9%{NA=rFRmTDBz2I)8>9aG}2Cab)$jL7^mCphyO{lhRh^<;==UI`%=sLO8LKco+>aZYM|LEIw+^lp-fW0a<_&Nu7|}a8_2e} zH|4oEndpyVOHmb2+x_U++3zY-OY|L&R4aHLMN+u9u}0e~REYI)O{6*3m8tbX3q&=l z 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

~B`9~j;H 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 C_YLs? diff --git a/.agent-review/visuals/usage-health-review.md b/.agent-review/visuals/usage-health-review.md deleted file mode 100644 index 9703065e8..000000000 --- a/.agent-review/visuals/usage-health-review.md +++ /dev/null @@ -1,8 +0,0 @@ -# Usage & health visual evidence - -These images render the production `UsageHealthPanel` with SwiftUI ImageRenderer at a 490-point width and 2x scale. Both use synthetic metadata; no customer activity, capture content, UUIDs, or device names are present. - -- `usage-health-populated.png`: weekly metrics, all three quality outcomes, three synthetic failures, and privacy copy. -- `usage-health-empty.png`: first-use/cleared summary. - -Reviewed for legibility, complete failure rows, wrapping, native system type, and clipped content. This is component-render evidence, not proof of a full macOS Settings click-through or live vendor ingestion. diff --git a/Sources/UI/Settings/TranscriptedSettingsView.swift b/Sources/UI/Settings/TranscriptedSettingsView.swift index 62fed49ee..7c888ebb6 100644 --- a/Sources/UI/Settings/TranscriptedSettingsView.swift +++ b/Sources/UI/Settings/TranscriptedSettingsView.swift @@ -2348,10 +2348,6 @@ struct TranscriptedSettingsView: View { showsDivider: false ) .disabled(!AnalyticsReporter.isAvailable) - if anonymousAnalyticsEnabled { - Divider() - UsageHealthSettingsSection() - } } } diff --git a/Sources/UI/Settings/UsageHealthSettingsSection.swift b/Sources/UI/Settings/UsageHealthSettingsSection.swift deleted file mode 100644 index 677a956f9..000000000 --- a/Sources/UI/Settings/UsageHealthSettingsSection.swift +++ /dev/null @@ -1,90 +0,0 @@ -import SwiftUI - -struct UsageHealthSettingsSection: View { - @State private var snapshot = UsageHealthStore.shared.snapshot() - - var body: some View { - UsageHealthPanel(snapshot: snapshot) - .onAppear { snapshot = UsageHealthStore.shared.snapshot() } - .onReceive(NotificationCenter.default.publisher(for: UsageHealthStore.didChange).receive(on: RunLoop.main)) { _ in - snapshot = UsageHealthStore.shared.snapshot() - } - .onReceive(Timer.publish(every: 60, on: .main, in: .common).autoconnect()) { now in - snapshot = UsageHealthStore.shared.snapshot(now: now) - } - } -} - -/// Presentation accepts metadata only, so previews and support never need real captures. -struct UsageHealthPanel: View { - let snapshot: UsageHealthSnapshot - - var body: some View { - VStack(alignment: .leading, spacing: 12) { - Text("Usage & health") - .font(.headline) - Text("This week on this Mac") - .font(.caption) - .foregroundStyle(.secondary) - HStack(spacing: 28) { - metric("Meetings saved", value: String(snapshot.meetings)) - metric("Dictations", value: String(snapshot.dictations)) - metric("Meeting time", value: minutesLabel(snapshot.meetingMinutesBucket)) - } - HStack(spacing: 14) { - Label("\(snapshot.qualityCounts["good", default: 0]) good", systemImage: "checkmark.circle") - Label("\(snapshot.qualityCounts["degraded", default: 0]) degraded", systemImage: "exclamationmark.circle") - Label("\(snapshot.qualityCounts["failed", default: 0]) failed", systemImage: "xmark.circle") - } - .font(.caption) - .foregroundStyle(.secondary) - if snapshot.qualityCounts["unknown", default: 0] > 0 { - Text("\(snapshot.qualityCounts["unknown", default: 0]) captures have unknown quality.") - .font(.caption).foregroundStyle(.secondary) - } - Divider() - Text("Recent failures").font(.subheadline.weight(.medium)) - if snapshot.failures.isEmpty { - Text("No failures recorded while usage stats were on.") - .font(.caption).foregroundStyle(.secondary) - } else { - ForEach(snapshot.failures) { failure in - VStack(alignment: .leading, spacing: 3) { - Text(failure.kind.replacingOccurrences(of: "_", with: " ").capitalized) - .font(.subheadline) - HStack(spacing: 8) { - Text(failure.time, style: .date) - Text(failure.time, style: .time) - Text("Version \(failure.version)") - } - .font(.caption).foregroundStyle(.secondary) - } - } - } - Text("Usage stats share feature use, duration and count ranges, permission state, and error codes with an anonymous install ID. Your recordings, words, titles, names, and email stay private. Turning this off clears this local summary and unsent stats.") - .font(.caption) - .foregroundStyle(.secondary) - .fixedSize(horizontal: false, vertical: true) - } - .padding(16) - .accessibilityIdentifier("transcripted.settings.usage-health") - } - - private func metric(_ title: String, value: String) -> some View { - VStack(alignment: .leading, spacing: 3) { - Text(value).font(.title3.weight(.semibold)).monospacedDigit() - Text(title).font(.caption).foregroundStyle(.secondary) - } - } - - private func minutesLabel(_ bucket: String) -> String { - switch bucket { - case "0": return "< 1 min" - case "1_14m": return "1–14 min" - case "15_59m": return "15–59 min" - case "1_2h": return "1–2 hr" - case "3_9h": return "3–9 hr" - default: return "10+ hr" - } - } -} diff --git a/docs/privacy-first-observability.md b/docs/privacy-first-observability.md index 8ec9c126e..f53489208 100644 --- a/docs/privacy-first-observability.md +++ b/docs/privacy-first-observability.md @@ -337,13 +337,12 @@ never a Sentry error, even if an old producer accidentally requests error level. Hard start, transcript, audio-loss, stop-timeout, and engine-loop failures keep their existing Sentry path. No audio-quality or routing policy changes here. -## Usage & health and daily digest +## Support diagnostics and daily digest -With Usage stats enabled, Settings shows this calendar week's saved meetings -(including successful imports), completed dictations, rounded meeting-time range, -and good/degraded/failed capture mix. The last three failures show code, time, and -version. `fair` capture grades join degraded; discarded captures do not count as -successful quality outcomes. Unknown quality stays visibly unknown. +The existing Usage stats toggle controls analytics collection. A local metadata +ledger supplies the daily digest and recent failure details for support diagnostics. +`fair` capture grades join degraded; discarded captures do not count as successful +quality outcomes. Missing quality measurements remain explicitly unknown. The local ledger retains at most 14 local days and three failure summaries in preferences. It consumes reviewed lifecycle metadata, not capture files or logs. @@ -356,7 +355,7 @@ last failure taxonomy; they no longer append raw event or reliability-log text. `usage_digest` is emitted for closed local days on launch or the minute timer, and for an unsent current day on normal quit. A quit snapshot has `digest_is_partial=true`: later activity after a same-day relaunch is still visible -in lifecycle events and local weekly totals but does not generate a second digest. +in lifecycle events and the local ledger but does not generate a second digest. `digest_day` identifies the activity's local date; timestamps identify delivery. Calendar arithmetic handles local midnight and DST rather than adding 24 hours.

~B`9~j;H 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 C_YLs? literal 0 HcmV?d00001 diff --git a/.agent-review/visuals/usage-health-review.md b/.agent-review/visuals/usage-health-review.md new file mode 100644 index 000000000..9703065e8 --- /dev/null +++ b/.agent-review/visuals/usage-health-review.md @@ -0,0 +1,8 @@ +# Usage & health visual evidence + +These images render the production `UsageHealthPanel` with SwiftUI ImageRenderer at a 490-point width and 2x scale. Both use synthetic metadata; no customer activity, capture content, UUIDs, or device names are present. + +- `usage-health-populated.png`: weekly metrics, all three quality outcomes, three synthetic failures, and privacy copy. +- `usage-health-empty.png`: first-use/cleared summary. + +Reviewed for legibility, complete failure rows, wrapping, native system type, and clipped content. This is component-render evidence, not proof of a full macOS Settings click-through or live vendor ingestion. diff --git a/Resources/analytics-events.psv b/Resources/analytics-events.psv index 5410aea49..c1413d4c3 100644 --- a/Resources/analytics-events.psv +++ b/Resources/analytics-events.psv @@ -97,6 +97,7 @@ update_installed|previous_version,version update_ready_to_install|automatic_downloads_enabled,state,version update_relaunching|version update_setting_changed|enabled,setting_id +usage_digest|app_version,dictation_median_duration_bucket,dictations_completed,digest_day,digest_is_partial,install_uuid,meeting_minutes_bucket,meetings_completed,meetings_started,os_major workflow_abandoned|elapsed_bucket,prior_ready_state,reason_kind,stage,surface,workflow_kind workflow_recovery_attempted|artifact_retained,failure_kind,recovery_attempt_bucket,retry_source,surface,workflow_kind workflow_recovery_failed|artifact_retained,elapsed_bucket,failure_kind,recovery_attempt_bucket,result,retry_source,surface,workflow_kind diff --git a/Resources/analytics-reviewed-properties.psv b/Resources/analytics-reviewed-properties.psv index 2acba4bae..f84b66dc6 100644 --- a/Resources/analytics-reviewed-properties.psv +++ b/Resources/analytics-reviewed-properties.psv @@ -79,6 +79,9 @@ default_system_output_volume_dropped default_system_output_volume_during delivery dictation_ready +dictations_completed +digest_day +digest_is_partial elapsed_bucket enabled entrypoint @@ -99,10 +102,13 @@ input_channels input_device_class input_rate_hz input_volume_scalar_available +install_uuid last_event location_type meeting_dry_run_completed meeting_recording_ready +meetings_completed +meetings_started mic_boost_prompt mic_permission_granted mic_processed_peak diff --git a/Sources/Meeting/MeetingCaptureHealthTelemetry.swift b/Sources/Meeting/MeetingCaptureHealthTelemetry.swift index e0d2e9d34..7b1768fa5 100644 --- a/Sources/Meeting/MeetingCaptureHealthTelemetry.swift +++ b/Sources/Meeting/MeetingCaptureHealthTelemetry.swift @@ -19,7 +19,7 @@ enum MeetingCaptureHealthTelemetry { let durationSeconds: Double let systemStreamPresent: Bool let stopTimedOut: Bool - var captureOutcome: String = "complete" + var captureOutcome: String = "unknown" } struct DegradedReportInput { diff --git a/Sources/Meeting/MeetingSessionController.swift b/Sources/Meeting/MeetingSessionController.swift index a97131040..fbf4da725 100644 --- a/Sources/Meeting/MeetingSessionController.swift +++ b/Sources/Meeting/MeetingSessionController.swift @@ -100,6 +100,7 @@ final class MeetingSessionController: ObservableObject { } private struct RecordingStopSnapshot { + let telemetryIdentity: UUID? let trigger: StartTrigger let systemAudioStatus: SystemAudioStatus let durationSeconds: TimeInterval @@ -841,6 +842,7 @@ final class MeetingSessionController: ObservableObject { : TranscriptedConstants.meetingStartTimeout let started = await capture.startRecording(timeout: startTimeout) guard started else { + let failedStartIdentity = activeRecordingIdentity await capture.flushSharedDictationMicHandler() clearSharedDictationMicRelay() activeRecordingTrigger = .unknown @@ -856,7 +858,7 @@ final class MeetingSessionController: ObservableObject { let pipelineSnapshot = capture.pipelineDiagnosticsSnapshot( overrideSystemAudioStatus: capture.startFailureStage == .systemAudio ? .failed : nil ) - let failureProperties = TelemetryContext.enrich(event: "meeting_recording_start_failed", properties: meetingCaptureAnalyticsProperties(snapshot: pipelineSnapshot).merging( + let failureProperties = TelemetryContext.enrich(event: "meeting_recording_start_failed", properties: meetingCaptureAnalyticsProperties(snapshot: pipelineSnapshot, telemetryIdentity: failedStartIdentity).merging( [ "failure_kind": meetingStartFailureKind( from: failureMessage, @@ -1149,7 +1151,7 @@ final class MeetingSessionController: ObservableObject { let afterStopVolumeContext = capture.routeVolumeDiagnosticsContext(currentPhase: "after") var stopCaptureDiagnostics = MeetingCaptureVolumeDiagnostics.annotatedStopContext( liveAttenuationCueObserved: capture.micAttenuationCueObserved, - baseContext: meetingCaptureAnalyticsProperties(snapshot: recordingSnapshot.pipelineSnapshot), + baseContext: meetingCaptureAnalyticsProperties(snapshot: recordingSnapshot.pipelineSnapshot, telemetryIdentity: recordingSnapshot.telemetryIdentity), afterStopContext: afterStopVolumeContext ) // Read the prompt outcome before any state mutations below; it is only @@ -1643,9 +1645,8 @@ final class MeetingSessionController: ObservableObject { _ = audioInactivityDetector.stopRecording() audioInactivityWarning = nil isMicBoostPromptVisible = false - clearActiveRecordingIdentity() - let recordingSnapshot = makeRecordingStopSnapshot() + clearActiveRecordingIdentity() DiagnosticsTrail.record( engine: "meeting", @@ -1668,7 +1669,7 @@ final class MeetingSessionController: ObservableObject { let afterStopVolumeContext = capture.routeVolumeDiagnosticsContext(currentPhase: "after") var cancelCaptureDiagnostics = MeetingCaptureVolumeDiagnostics.annotatedStopContext( liveAttenuationCueObserved: capture.micAttenuationCueObserved, - baseContext: meetingCaptureAnalyticsProperties(snapshot: recordingSnapshot.pipelineSnapshot), + baseContext: meetingCaptureAnalyticsProperties(snapshot: recordingSnapshot.pipelineSnapshot, telemetryIdentity: recordingSnapshot.telemetryIdentity), afterStopContext: afterStopVolumeContext ) // Mirror stopRecording(): cancelled meetings carry the prompt outcome @@ -2199,13 +2200,20 @@ final class MeetingSessionController: ObservableObject { splitLocalSpeakers: LocalSpeakerPreferences.isEnabled() ) + let failureOutcome = CaptureOutcome(micURL: files.micURL, systemURL: files.systemURL, didTimeOut: stopResult.didTimeOut) + let failureContext = TelemetryContext.enrich(event: "meeting_capture_stopped_under_controller", properties: + meetingCaptureAnalyticsProperties(snapshot: recordingSnapshot.pipelineSnapshot, telemetryIdentity: recordingSnapshot.telemetryIdentity).merging([ + "failure_kind": files.micURL == nil && files.systemURL == nil ? "no_audio" : "unexpected_capture_stop", + "failure_stage": "capture_stop", "capture_outcome": failureOutcome.rawValue, + "trigger": recordingSnapshot.trigger.rawValue, + ], uniquingKeysWith: { _, new in new }), isFailure: true) DiagnosticsTrail.record( level: .error, engine: "meeting", event: "meeting_capture_stopped_under_controller", message: "Meeting capture stopped before the app stop path ran", context: baseDiagnosticsContext( - extra: [ + extra: failureContext.merging([ "mic_file_present": boolString(files.micURL != nil), "system_file_present": boolString(files.systemURL != nil), "preserved_for_retry": boolString(preserved), @@ -2213,24 +2221,24 @@ final class MeetingSessionController: ObservableObject { "quality_reason": recordingSnapshot.healthInfo.qualityReason.rawValue, "audio_gaps": "\(recordingSnapshot.healthInfo.audioGaps)", "device_switches": "\(recordingSnapshot.healthInfo.deviceSwitches)" - ] + ], uniquingKeysWith: { _, new in new }) ) ) - AnalyticsReporter.track( - "meeting_capture_stopped_under_controller", - properties: MeetingCaptureHealthTelemetry.snapshotProperties( + let healthProperties = MeetingCaptureHealthTelemetry.snapshotProperties( .init( - captureDiagnostics: meetingCaptureAnalyticsProperties(snapshot: recordingSnapshot.pipelineSnapshot), + captureDiagnostics: failureContext, health: captureHealthFacts(from: recordingSnapshot.healthInfo), trigger: recordingSnapshot.trigger.rawValue, reason: "internal_stop", durationSeconds: recordingSnapshot.durationSeconds, systemStreamPresent: files.systemURL != nil, - stopTimedOut: stopResult.didTimeOut + stopTimedOut: stopResult.didTimeOut, + captureOutcome: failureOutcome.rawValue ) ) - ) + AnalyticsReporter.track("meeting_capture_stopped_under_controller", properties: healthProperties) + AnalyticsReporter.track("meeting_capture_health_snapshot", properties: healthProperties) transition( to: preserved @@ -3158,12 +3166,12 @@ final class MeetingSessionController: ObservableObject { MeetingStartFailureClassifier.kind(from: message, stage: stage) } - private func meetingCaptureAnalyticsProperties(snapshot: AudioPipelineDiagnosticsSnapshot) -> [String: String] { + private func meetingCaptureAnalyticsProperties(snapshot: AudioPipelineDiagnosticsSnapshot, telemetryIdentity: UUID? = nil) -> [String: String] { var properties = snapshot.privacySafeContext.merging( MeetingCaptureVolumeDiagnostics.measurementScope, uniquingKeysWith: { _, scope in scope } ) - if let id = activeRecordingIdentity?.uuidString { + if let id = (telemetryIdentity ?? activeRecordingIdentity)?.uuidString { properties["session_id"] = id properties["correlation_id"] = id } @@ -3427,6 +3435,7 @@ final class MeetingSessionController: ObservableObject { healthInfo = baseHealthInfo } return RecordingStopSnapshot( + telemetryIdentity: activeRecordingIdentity, trigger: activeRecordingTrigger, systemAudioStatus: systemAudioStatus, durationSeconds: durationSeconds, diff --git a/Sources/Observability/AnalyticsReporter.swift b/Sources/Observability/AnalyticsReporter.swift index 4a078f485..4eb2e0561 100644 --- a/Sources/Observability/AnalyticsReporter.swift +++ b/Sources/Observability/AnalyticsReporter.swift @@ -6,7 +6,9 @@ struct AnalyticsCaptureRequest: Encodable { let distinctID: String let timestamp: String let properties: [String: String] + var uuid: String? = nil var personProperties: [String: String]? = nil + var aggregateProperties: [String: [String: String]]? = nil private struct PropertyKey: CodingKey { let stringValue: String @@ -22,6 +24,7 @@ struct AnalyticsCaptureRequest: Encodable { try container.encode(event, forKey: .event) try container.encode(distinctID, forKey: .distinctID) try container.encode(timestamp, forKey: .timestamp) + try container.encodeIfPresent(uuid, forKey: .uuid) var values = container.nestedContainer(keyedBy: PropertyKey.self, forKey: .properties) for (key, value) in properties { try values.encode(value, forKey: PropertyKey(key)) @@ -31,8 +34,17 @@ struct AnalyticsCaptureRequest: Encodable { if let personProperties { try values.encode(personProperties, forKey: PropertyKey("$set")) } + if event == "usage_digest", let aggregateProperties { + for key in ["failures_by_kind", "capture_quality_counts"] { + if let counts = aggregateProperties[key] { + let safeCounts = counts.filter { + PayloadSanitizationCore.category($0.key) != nil && ["0", "1", "2_3", "4_9", "10_plus"].contains($0.value) + } + try values.encode(safeCounts, forKey: PropertyKey(key)) + } + } + } try values.encode(true, forKey: PropertyKey("$geoip_disable")) - try values.encode(false, forKey: PropertyKey("$ip")) } enum CodingKeys: String, CodingKey { @@ -40,6 +52,7 @@ struct AnalyticsCaptureRequest: Encodable { case event case distinctID = "distinct_id" case timestamp + case uuid case properties } } @@ -54,6 +67,7 @@ struct PendingAnalyticsCapture: Codable, Equatable { var nextRetryAt: TimeInterval? let properties: [String: String] var personProperties: [String: String]? = nil + var aggregateProperties: [String: [String: String]]? = nil } struct AnalyticsDeliveryBufferStore { @@ -103,11 +117,12 @@ struct AnalyticsDeliveryBufferStore { } } - func save(_ records: [PendingAnalyticsCapture], now: Date = Date()) { + @discardableResult + func save(_ records: [PendingAnalyticsCapture], now: Date = Date()) -> Bool { let capped = cappedRecords(records, now: now) guard !capped.isEmpty else { remove() - return + return !fileManager.fileExists(atPath: fileURL.path) } do { @@ -115,8 +130,9 @@ struct AnalyticsDeliveryBufferStore { let data = try JSONEncoder().encode(BufferFile(version: 1, records: capped)) try data.write(to: fileURL, options: [.atomic]) fileManager.restrictFileToOwnerOnly(at: fileURL) + return true } catch { - return + return false } } @@ -126,8 +142,9 @@ struct AnalyticsDeliveryBufferStore { func cappedRecords(_ records: [PendingAnalyticsCapture], now: Date = Date()) -> [PendingAnalyticsCapture] { let cutoff = now.timeIntervalSince1970 - ttl + let digestCutoff = now.timeIntervalSince1970 - 14 * 24 * 60 * 60 var capped = records - .filter { $0.enqueuedAt >= cutoff } + .filter { $0.enqueuedAt >= ($0.event == "usage_digest" ? digestCutoff : cutoff) } .sorted { lhs, rhs in if lhs.enqueuedAt == rhs.enqueuedAt { return lhs.id < rhs.id @@ -135,8 +152,14 @@ struct AnalyticsDeliveryBufferStore { return lhs.enqueuedAt < rhs.enqueuedAt } - if capped.count > maxRecordCount { - capped = Array(capped.suffix(maxRecordCount)) + // Reserve capacity for at most 14 daily rollups. Lifecycle bursts must + // never evict their only durable copy. The whole file keeps its original + // count/byte bounds; ordinary events yield space first. + let retainedDigestIDs = Set(capped.filter { $0.event == "usage_digest" }.suffix(14).map(\.id)) + capped.removeAll { $0.event == "usage_digest" && !retainedDigestIDs.contains($0.id) } + while capped.count > maxRecordCount { + let index = capped.firstIndex { $0.event != "usage_digest" } ?? capped.startIndex + capped.remove(at: index) } // Encode once up front, then trim by subtracting each removed record's own @@ -145,7 +168,8 @@ struct AnalyticsDeliveryBufferStore { // encoded bytes plus one array separator. var totalBytes = encodedByteCount(capped) while !capped.isEmpty && totalBytes > maxFileBytes { - let removed = capped.removeFirst() + let index = capped.firstIndex { $0.event != "usage_digest" } ?? capped.startIndex + let removed = capped.remove(at: index) let removedBytes = (try? JSONEncoder().encode(removed).count) ?? 0 let separatorBytes = capped.isEmpty ? 0 : 1 totalBytes = max(0, totalBytes - removedBytes - separatorBytes) @@ -488,7 +512,10 @@ final class AnalyticsReporter { queue: nil ) { [weak self] _ in guard let self else { return } - if !self.analyticsEnabled() { + // Defaults mutations made by the ledger also notify synchronously. + // Serialize clearing after the writer releases its ledger lock. + self.deliveryQueue.async { [weak self] in + guard let self, !self.analyticsEnabled() else { return } self.clearPendingCaptures() } } @@ -500,10 +527,22 @@ final class AnalyticsReporter { queue: nil ) { [weak self] _ in // Final synchronous persist so debounced buffer writes are not lost on quit. + self?.enqueueUsageDigests(includeCurrentDay: true) self?.persistPendingCapturesNow() } + if usageStore != nil { + let timer = DispatchSource.makeTimerSource(queue: deliveryQueue) + timer.schedule(deadline: .now() + 60, repeating: 60) + timer.setEventHandler { [weak self] in + self?.enqueueUsageDigests(includeCurrentDay: false) + self?.flushPendingCapturesLocked() + } + digestTimer = timer + timer.resume() + } if self.analyticsEnabled() { + enqueueUsageDigests(includeCurrentDay: false) flushPendingCaptures() } else { clearPendingCaptures() @@ -511,6 +550,7 @@ final class AnalyticsReporter { } deinit { + digestTimer?.cancel() if let preferenceObserver { NotificationCenter.default.removeObserver(preferenceObserver) } @@ -537,6 +577,7 @@ final class AnalyticsReporter { private var inFlightCaptureIDs: Set = [] private var preferenceObserver: NSObjectProtocol? private var terminationObserver: NSObjectProtocol? + private var digestTimer: DispatchSourceTimer? // The in-memory buffer is the source of truth after the first load; disk writes // are debounced so a burst of tracked events costs one file write, not one per @@ -591,6 +632,38 @@ final class AnalyticsReporter { enqueue(capture) } + func enqueueUsageDigests(includeCurrentDay: Bool) { + syncOnDeliveryQueue { + guard self.analyticsEnabled(), let store = self.usageStore, + self.apiKey != nil, let host = self.captureHost, + self.normalizedCaptureURL(from: host) != nil, + let policy = AnalyticsEventPolicy.policy(forEvent: "usage_digest") else { return } + let now = self.currentDate() + self.loadPendingCapturesIfNeededLocked(now: now) + for digest in store.pendingDigests(includeCurrentDay: includeCurrentDay, now: now) { + if !self.pendingCaptures.contains(where: { $0.id == digest.id }) { + var properties = digest.properties + properties["install_uuid"] = self.distinctID + let safe = AnalyticsPayloadSanitizer.sanitizeProperties(properties, allowedKeys: policy.allowedProperties) + self.pendingCaptures.append(PendingAnalyticsCapture( + id: digest.id, event: "usage_digest", distinctID: self.distinctID, + timestamp: Self.isoDateFormatter.string(from: now), enqueuedAt: now.timeIntervalSince1970, + attemptCount: 0, nextRetryAt: nil, + properties: Self.captureProperties(sanitizedProperties: safe, distinctID: self.distinctID, sessionID: self.sessionID), + personProperties: InstallIdentity.traits(userDefaults: self.userDefaults, now: now), + aggregateProperties: digest.aggregates + )) + self.pendingCaptures = self.bufferStore.cappedRecords(self.pendingCaptures, now: now) + self.needsPersist = true + } + guard self.pendingCaptures.contains(where: { $0.id == digest.id }), + self.persistPendingCapturesLocked() else { continue } + store.markDigestEnqueued(id: digest.id) + } + self.flushPendingCapturesLocked() + } + } + func flushPendingCapturesForTesting() { flushPendingCaptures() } @@ -676,14 +749,17 @@ final class AnalyticsReporter { deliveryQueue.asyncAfter(deadline: .now() + persistDebounceInterval, execute: workItem) } - private func persistPendingCapturesLocked() { + @discardableResult + private func persistPendingCapturesLocked() -> Bool { pendingPersistWorkItem?.cancel() pendingPersistWorkItem = nil - guard needsPersist else { return } + guard needsPersist else { return true } needsPersist = false // `save` re-applies TTL/count/byte caps and removes the file when empty, so // the on-disk format, owner-only permissions, and cap semantics are unchanged. - bufferStore.save(pendingCaptures, now: currentDate()) + let saved = bufferStore.save(pendingCaptures, now: currentDate()) + needsPersist = !saved + return saved } private func syncOnDeliveryQueue(_ work: () -> Void) { @@ -728,7 +804,9 @@ final class AnalyticsReporter { distinctID: capture.distinctID, timestamp: capture.timestamp, properties: capture.properties, - personProperties: capture.personProperties + uuid: capture.id, + personProperties: capture.personProperties, + aggregateProperties: capture.aggregateProperties ) guard let data = try? JSONEncoder().encode(payload) else { diff --git a/Sources/Observability/PayloadSanitizationCore.swift b/Sources/Observability/PayloadSanitizationCore.swift index e68b45772..bd9d385ff 100644 --- a/Sources/Observability/PayloadSanitizationCore.swift +++ b/Sources/Observability/PayloadSanitizationCore.swift @@ -11,6 +11,12 @@ import Foundation /// Generic free-text patterns live in `PrivacyTextRedactor`; the app-specific /// path profile stays behind `ObservabilityTextRedactor`. enum PayloadSanitizationCore { + static let commonTelemetryKeys: Set = [ + "session_id", "correlation_id", "failure_kind", "failure_stage", "start_failure_stage", + "app_version", "build_revision", "os_major", "input_device_class", "output_device_class", + "selection_reason", "mic_permission_granted", "screen_permission_granted", + "accessibility_permission_granted", "trigger", "quality_reason", "capture_outcome", + ] static func uuid(_ value: String?) -> String? { guard let value, UUID(uuidString: value) != nil else { return nil } return value diff --git a/Sources/Observability/SentryEventPolicy.swift b/Sources/Observability/SentryEventPolicy.swift index e98938804..cbec13427 100644 --- a/Sources/Observability/SentryEventPolicy.swift +++ b/Sources/Observability/SentryEventPolicy.swift @@ -21,26 +21,14 @@ struct SentryEventPolicy: Equatable { tags["wait_bucket"] = waitBucket } - // `reason` is allowlisted because callers usually set it to a short - // enum-style value (e.g. "preferred_built_in_for_bluetooth_headset"), - // but some producers pass free-text error strings. The redactor in - // `sanitizeTags` strips obvious paths/emails/secrets, yet a hard length - // cap is the cheaper backstop against an accidental free-text blob - // riding off-device under a "safe" key. We keep `reason` (it carries - // diagnostic signal `failure_kind` does not always duplicate) but bound - // it tightly; enum-style values fit well under the cap. - if let reason = tags["reason"], reason.count > maxReasonTagLength { - tags["reason"] = String(reason.prefix(maxReasonTagLength)) + "..." + // Reasons must be codes, never a shortened excerpt of a raw error. + if let reason = tags["reason"], PayloadSanitizationCore.category(reason) == nil { + tags["reason"] = "unknown" } return SentryPayloadSanitizer.sanitizeTags(tags) } - /// Hard cap for the free-text-capable `reason` diagnostic tag. Enum-style - /// reason values stay well below this; anything longer is truncated before - /// redaction so a stray error message cannot ship a large blob to Sentry. - static let maxReasonTagLength = 80 - private static let allowedDiagnosticTagKeys: Set = [ "attenuation_kind", "buffer_success_bucket", @@ -116,6 +104,11 @@ struct SentryEventPolicy: Equatable { ] private static let allowedPolicies: [String: SentryEventPolicy] = [ + "meeting.meeting_capture_stopped_under_controller": .init( + engine: "meeting", + event: "meeting_capture_stopped_under_controller", + summary: "Meeting recording stopped unexpectedly before completion." + ), "app.session_stall_detected": .init( engine: "app", event: "session_stall_detected", diff --git a/Sources/Observability/TelemetryContext.swift b/Sources/Observability/TelemetryContext.swift index b129be2f8..a16aa0eef 100644 --- a/Sources/Observability/TelemetryContext.swift +++ b/Sources/Observability/TelemetryContext.swift @@ -3,12 +3,7 @@ import Foundation /// Shared metadata contract. Only UUIDs and categorical state cross the reporting boundary. enum TelemetryContext { static let launchSessionID = UUID().uuidString - static let keys: Set = [ - "session_id", "correlation_id", "failure_kind", "failure_stage", "start_failure_stage", - "app_version", "build_revision", "os_major", "input_device_class", "output_device_class", - "selection_reason", "mic_permission_granted", "screen_permission_granted", - "accessibility_permission_granted", "trigger", "quality_reason", "capture_outcome", - ] + static let keys = PayloadSanitizationCore.commonTelemetryKeys static let deviceClasses: Set = [ "built_in", "bluetooth", "usb", "aggregate", "virtual", "continuity", "wired", "external", "hdmi", "displayport", "airplay", "thunderbolt", "firewire", "pci", "unknown", "none", @@ -44,9 +39,13 @@ enum TelemetryContext { for key in ["mic_permission_granted", "screen_permission_granted", "accessibility_permission_granted"] { result[key] = result[key] == "true" ? "true" : "false" } - let failed = isFailure || event.hasSuffix("_failed") || event == "reliability_failure_observed" + let outcome = category(result["capture_outcome"]) ?? "unknown" + let outcomeFailure = ["no_audio", "timed_out", "stop_timed_out", "failed"].contains(outcome) + let failed = isFailure || event.hasSuffix("_failed") || event == "reliability_failure_observed" || outcomeFailure if failed || event == "product_friction_observed" || event == "meeting_capture_health_snapshot" { - let fallbackKind = failed ? (category(event) ?? "unknown") : "none" + let fallbackKind = outcomeFailure ? outcome + : failed ? (category(event) ?? "unknown") + : event == "meeting_capture_health_snapshot" && outcome == "unknown" ? "unknown" : "none" result["failure_kind"] = category(result["failure_kind"]) ?? fallbackKind result["failure_stage"] = category(result["failure_stage"]) ?? category(result["start_failure_stage"]) ?? category(result["stage"]) diff --git a/Sources/Observability/UsageHealthModels.swift b/Sources/Observability/UsageHealthModels.swift new file mode 100644 index 000000000..8c830ad26 --- /dev/null +++ b/Sources/Observability/UsageHealthModels.swift @@ -0,0 +1,40 @@ +import Foundation + +struct UsageFailure: Codable, Equatable, Identifiable { + var id: String + var kind: String + var stage: String + var time: Date + var version: String +} + +struct UsageDay: Codable, Equatable { + var day: String + var startedAt: Date + var meetingsStarted = 0 + var meetingsCompleted = 0 + // Aggregate rounded minutes only; no per-recording duration or content is retained. + var meetingMinutes = 0 + var dictationsCompleted = 0 + var dictationDurationCounts: [String: Int] = [:] + var failuresByKind: [String: Int] = [:] + var captureQualityCounts: [String: Int] = [:] + var seenOutcomes: [String] = [] + var digestID = UUID().uuidString + var digestEnqueued = false +} + +struct UsageHealthSnapshot: Equatable { + var meetings = 0 + var dictations = 0 + var meetingMinutesBucket = "0" + var qualityCounts: [String: Int] = [:] + var failures: [UsageFailure] = [] +} + +struct UsageDigest: Equatable { + var id: String + var day: String + var properties: [String: String] + var aggregates: [String: [String: String]] +} diff --git a/Sources/Observability/UsageHealthStore.swift b/Sources/Observability/UsageHealthStore.swift index 5c85fee09..b16844821 100644 --- a/Sources/Observability/UsageHealthStore.swift +++ b/Sources/Observability/UsageHealthStore.swift @@ -1,42 +1,11 @@ import Foundation -struct UsageFailure: Codable, Equatable, Identifiable { - var id: String - var kind: String - var stage: String - var time: Date - var version: String -} - -struct UsageDay: Codable, Equatable { - var day: String - var startedAt: Date - var meetingsStarted = 0 - var meetingsCompleted = 0 - // Aggregate rounded minutes only; no per-recording duration or content is retained. - var meetingMinutes = 0 - var dictationsCompleted = 0 - var dictationDurationCounts: [String: Int] = [:] - var failuresByKind: [String: Int] = [:] - var captureQualityCounts: [String: Int] = [:] - var seenOutcomes: [String] = [] - var digestID = UUID().uuidString - var digestEnqueued = false -} - -struct UsageHealthSnapshot: Equatable { - var meetings = 0 - var dictations = 0 - var meetingMinutesBucket = "0" - var qualityCounts: [String: Int] = [:] - var failures: [UsageFailure] = [] -} - /// Bounded metadata ledger, populated from event enums only. Never scans a capture or a log. final class UsageHealthStore { static let shared = UsageHealthStore() static let didChange = Notification.Name("TranscriptedUsageHealthDidChange") static let storageKey = "observability-usage-health-v1" + static let digestReceiptKey = "observability-usage-digest-days-v1" static let durationBuckets = ["lt_10s", "10_29s", "30_119s", "2_9m", "10_29m", "30m_plus"] private struct State: Codable { var days: [UsageDay] = [] @@ -54,16 +23,21 @@ final class UsageHealthStore { func clear() { lock.lock() + let hadData = !state.days.isEmpty || !state.failures.isEmpty || defaults.data(forKey: Self.storageKey) != nil state = State() - defaults.removeObject(forKey: Self.storageKey) lock.unlock() + guard hadData else { return } + // UserDefaults notifications can re-enter observers synchronously. Never + // mutate defaults under this lock when clearing; repeated clears are no-ops. + defaults.removeObject(forKey: Self.storageKey) NotificationCenter.default.post(name: Self.didChange, object: nil) } func record(event: String, properties: [String: String], durationSeconds: Double? = nil, now: Date = Date(), calendar: Calendar = .current) { let relevant = ["meeting_recording_started", "meeting_transcript_saved", "meeting_recording_stopped", - "dictation_completed", "meeting_capture_health_snapshot", "reliability_failure_observed"].contains(event) + "dictation_completed", "meeting_capture_health_snapshot", "reliability_failure_observed", + "meeting_capture_stopped_under_controller"].contains(event) || event.hasSuffix("_failed") guard relevant else { return } lock.lock() @@ -80,6 +54,7 @@ final class UsageHealthStore { let index = state.days.firstIndex(where: { $0.day == key })! var day = state.days[index] let isFailure = event.hasSuffix("_failed") || event == "reliability_failure_observed" + || event == "meeting_capture_stopped_under_controller" let kind = TelemetryContext.category(properties["failure_kind"]) ?? "unknown" let correlation = TelemetryContext.uuid(properties["correlation_id"]) ?? UUID().uuidString let outcomeKey = correlation + ":" + (isFailure ? "failure:" + kind : event) @@ -137,6 +112,59 @@ final class UsageHealthStore { return snapshot } + func pendingDigests(includeCurrentDay: Bool, now: Date = Date(), calendar: Calendar = .current) -> [UsageDigest] { + lock.lock() + defer { lock.unlock() } + guard AnalyticsPreferences.isEnabled(userDefaults: defaults) else { return [] } + prune(now: now, calendar: calendar) + let today = Self.dayKey(now, calendar: calendar) + let receipts = Set(defaults.stringArray(forKey: Self.digestReceiptKey) ?? []) + return state.days.filter { !$0.digestEnqueued && !receipts.contains($0.day) && ($0.day < today || (includeCurrentDay && $0.day == today)) }.map { day in + UsageDigest(id: day.digestID, day: day.day, properties: [ + "digest_day": day.day, + "digest_is_partial": String(day.day == today), + "meetings_started": AnalyticsReporter.countBucket(day.meetingsStarted), + "meetings_completed": AnalyticsReporter.countBucket(day.meetingsCompleted), + "meeting_minutes_bucket": Self.minutesBucket(day.meetingMinutes), + "dictations_completed": AnalyticsReporter.countBucket(day.dictationsCompleted), + "dictation_median_duration_bucket": Self.medianDurationBucket(day.dictationDurationCounts), + ], aggregates: [ + "failures_by_kind": day.failuresByKind.mapValues(AnalyticsReporter.countBucket), + "capture_quality_counts": Dictionary(uniqueKeysWithValues: ["good", "degraded", "failed", "unknown"].map { + ($0, AnalyticsReporter.countBucket(day.captureQualityCounts[$0, default: 0])) + }), + ]) + } + } + + /// Called only after the matching capture is durably enqueued. Its stable insert ID + /// covers the small crash window between buffer persistence and this marker. + func markDigestEnqueued(id: String) { + lock.lock() + defer { lock.unlock() } + guard AnalyticsPreferences.isEnabled(userDefaults: defaults), + let index = state.days.firstIndex(where: { $0.digestID == id }) else { return } + state.days[index].digestEnqueued = true + // Retain only day receipts across opt-out. No counts, identity or activity + // remain; these prevent a second partial digest after re-enabling that day. + var receipts = Set(defaults.stringArray(forKey: Self.digestReceiptKey) ?? []) + receipts.insert(state.days[index].day) + defaults.set(Array(receipts.sorted().suffix(14)), forKey: Self.digestReceiptKey) + persist() + } + + static func medianDurationBucket(_ counts: [String: Int]) -> String { + let total = durationBuckets.reduce(0) { $0 + counts[$1, default: 0] } + guard total > 0 else { return "none" } + let rank = (total + 1) / 2 + var cumulative = 0 + for bucket in durationBuckets { + cumulative += counts[bucket, default: 0] + if cumulative >= rank { return bucket } + } + return "none" + } + static func minutesBucket(_ minutes: Int) -> String { switch minutes { case ..<1: return "0" diff --git a/Sources/UI/Shared/SupportDiagnosticsBundle.swift b/Sources/UI/Shared/SupportDiagnosticsBundle.swift index af1a02e85..39480c1f3 100644 --- a/Sources/UI/Shared/SupportDiagnosticsBundle.swift +++ b/Sources/UI/Shared/SupportDiagnosticsBundle.swift @@ -35,7 +35,7 @@ enum SupportDiagnosticsBundle { static func text(snapshot: SupportDiagnosticsSnapshot, now: Date = Date()) -> String { let failures = snapshot.recentFailures.prefix(3).map { - "\($0.time.formatted(date: .abbreviated, time: .shortened)) | \(TelemetryContext.category($0.kind) ?? "unknown") | \(TelemetryContext.category($0.stage) ?? "unknown") | version \(TelemetryContext.category($0.version) ?? "unknown")" + "\($0.time.formatted(date: .abbreviated, time: .shortened)) | \(PayloadSanitizationCore.category($0.kind) ?? "unknown") | \(PayloadSanitizationCore.category($0.stage) ?? "unknown") | version \(PayloadSanitizationCore.category($0.version) ?? "unknown")" } return """ @@ -45,8 +45,8 @@ enum SupportDiagnosticsBundle { App Version: \(snapshot.appVersion) Build: \(snapshot.buildVersion) - Revision: \(TelemetryContext.category(snapshot.buildRevision) ?? "unknown") - Install UUID: \(TelemetryContext.uuid(snapshot.installUUID) ?? "unknown") + Revision: \(PayloadSanitizationCore.category(snapshot.buildRevision) ?? "unknown") + Install UUID: \(PayloadSanitizationCore.uuid(snapshot.installUUID) ?? "unknown") macOS: \(snapshot.osVersion) Reporting @@ -87,11 +87,11 @@ enum SupportDiagnosticsBundle { static func sentryContext(snapshot: SupportDiagnosticsSnapshot) -> [String: String] { var context: [String: String] = [ - "install_uuid": TelemetryContext.uuid(snapshot.installUUID) ?? "unknown", - "build_revision": TelemetryContext.category(snapshot.buildRevision) ?? "unknown", - "last_failure_kind": TelemetryContext.category(snapshot.recentFailures.first?.kind) ?? "none", - "last_failure_stage": TelemetryContext.category(snapshot.recentFailures.first?.stage) ?? "none", - "last_failure_version": TelemetryContext.category(snapshot.recentFailures.first?.version) ?? "unknown", + "install_uuid": PayloadSanitizationCore.uuid(snapshot.installUUID) ?? "unknown", + "build_revision": PayloadSanitizationCore.category(snapshot.buildRevision) ?? "unknown", + "last_failure_kind": PayloadSanitizationCore.category(snapshot.recentFailures.first?.kind) ?? "none", + "last_failure_stage": PayloadSanitizationCore.category(snapshot.recentFailures.first?.stage) ?? "none", + "last_failure_version": PayloadSanitizationCore.category(snapshot.recentFailures.first?.version) ?? "unknown", "analytics_available": bool(snapshot.analyticsAvailable), "analytics_enabled": bool(snapshot.analyticsEnabled), "app_version": snapshot.appVersion, @@ -189,7 +189,7 @@ enum SupportDiagnosticsBundle { } private static func safeMetadata(_ values: [String: String]) -> [String: String] { - let keys = TelemetryContext.keys.union([ + let keys = PayloadSanitizationCore.commonTelemetryKeys.union([ "session_stage", "session_kind", "session_active", "previous_clean_shutdown", "heartbeat_age_bucket", "last_event", "session_duration_bucket", "route_shape", "default_input_class", "default_output_class", "selected_input_class", "selection_overrode_default", "input_channels", "output_channels", @@ -201,7 +201,7 @@ enum SupportDiagnosticsBundle { if key == "model_cache_total" || key == "known_stale_model_size" { return value.range(of: #"^[0-9]+(?:[.,][0-9]+)? (?:bytes|KB|MB|GB|TB)$"#, options: .regularExpression) != nil } - return TelemetryContext.category(value) != nil + return PayloadSanitizationCore.category(value) != nil } } diff --git a/Tests/AnalyticsPayloadSanitizerTests.swift b/Tests/AnalyticsPayloadSanitizerTests.swift index b7cc508cd..e685c37ce 100644 --- a/Tests/AnalyticsPayloadSanitizerTests.swift +++ b/Tests/AnalyticsPayloadSanitizerTests.swift @@ -11,6 +11,13 @@ func testAnalyticsPayloadSanitizer() { } } + runSuite("Analytics taxonomy drops free text instead of forwarding redacted excerpts") { + for key in ["failure_kind", "failure_stage", "start_failure_stage", "trigger", "quality_reason", "capture_outcome", "selection_reason"] { + let safe = AnalyticsPayloadSanitizer.sanitizeProperties([key: "Confidential words /Users/example/private.txt"], allowedKeys: [key]) + assertNil(safe[key], "taxonomy values must be codes") + } + } + let corpus = loadJSONFixture("Tests/Fixtures/ObservabilitySanitizerCorpus.json", as: ObservabilitySanitizerCorpus.self) runSuite("AnalyticsPayloadSanitizer keeps only allowlisted coarse properties") { @@ -33,12 +40,12 @@ func testAnalyticsPayloadSanitizer() { runSuite("AnalyticsPayloadSanitizer redacts file paths and emails from values") { let sanitized = AnalyticsPayloadSanitizer.sanitizeProperties( [ - "failure_kind": "Saved to /Users/redbars/Library/Application Support/Transcripted/logs/app.jsonl by person@example.com on Redbarss-MacBook-Pro.local", + "diagnostic_value": "Saved to /Users/redbars/Library/Application Support/Transcripted/logs/app.jsonl by person@example.com on Redbarss-MacBook-Pro.local", ], - allowedKeys: ["failure_kind"] + allowedKeys: ["diagnostic_value"] ) - let value = sanitized["failure_kind"] ?? "" + let value = sanitized["diagnostic_value"] ?? "" assertFalse(value.contains("/Users/redbars/"), "user paths should be redacted") assertFalse(value.contains("Application Support/Transcripted/logs/app.jsonl"), "app support paths should be fully redacted") assertFalse(value.contains("person@example.com"), "emails should be redacted") @@ -51,12 +58,12 @@ func testAnalyticsPayloadSanitizer() { runSuite("AnalyticsPayloadSanitizer redacts synthetic-root macOS paths from values") { let sanitized = AnalyticsPayloadSanitizer.sanitizeProperties( [ - "failure_kind": "Saved to /System/Volumes/Data/Users/redbars/Library/Application Support/Transcripted/logs/app.jsonl before retry", + "diagnostic_value": "Saved to /System/Volumes/Data/Users/redbars/Library/Application Support/Transcripted/logs/app.jsonl before retry", ], - allowedKeys: ["failure_kind"] + allowedKeys: ["diagnostic_value"] ) - let value = sanitized["failure_kind"] ?? "" + let value = sanitized["diagnostic_value"] ?? "" assertFalse(value.contains("/System/Volumes/Data/Users/redbars/"), "synthetic-root home paths should be redacted") assertFalse(value.contains("Application Support/Transcripted/logs/app.jsonl"), "synthetic-root app support path should be fully redacted") assertTrue(value.contains("[redacted-path]"), "path marker should remain") @@ -100,12 +107,12 @@ func testAnalyticsPayloadSanitizer() { runSuite("AnalyticsPayloadSanitizer redacts raw URLs and common secret values") { let sanitized = AnalyticsPayloadSanitizer.sanitizeProperties( [ - "failure_kind": "Upload failed at https://example.com/path?token=abc123 with github_pat_abcdefghijklmnopqrstuvwxyz_1234567890 AKIAIOSFODNN7EXAMPLE AIzaSyA-BCDEFGHIJKLMNOPQRSTUVWXYZ123456 and api_key=secret-value password=hunter2 client_secret:supersecret credential=temp-pass", + "diagnostic_value": "Upload failed at https://example.com/path?token=abc123 with github_pat_abcdefghijklmnopqrstuvwxyz_1234567890 AKIAIOSFODNN7EXAMPLE AIzaSyA-BCDEFGHIJKLMNOPQRSTUVWXYZ123456 and api_key=secret-value password=hunter2 client_secret:supersecret credential=temp-pass", ], - allowedKeys: ["failure_kind"] + allowedKeys: ["diagnostic_value"] ) - let value = sanitized["failure_kind"] ?? "" + let value = sanitized["diagnostic_value"] ?? "" assertFalse(value.contains("https://example.com/path?token=abc123"), "raw URLs should be redacted") assertFalse(value.contains("github_pat_abcdefghijklmnopqrstuvwxyz_1234567890"), "GitHub fine-grained tokens should be redacted") assertFalse(value.contains("AKIAIOSFODNN7EXAMPLE"), "AWS access key IDs should be redacted") @@ -121,12 +128,12 @@ func testAnalyticsPayloadSanitizer() { runSuite("AnalyticsPayloadSanitizer redacts bearer headers and sk-style keys") { let sanitized = AnalyticsPayloadSanitizer.sanitizeProperties( [ - "failure_kind": "Request used Bearer abc123 and sk-proj-secret-value while retrying", + "diagnostic_value": "Request used Bearer abc123 and sk-proj-secret-value while retrying", ], - allowedKeys: ["failure_kind"] + allowedKeys: ["diagnostic_value"] ) - let value = sanitized["failure_kind"] ?? "" + let value = sanitized["diagnostic_value"] ?? "" assertFalse(value.contains("Bearer abc123"), "bearer headers should be redacted") assertFalse(value.contains("sk-proj-secret-value"), "sk-style API keys should be redacted") assertTrue(value.contains("Bearer ****"), "bearer marker should remain") @@ -145,12 +152,12 @@ func testAnalyticsPayloadSanitizer() { runSuite("AnalyticsPayloadSanitizer redacts basic auth headers and authorization assignments") { let sanitized = AnalyticsPayloadSanitizer.sanitizeProperties( [ - "failure_kind": "Authorization: Basic dXNlcjpwYXNz Basic ZGVtbzpwYXNz", + "diagnostic_value": "Authorization: Basic dXNlcjpwYXNz Basic ZGVtbzpwYXNz", ], - allowedKeys: ["failure_kind"] + allowedKeys: ["diagnostic_value"] ) - let value = sanitized["failure_kind"] ?? "" + let value = sanitized["diagnostic_value"] ?? "" assertFalse(value.contains("dXNlcjpwYXNz"), "basic auth payloads should be redacted") assertFalse(value.contains("ZGVtbzpwYXNz"), "standalone basic auth values should be redacted") assertTrue(value.contains("Authorization=[redacted-secret]"), "authorization assignments should collapse to a redacted marker") @@ -160,17 +167,17 @@ func testAnalyticsPayloadSanitizer() { runSuite("AnalyticsPayloadSanitizer redacts PEM private key material") { let sanitized = AnalyticsPayloadSanitizer.sanitizeProperties( [ - "failure_kind": """ + "diagnostic_value": """ Failure included: -----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEAz7i9W5tQ3k3FdemoKeyMaterial -----END RSA PRIVATE KEY----- """, ], - allowedKeys: ["failure_kind"] + allowedKeys: ["diagnostic_value"] ) - let value = sanitized["failure_kind"] ?? "" + let value = sanitized["diagnostic_value"] ?? "" assertFalse(value.contains("BEGIN RSA PRIVATE KEY"), "private key header should be redacted") assertFalse(value.contains("MIIEpAIBAAKCAQEA"), "private key body should be redacted") assertFalse(value.contains("END RSA PRIVATE KEY"), "private key footer should be redacted") diff --git a/Tests/AnalyticsReporterTests.swift b/Tests/AnalyticsReporterTests.swift index 141e8da5a..20868166d 100644 --- a/Tests/AnalyticsReporterTests.swift +++ b/Tests/AnalyticsReporterTests.swift @@ -36,6 +36,34 @@ func testAnalyticsReporter() { assertEqual(InstallIdentity.id(userDefaults: defaults), id, "replacement is stable") } + runSuite("Usage digest is durably queued once and carries only bucketed metadata") { + let fixture = makeAnalyticsReporterFixture(responses: [.networkFailure, .networkFailure], includeUsageStore: true) + defer { fixture.cleanup() } + fixture.reporter.trackEvent("dictation_completed", properties: ["duration_bucket": "10_29s", "transcript_text": "Private words"]) + fixture.reporter.enqueueUsageDigests(includeCurrentDay: true) + fixture.reporter.enqueueUsageDigests(includeCurrentDay: true) + fixture.reporter.persistPendingCapturesNow() + let digests = loadBufferedAnalyticsCaptures(from: fixture.bufferURL).filter { $0.event == "usage_digest" } + assertEqual(digests.count, 1, "repeated quit/timer callbacks enqueue one digest") + let digest = digests.first! + assertEqual(digest.properties["dictations_completed"], "1", "count uses the count bucket") + assertEqual(digest.properties["dictation_median_duration_bucket"], "10_29s", "median uses only histogram buckets") + assertEqual(digest.properties["install_uuid"], digest.distinctID, "digest joins the same install") + let payload = AnalyticsCaptureRequest(apiKey: "test", event: digest.event, distinctID: digest.distinctID, + timestamp: digest.timestamp, properties: digest.properties, uuid: digest.id, personProperties: digest.personProperties, + aggregateProperties: digest.aggregateProperties) + let json = String(decoding: try! JSONEncoder().encode(payload), as: UTF8.self) + assertTrue(json.contains("capture_quality_counts"), "quality is a JSON aggregate object") + assertFalse(json.contains("Private words"), "no capture content enters the digest") + let reloaded = UsageHealthStore(userDefaults: fixture.userDefaults) + assertEqual(reloaded.pendingDigests(includeCurrentDay: true, now: Date(timeIntervalSince1970: 2_000)).count, 0, "restart retains sent-day marker") + AnalyticsPreferences.setEnabled(false, userDefaults: fixture.userDefaults) + fixture.reporter.trackEvent("dictation_completed") + fixture.reporter.enqueueUsageDigests(includeCurrentDay: true) + assertNil(fixture.userDefaults.data(forKey: UsageHealthStore.storageKey), "opt-out clears rollups") + assertFalse(FileManager.default.fileExists(atPath: fixture.bufferURL.path), "opt-out clears unsent digests and person traits") + } + runSuite("AnalyticsRuntimeConfiguration prefers Transcripted overrides before legacy Draft") { let appSupport = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true) .appendingPathComponent("AnalyticsReporterTests-\(UUID().uuidString)", isDirectory: true) @@ -471,6 +499,11 @@ func testAnalyticsReporter() { waitUntil { AnalyticsReporterTestURLProtocol.requestCount() == 2 && !FileManager.default.fileExists(atPath: fixture.bufferURL.path) }, "successful retry should delete the persisted capture" ) + let payloads = AnalyticsReporterTestURLProtocol.wirePayloads() + assertEqual(payloads.count, 2, "both transport attempts were recorded") + assertNotNil(payloads.first?["uuid"] as? String, "capture provides PostHog's top-level UUID") + assertEqual(payloads.first?["uuid"] as? String, payloads.last?["uuid"] as? String, "retries preserve the dedup UUID") + assertEqual(payloads.first?["timestamp"] as? String, payloads.last?["timestamp"] as? String, "retries preserve occurrence time") } runSuite("AnalyticsReporter drops non-429 4xx responses and retains 429 responses") { @@ -524,16 +557,17 @@ func testAnalyticsReporter() { } runSuite("AnalyticsReporter opt-out wipes the retry buffer and prevents delivery") { - let fixture = makeAnalyticsReporterFixture(responses: [.networkFailure], observePreferenceChanges: true) + let fixture = makeAnalyticsReporterFixture(responses: [.networkFailure], observePreferenceChanges: true, includeUsageStore: true) defer { fixture.cleanup() } - fixture.reporter.trackEvent("app_launched") + fixture.reporter.trackEvent("dictation_completed") assertTrue( waitUntil { AnalyticsReporterTestURLProtocol.requestCount() == 1 && loadBufferedAnalyticsCaptures(from: fixture.bufferURL).count == 1 }, "failed delivery should create a retry file before opt-out" ) + assertNotNil(fixture.userDefaults.data(forKey: UsageHealthStore.storageKey), "observed reporter populated the ledger") AnalyticsPreferences.setEnabled(false, userDefaults: fixture.userDefaults) assertTrue( @@ -541,6 +575,7 @@ func testAnalyticsReporter() { "analytics opt-out notification should delete the retry file" ) + assertTrue(waitUntil { fixture.userDefaults.data(forKey: UsageHealthStore.storageKey) == nil }, "opt-out clears ledger without recursive defaults deadlock") fixture.reporter.trackEvent("app_launched") assertTrue( @@ -626,6 +661,18 @@ func testAnalyticsReporter() { let ttlCaptures = ttlStore.load(now: Date(timeIntervalSince1970: 1_020)) assertEqual(ttlCaptures.map(\.id), ["fresh"], "TTL should drop records older than one day in production") + let day: TimeInterval = 24 * 60 * 60 + let digest = makePendingAnalyticsCapture(id: "daily-rollup", event: "usage_digest", enqueuedAt: 1_000) + let burst = (0..<150).map { makePendingAnalyticsCapture(id: "burst-\($0)", enqueuedAt: 1_000 + 2 * day) } + defaultStore.save([digest] + burst, now: Date(timeIntervalSince1970: 1_000 + 2 * day)) + let offline = defaultStore.load(now: Date(timeIntervalSince1970: 1_000 + 2 * day)) + assertTrue(offline.contains { $0.id == digest.id }, "daily digest survives 48 hours offline and lifecycle count pressure") + assertTrue(offline.count <= 100, "reserved digest still respects the total count bound") + tinyStore.save([digest] + burst, now: Date(timeIntervalSince1970: 1_000 + 2 * day)) + assertTrue(tinyStore.load(now: Date(timeIntervalSince1970: 1_000 + 2 * day)).contains { $0.id == digest.id }, "byte pressure discards ordinary events before a digest") + assertTrue((try? Data(contentsOf: fixture.bufferURL).count) ?? Int.max <= 1_200, "reserved digest respects total bytes") + assertFalse(defaultStore.cappedRecords([digest], now: Date(timeIntervalSince1970: 1_000 + 15 * day)).contains { $0.id == digest.id }, "digest retention remains bounded at 14 days") + try? Data("not-json".utf8).write(to: fixture.bufferURL, options: [.atomic]) assertEqual(defaultStore.load().count, 0, "corrupt retry files should recover as an empty buffer") assertFalse(FileManager.default.fileExists(atPath: fixture.bufferURL.path), "corrupt retry files should be deleted") @@ -691,7 +738,8 @@ private func makeAnalyticsReporterFixture( persistDebounceInterval: TimeInterval = 0.05, analyticsEnabled: (() -> Bool)? = nil, observePreferenceChanges: Bool = false, - autostart: Bool = true + autostart: Bool = true, + includeUsageStore: Bool = false ) -> AnalyticsReporterFixture { AnalyticsReporterTestURLProtocol.reset(responses: responses) @@ -725,7 +773,8 @@ private func makeAnalyticsReporterFixture( retryDelay: retryDelay, persistDebounceInterval: persistDebounceInterval, analyticsEnabled: analyticsEnabled, - observePreferenceChanges: observePreferenceChanges + observePreferenceChanges: observePreferenceChanges, + usageStore: includeUsageStore ? UsageHealthStore(userDefaults: defaults) : nil ) } ) @@ -796,11 +845,19 @@ private final class AnalyticsReporterTestURLProtocol: URLProtocol { private static let lock = NSLock() private static var responses: [AnalyticsReporterTestResponse] = [] private static var requests: [URLRequest] = [] + private static var payloads: [[String: Any]] = [] + + static func wirePayloads() -> [[String: Any]] { + lock.lock() + defer { lock.unlock() } + return payloads + } static func reset(responses: [AnalyticsReporterTestResponse]) { lock.lock() self.responses = responses self.requests = [] + self.payloads = [] lock.unlock() } @@ -840,8 +897,21 @@ private final class AnalyticsReporterTestURLProtocol: URLProtocol { override func stopLoading() {} private static func nextResponse(recording request: URLRequest) -> AnalyticsReporterTestResponse { + var body = request.httpBody ?? Data() + if body.isEmpty, let stream = request.httpBodyStream { + stream.open() + var bytes = [UInt8](repeating: 0, count: 4096) + while stream.hasBytesAvailable { + let count = stream.read(&bytes, maxLength: bytes.count) + guard count > 0 else { break } + body.append(contentsOf: bytes.prefix(count)) + } + stream.close() + } + let payload = (try? JSONSerialization.jsonObject(with: body)) as? [String: Any] ?? [:] lock.lock() requests.append(request) + payloads.append(payload) let response = responses.isEmpty ? .status(200) : responses.removeFirst() lock.unlock() return response diff --git a/Tests/E2E/TranscriptedE2ESmoke.swift b/Tests/E2E/TranscriptedE2ESmoke.swift index 46d22d7a0..14402b436 100644 --- a/Tests/E2E/TranscriptedE2ESmoke.swift +++ b/Tests/E2E/TranscriptedE2ESmoke.swift @@ -511,7 +511,10 @@ private final class TranscriptedE2ESmokeHarness { recentLogLines: [ "DIAG source_app_name=Codex transcript_text=\(secretTranscript)", "DIAG meeting_title=\(secretMeetingTitle) speaker_name=\(secretSpeaker)", - ] + ], + installUUID: "F31DB235-6730-4620-9646-55F7CBE6FA0C", + buildRevision: "fixture-revision", + recentFailures: [.init(id: "fixture", kind: "mic_unavailable", stage: "start", time: try fixedDate("2026-05-18T15:00:00Z"), version: "9.9.9")] ), now: try fixedDate("2026-05-18T15:10:00Z") ) @@ -533,27 +536,20 @@ private final class TranscriptedE2ESmokeHarness { try expect(!diagnostics.contains(forbidden), "Support diagnostics should not contain sensitive value: \(forbidden)") } - // Absence of forbidden values alone also passes if a whole section was silently dropped or - // the body came back empty. Assert the redacted structure actually survived: known-safe - // fields must still be present, and the explicit redaction markers must appear, proving the - // sensitive sections were emitted and redacted rather than omitted. + // Known-safe structure proves the summary is useful even though raw + // logs and reliability packet strings are intentionally omitted. for expected in [ "Version: 9.9.9", "input_device_class: built_in", "session_stage: recording", + "Install UUID: F31DB235-6730-4620-9646-55F7CBE6FA0C", + "mic_unavailable | start | version 9.9.9", + "Revision: fixture-revision", ] { try expect(diagnostics.contains(expected), "Support diagnostics should keep known-safe field: \(expected)") } - // Note: the injected sk- token only appears inside `token=...` assignments, so the - // apiKeyRegex's "sk-****" is superseded by a later secret-assignment pass; the raw token's - // absence is already asserted above. These markers prove sections were emitted and redacted. - for marker in [ - "[redacted-path]", - "[redacted-email]", - "[redacted-sensitive-value]", - ] { - try expect(diagnostics.contains(marker), "Support diagnostics should surface redaction marker: \(marker)") - } + try expect(!diagnostics.contains("DIAG "), "Raw log lines must be omitted from copied diagnostics") + try expect(!diagnostics.contains("event=retry"), "Raw reliability packets must be omitted from copied diagnostics") let eventLog = try String(contentsOf: fixtures.eventLogURL, encoding: .utf8) try expect(!eventLog.contains(fixtures.meetingURL.path), "Sanitized observability event should not contain file paths") diff --git a/Tests/MeetingCaptureVolumeDiagnosticsTests.swift b/Tests/MeetingCaptureVolumeDiagnosticsTests.swift index 7690cf22a..f0fc2834e 100644 --- a/Tests/MeetingCaptureVolumeDiagnosticsTests.swift +++ b/Tests/MeetingCaptureVolumeDiagnosticsTests.swift @@ -483,7 +483,7 @@ func testMeetingCaptureVolumeDiagnostics() { let input = MeetingCaptureHealthTelemetry.SnapshotInput( captureDiagnostics: [:], health: .init(captureQuality: "good", audioGaps: 0, deviceSwitches: 0), trigger: "menu", reason: "stop_button", durationSeconds: 30, systemStreamPresent: true, stopTimedOut: false) - assertEqual(MeetingCaptureHealthTelemetry.snapshotProperties(input)["capture_outcome"], "complete", "normal stop is complete") + assertEqual(MeetingCaptureHealthTelemetry.snapshotProperties(input)["capture_outcome"], "unknown", "unmeasured outcome cannot claim completion") assertEqual(MeetingCaptureHealthTelemetry.snapshotProperties(input)["quality_reason"], "none", "good health has an explicit reason") var cancelled = input cancelled.captureOutcome = "cancelled" diff --git a/Tests/SentryEventPolicyTests.swift b/Tests/SentryEventPolicyTests.swift index 830b22695..4c2ebcfad 100644 --- a/Tests/SentryEventPolicyTests.swift +++ b/Tests/SentryEventPolicyTests.swift @@ -418,7 +418,7 @@ func testSentryEventPolicy() { assertNil(tags["transcript_text"], "transcript text must stay out of Sentry") } - runSuite("SentryEventPolicy diagnosticTags hard-caps the free-text reason tag") { + runSuite("SentryEventPolicy diagnosticTags rejects free-text reason tags") { let enumReason = "preferred_built_in_for_bluetooth_headset" let shortTags = SentryEventPolicy.diagnosticTags( forEngine: "meeting", @@ -433,16 +433,9 @@ func testSentryEventPolicy() { event: "meeting_transcript_failed", context: ["reason": freeText] ) - let cappedReason = cappedTags["reason"] - assertTrue(cappedReason != nil, "reason should still be forwarded after capping") - assertTrue( - (cappedReason?.count ?? 0) <= SentryEventPolicy.maxReasonTagLength + 3, - "an oversized free-text reason should be truncated to the hard cap plus ellipsis" - ) - assertTrue( - cappedReason?.hasSuffix("...") ?? false, - "a truncated reason should be marked with an ellipsis" - ) + assertEqual(cappedTags["reason"], "unknown", "free-text reasons never leave as a truncated excerpt") + let privateReason = SentryEventPolicy.diagnosticTags(forEngine: "meeting", event: "meeting_transcript_failed", context: ["reason": "Confidential meeting words"]) + assertEqual(privateReason["reason"], "unknown", "short free text is also excluded") } runSuite("Meeting stop emits one canonical Sentry terminal before generic degraded capture") { diff --git a/Tests/TelemetryContextTests.swift b/Tests/TelemetryContextTests.swift index 97d5d0f78..20220d2ab 100644 --- a/Tests/TelemetryContextTests.swift +++ b/Tests/TelemetryContextTests.swift @@ -25,6 +25,12 @@ func testTelemetryContext() { let health = TelemetryContext.enrich(event: "meeting_capture_health_snapshot", properties: [:], environment: permissions) assertEqual(health["quality_reason"], "unknown", "missing measurements never imply good health") assertEqual(health["capture_outcome"], "unknown", "missing outcome never implies completion") + assertEqual(health["failure_kind"], "unknown", "unmeasured health does not claim no failure") + for outcome in ["no_audio", "timed_out", "stop_timed_out"] { + let failed = TelemetryContext.enrich(event: "meeting_capture_health_snapshot", properties: ["capture_outcome": outcome], environment: permissions) + assertEqual(failed["failure_kind"], outcome, "failed outcome has a stable failure code") + assertEqual(failed["failure_stage"], "capture_stop", "failed outcome has a stage") + } let friction = TelemetryContext.enrich(event: "product_friction_observed", properties: ["stage": "dictation_start", "result": "started"], environment: permissions) assertEqual(friction["failure_kind"], "none", "normal friction observations are not failures") assertEqual(friction["failure_stage"], "dictation_start", "stage is explicit") diff --git a/Tests/UsageHealthStoreTests.swift b/Tests/UsageHealthStoreTests.swift index c4c5fe8bb..b93631a16 100644 --- a/Tests/UsageHealthStoreTests.swift +++ b/Tests/UsageHealthStoreTests.swift @@ -1,6 +1,33 @@ import Foundation func testUsageHealthStore() { + runSuite("Daily rollups handle local midnight, DST, and partial quit snapshots") { + let name = "UsageDaysTests-\(UUID().uuidString)" + let defaults = UserDefaults(suiteName: name)! + defer { defaults.removePersistentDomain(forName: name) } + let store = UsageHealthStore(userDefaults: defaults) + var calendar = Calendar(identifier: .gregorian) + calendar.timeZone = TimeZone(identifier: "America/Chicago")! + let now = ISO8601DateFormatter().date(from: "2026-03-08T07:30:00Z")! + let nextDay = calendar.date(byAdding: .day, value: 1, to: now)! + store.record(event: "dictation_completed", properties: ["duration_bucket": "10_29s"], now: now, calendar: calendar) + assertEqual(store.pendingDigests(includeCurrentDay: false, now: now, calendar: calendar).count, 0, "do not send an empty launch-day rollup") + let complete = store.pendingDigests(includeCurrentDay: false, now: nextDay, calendar: calendar) + assertEqual(complete.count, 1, "a closed local day rolls over across DST") + assertEqual(complete.first?.day, "2026-03-08", "report the activity's local day") + assertEqual(complete.first?.properties["digest_is_partial"], "false", "closed day is complete") + let partial = store.pendingDigests(includeCurrentDay: true, now: now, calendar: calendar).first! + assertEqual(partial.properties["digest_is_partial"], "true", "quit snapshots disclose partial coverage") + assertEqual(partial.id, complete.first?.id, "quit and rollover share a stable insert ID") + store.markDigestEnqueued(id: partial.id) + assertEqual(store.pendingDigests(includeCurrentDay: true, now: nextDay, calendar: calendar).count, 0, "at most one digest per reported local day") + AnalyticsPreferences.setEnabled(false, userDefaults: defaults) + store.clear() + AnalyticsPreferences.setEnabled(true, userDefaults: defaults) + store.record(event: "dictation_completed", properties: [:], now: now, calendar: calendar) + assertEqual(store.pendingDigests(includeCurrentDay: true, now: now, calendar: calendar).count, 0, "date-only receipt prevents duplicate digest after same-day re-opt-in") + assertEqual(UsageHealthStore.medianDurationBucket(["lt_10s": 1, "10_29s": 3, "30m_plus": 1]), "10_29s", "median uses ordered duration bins") + } runSuite("Usage health counts lifecycle outcomes and deduplicates paired failures") { let name = "UsageHealthTests-\(UUID().uuidString)" let defaults = UserDefaults(suiteName: name)! @@ -28,6 +55,21 @@ func testUsageHealthStore() { assertEqual(summary.qualityCounts["degraded"], 1, "fair capture quality is degraded, not an unknown or hard failure") assertEqual(UsageHealthStore(userDefaults: defaults).snapshot(now: now), summary, "metadata survives restart") } + runSuite("Unexpected no-audio stop records one failure and a failed quality outcome") { + let name = "UsageUnexpectedStopTests-\(UUID().uuidString)" + let defaults = UserDefaults(suiteName: name)! + defer { defaults.removePersistentDomain(forName: name) } + let store = UsageHealthStore(userDefaults: defaults) + let properties = ["correlation_id": UUID().uuidString, "failure_kind": "no_audio", "failure_stage": "capture_stop", "capture_outcome": "no_audio"] + store.record(event: "meeting_capture_stopped_under_controller", properties: properties) + store.record(event: "reliability_failure_observed", properties: properties) + store.record(event: "meeting_capture_health_snapshot", properties: properties) + assertEqual(store.snapshot().failures.map(\.kind), ["no_audio"], "terminal and canonical failure are deduplicated") + assertEqual(store.snapshot().qualityCounts["failed"], 1, "health records the failed capture once") + let digest = store.pendingDigests(includeCurrentDay: true).first! + assertEqual(digest.aggregates["failures_by_kind"]?["no_audio"], "1", "digest includes the terminal failure") + assertNotNil(SentryEventPolicy.policy(forEngine: "meeting", event: "meeting_capture_stopped_under_controller"), "unexpected stop remains a hard Sentry failure") + } runSuite("Usage health excludes cancellations and ignores content fields") { let name = "UsageHealthTests-\(UUID().uuidString)" let defaults = UserDefaults(suiteName: name)! diff --git a/docs/privacy-first-observability.md b/docs/privacy-first-observability.md index eb3d19c21..8ec9c126e 100644 --- a/docs/privacy-first-observability.md +++ b/docs/privacy-first-observability.md @@ -81,6 +81,7 @@ This list should match `Resources/analytics-events.psv`, which `Sources/Observability/AnalyticsEventPolicy.swift` compiles into the runtime allowlist. +- `usage_digest` - `reliability_failure_observed` - `app_launched` - `app_unclean_shutdown_detected` @@ -335,3 +336,69 @@ Capture degradation is a local warning and a PostHog health observation. It is never a Sentry error, even if an old producer accidentally requests error level. Hard start, transcript, audio-loss, stop-timeout, and engine-loop failures keep their existing Sentry path. No audio-quality or routing policy changes here. + +## Usage & health and daily digest + +With Usage stats enabled, Settings shows this calendar week's saved meetings +(including successful imports), completed dictations, rounded meeting-time range, +and good/degraded/failed capture mix. The last three failures show code, time, and +version. `fair` capture grades join degraded; discarded captures do not count as +successful quality outcomes. Unknown quality stays visibly unknown. + +The local ledger retains at most 14 local days and three failure summaries in +preferences. It consumes reviewed lifecycle metadata, not capture files or logs. +It stores aggregate rounded minutes and a histogram of dictation duration buckets, +not individual durations. Matching failure kind + correlation ID is counted once +across the canonical and lifecycle reports. Copy diagnostics and support drafts +include install UUID, release, OS, permissions, coarse runtime/route state, and +last failure taxonomy; they no longer append raw event or reliability-log text. + +`usage_digest` is emitted for closed local days on launch or the minute timer, +and for an unsent current day on normal quit. A quit snapshot has +`digest_is_partial=true`: later activity after a same-day relaunch is still visible +in lifecycle events and local weekly totals but does not generate a second digest. +`digest_day` identifies the activity's local date; timestamps identify delivery. +Calendar arithmetic handles local midnight and DST rather than adding 24 hours. + +Digest fields `meetings_started`, `meetings_completed`, `dictations_completed`, +and values inside `failures_by_kind` / `capture_quality_counts` use the shared +count buckets `0`, `1`, `2_3`, `4_9`, `10_plus`. `meeting_minutes_bucket` uses +`0`, `1_14m`, `15_59m`, `1_2h`, `3_9h`, `10h_plus`. +`dictation_median_duration_bucket` is the lower median bin of the duration +histogram, or `none` when no dictations completed. No exact count or duration is +sent by the digest. This deliberately follows the brief's bucket-only hard rule. +The two aggregate maps are encoded as JSON objects, never free-form JSON strings. + +Each digest is persisted in the existing bounded retry buffer before its day is +marked enqueued. Retries keep the same top-level `uuid`, event, timestamp, and distinct ID, so a retry after an uncertain +HTTP response does not intentionally count the rollup twice. Transport remains +best effort: digests expire after 14 days, ordinary events after 24 hours. +Up to 14 digests receive priority over lifecycle traffic within the same +100-record / 64 KiB file bound. Off disables new events/person properties/digests, +purges unsent captures, and clears the local usage ledger. Up to 14 date-only +enqueue receipts remain to prevent duplicate same-day digests after re-enabling; +they contain no IDs or activity counts. Crash reporting retains its separate +preference. No vendor, recording behavior, or release configuration changes. + +## Verify the candidate + +- Exercise a failure with a controlled test install. Match Sentry `user.id` to + PostHog `distinct_id`, and `correlation_id` + `failure_kind` on the Sentry event, + `reliability_failure_observed`, and the corresponding lifecycle failure. +- Break down new-build health snapshots by `quality_reason` and `capture_outcome`; + both must be present. Separate older releases when assessing null rates. +- Complete a degraded capture: the PostHog snapshot and local warning remain, + but `meeting.recording_capture_degraded` is absent from Sentry errors. +- Compare unique users across the whole selected window; never sum daily DAU. + Do not add canonical failure counts to the same lifecycle failures. +- Check Settings, copy diagnostics, then turn Usage stats off. Reopen and quit: + there must be no additional analytics/person/digest requests after opt-out. +- Check local-day rollover, repeated quit/relaunch, retry after a network failure, + and nested count maps. Synthetic local tests validate plumbing, not production + ingestion or physical audio behavior. Fleet changes require deployment. + +PostHog's [capture API](https://posthog.com/docs/api/capture) and +[person properties](https://posthog.com/docs/product-analytics/person-properties) +provide the wire contract for `$set` and event-based anonymous install profiles. + +Every capture disables GeoIP enrichment with `$geoip_disable=true`. Request transport IP handling is a server setting: PostHog's [Discard IP data setting](https://posthog.com/tutorials/web-redact-properties#hiding-customer-ip-address) should be verified separately; a client-side `ip: false` option does not provide that guarantee. diff --git a/scripts/entrypoints/lib/shared-smoke-sources.sh b/scripts/entrypoints/lib/shared-smoke-sources.sh index 0e58c7f05..b5ab14197 100644 --- a/scripts/entrypoints/lib/shared-smoke-sources.sh +++ b/scripts/entrypoints/lib/shared-smoke-sources.sh @@ -56,6 +56,7 @@ SHARED_TEST_STORAGE_SOURCES=( "Sources/Meeting/MeetingArtifactRenamer.swift" "Sources/Observability/ObservabilityTextRedactor.swift" "Sources/Observability/PayloadSanitizationCore.swift" + "Sources/Observability/UsageHealthModels.swift" "Sources/Observability/AnalyticsPayloadSanitizer.swift" "Sources/TranscriptedCore/Audio/MicRecordingSegment.swift" "Sources/TranscriptedCore/Logging/PrivacyTextRedactor.swift" diff --git a/scripts/ops/transcripted-qa-bench.sh b/scripts/ops/transcripted-qa-bench.sh index ea7ab1992..649569310 100755 --- a/scripts/ops/transcripted-qa-bench.sh +++ b/scripts/ops/transcripted-qa-bench.sh @@ -721,7 +721,7 @@ run_deep_tail() { run_full_tail() { run_step_when_present "60-release-health" "Deterministic release health gate" "yes" \ "scripts/ops/nightly-security-check.py" \ - "python3 scripts/ops/nightly-security-check.py --strict --automation-toml Tests/Fixtures/nightly-security-automation.toml --github-release-json Tests/Fixtures/release-health-github-release-1.1.58.json --write-report $(shell_quote "${RAW_DIR}/release-health.json")" + "python3 scripts/ops/nightly-security-check.py --strict --automation-toml Tests/Fixtures/nightly-security-automation.toml --github-release-json Tests/Fixtures/release-health-github-release-1.1.59.json --write-report $(shell_quote "${RAW_DIR}/release-health.json")" run_step_when_present "62-posthog-product-tasks" "PostHog product task fixture gate" "yes" \ "scripts/ops/posthog-product-dashboard-summary.py" \ From 9783455d31ff2439deb1a006e63e8d10e7b22d8e Mon Sep 17 00:00:00 2001 From: r3dbars Date: Thu, 10 Sep 2026 20:37:58 -0500 Subject: [PATCH 6/6] Remove customer-facing usage and health panel --- .agent-review/visuals/usage-health-empty.png | Bin 58239 -> 0 bytes .../visuals/usage-health-populated.png | Bin 87626 -> 0 bytes .agent-review/visuals/usage-health-review.md | 8 -- .../Settings/TranscriptedSettingsView.swift | 4 - .../Settings/UsageHealthSettingsSection.swift | 90 ------------------ docs/privacy-first-observability.md | 13 ++- 6 files changed, 6 insertions(+), 109 deletions(-) delete mode 100644 .agent-review/visuals/usage-health-empty.png delete mode 100644 .agent-review/visuals/usage-health-populated.png delete mode 100644 .agent-review/visuals/usage-health-review.md delete mode 100644 Sources/UI/Settings/UsageHealthSettingsSection.swift diff --git a/.agent-review/visuals/usage-health-empty.png b/.agent-review/visuals/usage-health-empty.png deleted file mode 100644 index 4a2d03dd00b9c3532c7cfc776fe796d2a5dd04a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 58239 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 mo&Nu)>_3B$|4*EaR{()0bkO%4FCKt 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 z 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